summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--cmake/QtBuildHelpers.cmake3
-rw-r--r--cmake/QtBuildInformation.cmake5
-rw-r--r--cmake/QtBuildRepoExamplesHelpers.cmake31
-rw-r--r--cmake/QtConfigDependencies.cmake.in4
-rw-r--r--cmake/QtModuleDependencies.cmake.in14
-rw-r--r--cmake/QtModuleToolsDependencies.cmake.in8
-rw-r--r--cmake/QtPluginDependencies.cmake.in10
-rw-r--r--cmake/QtProcessConfigureArgs.cmake29
-rw-r--r--cmake/QtPublicSbomCommonGenerationHelpers.cmake668
-rw-r--r--cmake/QtPublicSbomCycloneDXHelpers.cmake382
-rw-r--r--cmake/QtPublicSbomDepHelpers.cmake75
-rw-r--r--cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake425
-rw-r--r--cmake/QtPublicSbomGenerationHelpers.cmake426
-rw-r--r--cmake/QtPublicSbomHelpers.cmake568
-rw-r--r--cmake/QtPublicSbomLicenseHelpers.cmake18
-rw-r--r--cmake/QtPublicSbomOpsHelpers.cmake324
-rw-r--r--cmake/QtPublicSbomPurlHelpers.cmake79
-rw-r--r--cmake/QtPublicSbomPythonHelpers.cmake19
-rw-r--r--cmake/QtPublicSbomQtEntityHelpers.cmake7
-rw-r--r--cmake/QtSbomHelpers.cmake98
-rw-r--r--cmake/QtTargetHelpers.cmake19
-rw-r--r--cmake/QtWrapperScriptHelpers.cmake18
-rw-r--r--cmake/configure-cmake-mapping.md15
-rw-r--r--coin/instructions/prepare_building_env.yaml22
-rw-r--r--coin/instructions/vxworks_test_env_setup.yaml1
-rw-r--r--doc/global/config.qdocconf3
-rw-r--r--examples/widgets/doc/images/treemodel-structure.svg134
-rw-r--r--licenseRule.json5
-rw-r--r--src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch40
-rw-r--r--src/3rdparty/double-conversion/REUSE.toml2
-rw-r--r--src/3rdparty/double-conversion/double-conversion/double-to-string.cc3
-rw-r--r--src/3rdparty/pcre2/qt_attribution.json2
-rw-r--r--src/CMakeLists.txt1
-rw-r--r--src/assets/CMakeLists.txt6
-rw-r--r--src/assets/downloader/CMakeLists.txt32
-rw-r--r--src/assets/downloader/assetdownloader.cpp592
-rw-r--r--src/assets/downloader/assetdownloader.h118
-rw-r--r--src/assets/downloader/tasking/barrier.cpp54
-rw-r--r--src/assets/downloader/tasking/barrier.h126
-rw-r--r--src/assets/downloader/tasking/concurrentcall.h119
-rw-r--r--src/assets/downloader/tasking/conditional.cpp91
-rw-r--r--src/assets/downloader/tasking/conditional.h142
-rw-r--r--src/assets/downloader/tasking/networkquery.cpp63
-rw-r--r--src/assets/downloader/tasking/networkquery.h74
-rw-r--r--src/assets/downloader/tasking/qprocesstask.cpp280
-rw-r--r--src/assets/downloader/tasking/qprocesstask.h89
-rw-r--r--src/assets/downloader/tasking/tasking_global.h25
-rw-r--r--src/assets/downloader/tasking/tasktree.cpp3701
-rw-r--r--src/assets/downloader/tasking/tasktree.h757
-rw-r--r--src/assets/downloader/tasking/tasktreerunner.cpp45
-rw-r--r--src/assets/downloader/tasking/tasktreerunner.h63
-rw-r--r--src/assets/downloader/tasking/tcpsocket.cpp57
-rw-r--r--src/assets/downloader/tasking/tcpsocket.h65
-rw-r--r--src/corelib/CMakeLists.txt4
-rw-r--r--src/corelib/configure.cmake2
-rw-r--r--src/corelib/doc/images/javaiterators1.svg59
-rw-r--r--src/corelib/doc/images/javaiterators2.svg63
-rw-r--r--src/corelib/doc/src/java-style-iterators.qdoc4
-rw-r--r--src/corelib/doc/src/objectmodel/bindableproperties.qdoc4
-rw-r--r--src/corelib/io/qlockfile.cpp5
-rw-r--r--src/corelib/itemmodels/qrangemodel.cpp12
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h70
-rw-r--r--src/corelib/kernel/qcoreapplication.cpp2
-rw-r--r--src/corelib/kernel/qcoreapplication.h2
-rw-r--r--src/corelib/kernel/qpermissions.cpp4
-rw-r--r--src/corelib/kernel/qvariant.cpp152
-rw-r--r--src/corelib/kernel/qvariant.h132
-rw-r--r--src/corelib/mimetypes/qmimedatabase.cpp4
-rw-r--r--src/corelib/mimetypes/qmimeprovider.cpp19
-rw-r--r--src/corelib/mimetypes/qmimeprovider_p.h2
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp265
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h77
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp47
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h2
-rw-r--r--src/corelib/plugin/qlibrary.h1
-rw-r--r--src/corelib/plugin/qlibrary_p.h7
-rw-r--r--src/corelib/text/qstring.cpp95
-rw-r--r--src/corelib/thread/qthread.h28
-rw-r--r--src/corelib/time/qdatetime.cpp186
-rw-r--r--src/corelib/time/qgregoriancalendar.cpp1
-rw-r--r--src/corelib/time/qjalalicalendar.cpp1
-rw-r--r--src/corelib/time/qjuliancalendar.cpp1
-rw-r--r--src/corelib/time/qmilankoviccalendar.cpp1
-rw-r--r--src/corelib/time/qromancalendar.cpp1
-rw-r--r--src/corelib/time/qtimezone.cpp7
-rw-r--r--src/corelib/time/qtimezonelocale.cpp64
-rw-r--r--src/corelib/time/qtimezoneprivate.cpp12
-rw-r--r--src/corelib/time/qtimezoneprivate_p.h18
-rw-r--r--src/corelib/tools/qiterator.qdoc20
-rw-r--r--src/corelib/tools/quniquehandle_p.h103
-rw-r--r--src/gui/configure.cmake11
-rw-r--r--src/gui/doc/src/richtext.qdoc6
-rw-r--r--src/gui/image/qimage.cpp5
-rw-r--r--src/gui/image/qplatformpixmap.h7
-rw-r--r--src/gui/itemmodels/qfilesystemmodel.cpp9
-rw-r--r--src/gui/kernel/qevent.cpp75
-rw-r--r--src/gui/kernel/qinputdevice.cpp5
-rw-r--r--src/gui/kernel/qpointingdevice.cpp23
-rw-r--r--src/gui/painting/qtextureglyphcache.cpp2
-rw-r--r--src/gui/rhi/qrhi.cpp20
-rw-r--r--src/gui/rhi/qrhi.h4
-rw-r--r--src/gui/rhi/qrhid3d11.cpp2
-rw-r--r--src/gui/rhi/qrhid3d12.cpp2
-rw-r--r--src/gui/rhi/qrhigles2.cpp22
-rw-r--r--src/gui/rhi/qrhigles2_p.h3
-rw-r--r--src/gui/rhi/qrhimetal.mm8
-rw-r--r--src/gui/rhi/qrhimetal_p.h1
-rw-r--r--src/gui/rhi/qrhivulkan.cpp4
-rw-r--r--src/gui/rhi/qrhivulkan_p.h1
-rw-r--r--src/gui/text/qfont_p.h4
-rw-r--r--src/gui/text/qfontvariableaxis.cpp38
-rw-r--r--src/gui/text/qtextengine.cpp61
-rw-r--r--src/gui/text/qtextengine_p.h4
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp14
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h1
-rw-r--r--src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp3
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibility.mm1
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.mm5
-rw-r--r--src/plugins/platforms/ios/qiostheme.h1
-rw-r--r--src/plugins/platforms/ios/qiostheme.mm6
-rw-r--r--src/plugins/platforms/ios/quiwindow.mm2
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp9
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp65
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h2
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp13
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h2
-rw-r--r--src/plugins/platforms/wayland/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/wayland/qwaylandinputcontext.cpp10
-rw-r--r--src/plugins/platforms/windows/qwindowsiconengine.cpp6
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.cpp17
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.h2
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp32
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp1
-rw-r--r--src/plugins/platforms/xcb/CMakeLists.txt24
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp175
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h38
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp615
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h43
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp2807
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h86
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp2087
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h131
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h278
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qt_x11_p.h163
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qtessellator.cpp1466
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qtessellator_p.h51
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp288
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h64
-rw-r--r--src/plugins/platforms/xcb/qxcbimage.cpp6
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.cpp31
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.h2
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style.cpp173
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style_p.h1
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle.cpp14
-rw-r--r--src/testlib/qtestcase.cpp13
-rw-r--r--src/tools/windeployqt/main.cpp8
-rw-r--r--src/widgets/CMakeLists.txt1
-rw-r--r--src/widgets/accessible/itemviews.cpp4
-rw-r--r--src/widgets/accessible/itemviews_p.h4
-rw-r--r--src/widgets/accessible/rangecontrols.cpp10
-rw-r--r--src/widgets/accessible/rangecontrols_p.h1
-rw-r--r--src/widgets/dialogs/qcolordialog.cpp255
-rw-r--r--src/widgets/dialogs/qcolorwell_p.h142
-rw-r--r--src/widgets/dialogs/qfiledialog.ui10
-rw-r--r--src/widgets/dialogs/qfontdialog.cpp2
-rw-r--r--src/widgets/kernel/qlayout.cpp11
-rw-r--r--src/widgets/styles/qcommonstyle.cpp21
-rw-r--r--src/widgets/styles/qwindowsstyle.cpp13
-rw-r--r--src/widgets/widgets/qlineedit_p.cpp7
-rw-r--r--src/widgets/widgets/qtabbar.cpp2
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt6
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake63
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/check.cmake63
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake67
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake12
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/full.cmake17
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/minimal.cmake11
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt5
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt51
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp4
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt46
-rw-r--r--tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp4
-rw-r--r--tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp2
-rw-r--r--tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp22
-rw-r--r--tests/auto/corelib/global/qglobal/tst_qglobal.cpp2
-rw-r--r--tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp2
-rw-r--r--tests/auto/corelib/io/qdebug/tst_qdebug.cpp2
-rw-r--r--tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp6
-rw-r--r--tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp2
-rw-r--r--tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp7
-rw-r--r--tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp25
-rw-r--r--tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h7
-rw-r--r--tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp10
-rw-r--r--tests/auto/corelib/kernel/qobject/tst_qobject.cpp3
-rw-r--r--tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp4
-rw-r--r--tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp70
-rw-r--r--tests/auto/corelib/plugin/CMakeLists.txt4
-rw-r--r--tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt2
-rw-r--r--tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp8
-rw-r--r--tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp46
-rw-r--r--tests/auto/corelib/time/CMakeLists.txt5
-rw-r--r--tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp3
-rw-r--r--tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp796
-rw-r--r--tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt32
-rw-r--r--tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp784
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp6
-rw-r--r--tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp2
-rw-r--r--tests/auto/other/languagechange/tst_languagechange.cpp4
-rw-r--r--tests/auto/other/qaccessibility/tst_qaccessibility.cpp9
-rw-r--r--tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp4
-rw-r--r--tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp7
-rw-r--r--tests/manual/CMakeLists.txt1
-rw-r--r--tests/manual/assets/CMakeLists.txt4
-rw-r--r--tests/manual/assets/assets.pro3
-rw-r--r--tests/manual/assets/downloader/CMakeLists.txt15
-rw-r--r--tests/manual/assets/downloader/downloader.pro5
-rw-r--r--tests/manual/assets/downloader/main.cpp48
-rw-r--r--tests/manual/manual.pro1
-rw-r--r--tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp45
-rw-r--r--tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp2
-rw-r--r--util/sbom/cyclonedx/.gitignore6
-rw-r--r--util/sbom/cyclonedx/Makefile14
-rw-r--r--util/sbom/cyclonedx/README.md54
-rw-r--r--util/sbom/cyclonedx/pyproject.toml54
-rw-r--r--util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py2
-rw-r--r--util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py881
-rw-r--r--util/sbom/cyclonedx/uv.lock625
230 files changed, 7940 insertions, 17048 deletions
diff --git a/.gitignore b/.gitignore
index 83c3424e9ae..20aff5f53d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -224,6 +224,7 @@ qtc-qmldbg/
callgrind.out.*
core
Makefile*
+!/util/sbom/cyclonedx/Makefile
!/qmake/Makefile.win32*
!/qmake/Makefile.unix
pcviewer.cfg
diff --git a/cmake/QtBuildHelpers.cmake b/cmake/QtBuildHelpers.cmake
index b6132ddae7e..7205bad5253 100644
--- a/cmake/QtBuildHelpers.cmake
+++ b/cmake/QtBuildHelpers.cmake
@@ -295,10 +295,13 @@ function(qt_internal_get_qt_build_public_helpers out_var)
QtPublicPluginHelpers
QtPublicPluginHelpers_v2
QtPublicSbomAttributionHelpers
+ QtPublicSbomCommonGenerationHelpers
QtPublicSbomCpeHelpers
+ QtPublicSbomCycloneDXHelpers
QtPublicSbomDepHelpers
QtPublicSbomFileHelpers
QtPublicSbomGenerationHelpers
+ QtPublicSbomGenerationCycloneDXHelpers
QtPublicSbomHelpers
QtPublicSbomLicenseHelpers
QtPublicSbomOpsHelpers
diff --git a/cmake/QtBuildInformation.cmake b/cmake/QtBuildInformation.cmake
index 77c6d8184b3..5c44a7e5f48 100644
--- a/cmake/QtBuildInformation.cmake
+++ b/cmake/QtBuildInformation.cmake
@@ -47,6 +47,11 @@ function(qt_print_feature_summary)
endforeach()
endif()
+ # Print an SBOM section for a top-level build, or a single repo.
+ if(NOT QT_NO_SBOM_SUMMARY_INFO)
+ qt_internal_add_sbom_summary_info()
+ endif()
+
# Show which packages were found.
feature_summary(INCLUDE_QUIET_PACKAGES
WHAT PACKAGES_FOUND
diff --git a/cmake/QtBuildRepoExamplesHelpers.cmake b/cmake/QtBuildRepoExamplesHelpers.cmake
index a6d7c2b144b..1292844f132 100644
--- a/cmake/QtBuildRepoExamplesHelpers.cmake
+++ b/cmake/QtBuildRepoExamplesHelpers.cmake
@@ -90,6 +90,10 @@ macro(qt_examples_build_begin)
add_link_options(${active_linker_flags})
endif()
+ if(QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ add_compile_definitions(${QT_EXTRA_EXAMPLE_TARGET_DEFINES})
+ endif()
+
# Marker for warnings_as_errors.
set(QT_INTERNAL_IS_EXAMPLE_IN_TREE_BUILD ON)
endif()
@@ -200,8 +204,11 @@ function(qt_internal_add_example subdir)
# Don't show warnings for examples that were added via qt_internal_add_example.
# Those that are added via add_subdirectory will see the warning, due to the parent scope
# having the variable set to TRUE.
- if(QT_FEATURE_developer_build AND NOT QT_NO_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY_WARNING)
- set(QT_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY FALSE)
+ if(QT_FEATURE_developer_build)
+ set(QT_LINT_EXAMPLES ON)
+ if(NOT QT_NO_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY_WARNING)
+ set(QT_WARN_ABOUT_EXAMPLE_ADD_SUBDIRECTORY FALSE)
+ endif()
endif()
# Pre-compute unique example name based on the subdir, in case of target name clashes.
@@ -354,6 +361,10 @@ function(qt_internal_add_example_external_project subdir)
cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${singleOpts}" "${multiOpts}")
+ if(QT_FEATURE_developer_build)
+ set(QT_LINT_EXAMPLES ON)
+ endif()
+
_qt_internal_get_build_vars_for_external_projects(
CMAKE_DIR_VAR qt_cmake_dir
PREFIXES_VAR qt_prefixes
@@ -368,6 +379,10 @@ function(qt_internal_add_example_external_project subdir)
list(APPEND var_defs
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${qt_cmake_dir}/qt.toolchain.cmake
)
+ if(QT_LINT_EXAMPLES)
+ list(APPEND var_defs -DQT_LINT_EXAMPLES=ON)
+ endif()
+
else()
list(PREPEND CMAKE_PREFIX_PATH ${qt_prefixes})
@@ -519,6 +534,18 @@ function(qt_internal_add_example_external_project subdir)
"-DCMAKE_${item_type}_LINKER_FLAGS_INIT:STRING=${active_linker_flags}")
endforeach()
endif()
+ if(QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ unset(extra_args)
+ foreach(def IN LISTS QT_EXTRA_EXAMPLE_TARGET_DEFINES)
+ list(APPEND extra_args "-D${def}")
+ endforeach()
+ list(JOIN extra_args " " extra_defines_str)
+
+ foreach(language IN ITEMS C CXX OBJC OBJCXX)
+ list(APPEND var_defs
+ "-DCMAKE_${language}_FLAGS_INIT:STRING=${extra_defines_str}")
+ endforeach()
+ endif()
set(deps "")
list(REMOVE_DUPLICATES QT_EXAMPLE_DEPENDENCIES)
diff --git a/cmake/QtConfigDependencies.cmake.in b/cmake/QtConfigDependencies.cmake.in
index b006f052f7f..9144512a8f3 100644
--- a/cmake/QtConfigDependencies.cmake.in
+++ b/cmake/QtConfigDependencies.cmake.in
@@ -31,6 +31,8 @@ set(__qt_third_party_deps "@third_party_deps@")
# set _NOT_FOUND_MESSAGE which will be displayed by the includer of the Dependencies file.
set(${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED FALSE)
-_qt_internal_find_third_party_dependencies(@INSTALL_CMAKE_NAMESPACE@ __qt_third_party_deps)
+if(__qt_third_party_deps)
+ _qt_internal_find_third_party_dependencies(@INSTALL_CMAKE_NAMESPACE@ __qt_third_party_deps)
+endif()
set(@INSTALL_CMAKE_NAMESPACE@_FOUND TRUE)
diff --git a/cmake/QtModuleDependencies.cmake.in b/cmake/QtModuleDependencies.cmake.in
index ff84817ecf9..78ada0a7425 100644
--- a/cmake/QtModuleDependencies.cmake.in
+++ b/cmake/QtModuleDependencies.cmake.in
@@ -31,19 +31,25 @@ endif()
# note: _third_party_deps example: "ICU\\;FALSE\\;1.0\\;i18n uc data;ZLIB\\;FALSE\\;\\;"
set(__qt_@target@_third_party_deps "@third_party_deps@")
@third_party_deps_extra_info@
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+if(__qt_@target@_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+endif()
unset(__qt_@target@_third_party_deps)
# Find Qt tool package.
set(__qt_@target@_tool_deps "@main_module_tool_deps@")
-_qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+if(__qt_@target@_tool_deps)
+ _qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+endif()
unset(__qt_@target@_tool_deps)
# note: target_deps example: "Qt6Core\;5.12.0;Qt6Gui\;5.12.0"
set(__qt_@target@_target_deps "@target_deps@")
set(__qt_@target@_find_dependency_paths "${CMAKE_CURRENT_LIST_DIR}/.." "${_qt_cmake_dir}")
-_qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
- __qt_@target@_find_dependency_paths)
+if(__qt_@target@_target_deps)
+ _qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
+ __qt_@target@_find_dependency_paths)
+endif()
unset(__qt_@target@_target_deps)
unset(__qt_@target@_find_dependency_paths)
diff --git a/cmake/QtModuleToolsDependencies.cmake.in b/cmake/QtModuleToolsDependencies.cmake.in
index 14af9c90e38..51e0636fa91 100644
--- a/cmake/QtModuleToolsDependencies.cmake.in
+++ b/cmake/QtModuleToolsDependencies.cmake.in
@@ -5,7 +5,11 @@
set(@INSTALL_CMAKE_NAMESPACE@@target@_FOUND TRUE)
set(__qt_@target@_tool_third_party_deps "@third_party_deps@")
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_tool_third_party_deps)
+if(__qt_@target@_tool_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_tool_third_party_deps)
+endif()
set(__qt_@target@_tool_deps "@package_deps@")
-_qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+if(__qt_@target@_tool_deps)
+ _qt_internal_find_tool_dependencies("@target@" __qt_@target@_tool_deps)
+endif()
diff --git a/cmake/QtPluginDependencies.cmake.in b/cmake/QtPluginDependencies.cmake.in
index d5d3398e9e3..2ea6a19a732 100644
--- a/cmake/QtPluginDependencies.cmake.in
+++ b/cmake/QtPluginDependencies.cmake.in
@@ -5,7 +5,9 @@ set(@target@_FOUND TRUE)
# note: _third_party_deps example: "ICU\\;FALSE\\;1.0\\;i18n uc data;ZLIB\\;FALSE\\;\\;"
set(__qt_@target@_third_party_deps "@third_party_deps@")
-_qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+if(__qt_@target@_third_party_deps)
+ _qt_internal_find_third_party_dependencies("@target@" __qt_@target@_third_party_deps)
+endif()
unset(__qt_@target@_third_party_deps)
set(__qt_use_no_default_path_for_qt_packages "NO_DEFAULT_PATH")
@@ -16,8 +18,10 @@ endif()
# note: target_deps example: "Qt6Core\;5.12.0;Qt6Gui\;5.12.0"
set(__qt_@target@_target_deps "@target_deps@")
set(__qt_@target@_find_dependency_paths "@find_dependency_paths@")
-_qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
- __qt_@target@_find_dependency_paths)
+if(__qt_@target@_target_deps)
+ _qt_internal_find_qt_dependencies("@target@" __qt_@target@_target_deps
+ __qt_@target@_find_dependency_paths)
+endif()
unset(__qt_@target@_target_deps)
unset(__qt_@target@_find_dependency_paths)
diff --git a/cmake/QtProcessConfigureArgs.cmake b/cmake/QtProcessConfigureArgs.cmake
index 7118f1f6f92..05e179ba3b3 100644
--- a/cmake/QtProcessConfigureArgs.cmake
+++ b/cmake/QtProcessConfigureArgs.cmake
@@ -351,12 +351,33 @@ endfunction()
macro(qt_add_common_commandline_options)
qt_commandline_option(headersclean TYPE boolean)
qt_commandline_option(sbom TYPE boolean CMAKE_VARIABLE QT_GENERATE_SBOM)
- qt_commandline_option(sbom-json TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_JSON)
+
+ # Semi-public, undocumented.
+ qt_commandline_option(sbom-all TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_AND_VERIFY_ALL)
+
+ qt_commandline_option(sbom-spdx-v2 TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_GENERATE_SPDX_V2)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6 TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_GENERATE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-verify-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6)
+
+ qt_commandline_option(sbom-cyclonedx-v1_6-verbose TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_VERBOSE_CYDX_V1_6)
+
+ qt_commandline_option(sbom-json TYPE boolean CMAKE_VARIABLE QT_SBOM_GENERATE_SPDX_V2_JSON)
qt_commandline_option(sbom-json-required TYPE boolean
- CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_JSON
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON
)
- qt_commandline_option(sbom-verify TYPE boolean CMAKE_VARIABLE QT_SBOM_VERIFY)
- qt_commandline_option(sbom-verify-required TYPE boolean CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY)
+
+ qt_commandline_option(sbom-verify TYPE boolean CMAKE_VARIABLE QT_SBOM_VERIFY_SPDX_V2)
+ qt_commandline_option(sbom-verify-required TYPE boolean
+ CMAKE_VARIABLE QT_SBOM_REQUIRE_VERIFY_SPDX_V2)
endmacro()
function(qt_commandline_prefix arg var)
diff --git a/cmake/QtPublicSbomCommonGenerationHelpers.cmake b/cmake/QtPublicSbomCommonGenerationHelpers.cmake
new file mode 100644
index 00000000000..9de2055b5b6
--- /dev/null
+++ b/cmake/QtPublicSbomCommonGenerationHelpers.cmake
@@ -0,0 +1,668 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Helper function to get common project related variables for SBOM generation for both SPDX and
+# CycloneDX formats.
+function(_qt_internal_sbom_get_common_project_variables)
+ set(opt_args "")
+ set(single_args
+ # Forwarded
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ COPYRIGHT
+ PROJECT
+ PROJECT_FOR_SPDX_ID
+ SUPPLIER
+ SUPPLIER_URL
+
+ # Custom inputs
+ DEFAULT_SBOM_FILE_NAME_EXTENSION
+
+ # Out vars
+ OUT_VAR_PROJECT_NAME
+ OUT_VAR_CURRENT_UTC
+ OUT_VAR_CURRENT_YEAR
+ OUT_VAR_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID
+ OUT_VAR_COPYRIGHT
+ OUT_VAR_SUPPLIER
+ OUT_VAR_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(QT_SBOM_FAKE_TIMESTAMP)
+ set(current_utc "2590-01-01T11:33:55Z")
+ set(current_year "2590")
+ else()
+ string(TIMESTAMP current_utc UTC)
+ string(TIMESTAMP current_year "%Y" UTC)
+ endif()
+
+ set(${arg_OUT_VAR_CURRENT_UTC} "${current_utc}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_CURRENT_YEAR} "${current_year}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
+ set(${arg_OUT_VAR_PROJECT_NAME} "${arg_PROJECT}" PARENT_SCOPE)
+
+ _qt_internal_sbom_get_git_version_vars()
+
+ _qt_internal_path_join(default_sbom_file_name
+ "${arg_PROJECT}"
+ "${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.${arg_DEFAULT_SBOM_FILE_NAME_EXTENSION}")
+ _qt_internal_path_join(default_install_sbom_path
+ "\${CMAKE_INSTALL_PREFIX}/" "${CMAKE_INSTALL_DATAROOTDIR}" "${default_sbom_file_name}"
+ )
+
+ _qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
+ _qt_internal_sbom_set_default_option_value(OUTPUT_RELATIVE_PATH
+ "${default_sbom_file_name}")
+
+ set(${arg_OUT_VAR_OUTPUT} "${arg_OUTPUT}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_OUTPUT_RELATIVE_PATH} "${arg_OUTPUT_RELATIVE_PATH}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX_ID "Package-${arg_PROJECT}")
+ string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+ string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+
+ # Prevent collision with other generated SPDXID with -[0-9]+ suffix.
+ string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
+
+ set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
+ set(${arg_OUT_VAR_PROJECT_FOR_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
+ set(${arg_OUT_VAR_SUPPLIER} "${arg_SUPPLIER}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
+ "${PROJECT_HOMEPAGE_URL}")
+ set(${arg_OUT_VAR_SUPPLIER_URL} "${arg_SUPPLIER_URL}" PARENT_SCOPE)
+
+ _qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
+ set(${arg_OUT_VAR_COPYRIGHT} "${arg_COPYRIGHT}" PARENT_SCOPE)
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
+ else()
+ set(cmake_configs "${CMAKE_BUILD_TYPE}")
+ endif()
+
+ set(cmake_version "Built by CMake ${CMAKE_VERSION}")
+ set(system_name_and_processor "${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})")
+ set(default_project_comment
+ "${cmake_version} with ${cmake_configs} configuration for ${system_name_and_processor}")
+ set(${arg_OUT_VAR_DEFAULT_PROJECT_COMMENT} "${default_project_comment}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to save SBOM project path values like relative build and install dirs,
+# in global properties.
+function(_qt_internal_sbom_save_common_path_variables_in_global_properties)
+ set(opt_args "")
+ set(single_args
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ SBOM_DIR
+ PROPERTY_SUFFIX
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ get_filename_component(output_file_name_without_ext "${arg_OUTPUT}" NAME_WLE)
+ get_filename_component(output_file_ext "${arg_OUTPUT}" LAST_EXT)
+
+ set(computed_sbom_file_name_without_ext "${output_file_name_without_ext}${multi_config_suffix}")
+ set(computed_sbom_file_name "${output_file_name_without_ext}${output_file_ext}")
+
+ # In a super build and in a no-prefix build, put all the build time sboms into the same dir in,
+ # in the qtbase build dir.
+ if(QT_BUILDING_QT AND (QT_SUPERBUILD OR (NOT QT_WILL_INSTALL)))
+ set(build_sbom_root_dir "${QT_BUILD_DIR}")
+ else()
+ set(build_sbom_root_dir "${arg_SBOM_DIR}")
+ endif()
+
+ get_filename_component(output_relative_dir "${arg_OUTPUT_RELATIVE_PATH}" DIRECTORY)
+
+ set(build_sbom_dir "${build_sbom_root_dir}/${output_relative_dir}")
+ set(build_sbom_path "${build_sbom_dir}/${computed_sbom_file_name}")
+ set(build_sbom_path_without_ext
+ "${build_sbom_dir}/${computed_sbom_file_name_without_ext}")
+
+ set(install_sbom_path "${arg_OUTPUT}")
+
+ get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
+ set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
+
+ set(suffix "${arg_PROPERTY_SUFFIX}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_path${suffix} "${build_sbom_path}")
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext${suffix}
+ "${build_sbom_path_without_ext}")
+ set_property(GLOBAL PROPERTY _qt_sbom_build_output_dir${suffix} "${build_sbom_dir}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_path${suffix} "${install_sbom_path}")
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_path_without_ext${suffix}
+ "${install_sbom_path_without_ext}")
+ set_property(GLOBAL PROPERTY _qt_sbom_install_output_dir${suffix} "${install_sbom_dir}")
+endfunction()
+
+# Helper function to get SBOM project path values like relative build and install dirs,
+function(_qt_internal_sbom_get_common_path_variables_from_global_properties)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path${suffix})
+ get_property(sbom_build_output_path_without_ext GLOBAL PROPERTY
+ _qt_sbom_build_output_path_without_ext${suffix})
+ get_property(sbom_build_output_dir GLOBAL PROPERTY _qt_sbom_build_output_dir${suffix})
+
+ get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path${suffix})
+ get_property(sbom_install_output_path_without_ext GLOBAL PROPERTY
+ _qt_sbom_install_output_path_without_ext${suffix})
+ get_property(sbom_install_output_dir GLOBAL PROPERTY _qt_sbom_install_output_dir${suffix})
+
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH} "${sbom_build_output_path}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT}
+ "${sbom_build_output_path_without_ext}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_BUILD_OUTPUT_DIR)
+ set(${arg_OUT_VAR_SBOM_BUILD_OUTPUT_DIR} "${sbom_build_output_dir}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH} "${sbom_install_output_path}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT}
+ "${sbom_install_output_path_without_ext}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SBOM_INSTALL_OUTPUT_DIR)
+ set(${arg_OUT_VAR_SBOM_INSTALL_OUTPUT_DIR} "${sbom_install_output_dir}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Helper function to create a staging file for SBOM generation.
+# It is the file that will be incrementally assembled by having content appended to it.
+# Also creates the intro file that will add the assembled content for the SBOM project, aka for the
+# main SPDX package or root CycloneDX component.
+function(_qt_internal_sbom_create_sbom_staging_file)
+ set(opt_args "")
+ set(single_args
+ CONTENT
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ OUT_VAR_CREATE_STAGING_FILE
+ OUT_VAR_SBOM_DIR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ # Create the directory that will contain all sbom related files.
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ file(MAKE_DIRECTORY "${sbom_dir}")
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(doc_base_name "SPDXRef-DOCUMENT")
+ set(doc_extension "spdx.in")
+ set(suffix "")
+ set(extra_content "
+ set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
+")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(starting_message "Starting SPDX SBOM generation in build dir: ${staging_area_file}")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(doc_base_name "cydx-document")
+ set(doc_extension "cdx.in.toml")
+ set(suffix "_cydx")
+ set(extra_content "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ set(starting_message
+ "Starting CycloneDX SBOM TOML file generation in build dir: ${staging_area_file}")
+ endif()
+
+ # Generate project document intro spdx file.
+ set(document_intro_file_name
+ "${sbom_dir}/${doc_base_name}-${arg_REPO_PROJECT_NAME_LOWERCASE}.${doc_extension}")
+ file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$<CONFIG>")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ # Create cmake file to append the document intro spdx to the staging file.
+ set(create_staging_file
+ "${sbom_dir}/append_document_to_staging${suffix}${multi_config_suffix}.cmake")
+
+ set(content "
+ cmake_minimum_required(VERSION 3.16)
+ message(STATUS \"${starting_message}\")
+ ${extra_content}
+ file(READ \"${document_intro_file_name}\" content)
+ # Override any previous file because we're starting from scratch.
+ file(WRITE \"${staging_area_file}\" \"\${content}\")
+")
+ file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
+
+ set(${arg_OUT_VAR_CREATE_STAGING_FILE} "${create_staging_file}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SBOM_DIR} "${sbom_dir}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to save common project info like supplier, project name, spdx id in global
+# properties.
+function(_qt_internal_sbom_save_project_info_in_global_properties)
+ set(opt_args "")
+ set(single_args
+ SUPPLIER
+ SUPPLIER_URL
+ NAMESPACE
+ PROJECT
+ PROJECT_SPDX_ID
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set_property(GLOBAL PROPERTY _qt_sbom_project_supplier "${arg_SUPPLIER}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_supplier_url "${arg_SUPPLIER_URL}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_namespace "${arg_NAMESPACE}")
+
+ set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
+ set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${arg_PROJECT_SPDX_ID}")
+endfunction()
+
+# Helper function to get cmake include files for SBOM generation from global properties.
+function(_qt_internal_sbom_get_cmake_include_files)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ OUT_VAR_INCLUDES
+ OUT_VAR_BEFORE_CHECKSUM_INCLUDES
+ OUT_VAR_AFTER_CHECKSUM_INCLUDES
+ OUT_VAR_POST_GENERATION_INCLUDES
+ OUT_VAR_VERIFY_INCLUDES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ _qt_internal_sbom_collect_cmake_include_files(includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_include_files${suffix} _qt_sbom_cmake_end_include_files${suffix}
+ )
+
+ # Before checksum includes are included after the verification codes have been collected
+ # and before their merged checksum(s) has been computed.
+ _qt_internal_sbom_collect_cmake_include_files(before_checksum_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_before_checksum_include_files${suffix}
+ )
+
+ # After checksum includes are included after the checksum has been computed and written to the
+ # QT_SBOM_VERIFICATION_CODE variable.
+ _qt_internal_sbom_collect_cmake_include_files(after_checksum_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_after_checksum_include_files${suffix}
+ )
+
+ # Post generation includes are included for both build and install time sboms, after
+ # sbom generation has finished.
+ _qt_internal_sbom_collect_cmake_include_files(post_generation_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_post_generation_include_files${suffix}
+ )
+
+ # Verification only makes sense on installation, where the checksums are present.
+ _qt_internal_sbom_collect_cmake_include_files(verify_includes
+ JOIN_WITH_NEWLINES
+ PROPERTIES _qt_sbom_cmake_verify_include_files${suffix}
+ )
+
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_INCLUDES} "${includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_BEFORE_CHECKSUM_INCLUDES} "${before_checksum_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_AFTER_CHECKSUM_INCLUDES} "${after_checksum_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_POST_GENERATION_INCLUDES} "${post_generation_includes}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_INCLUDES)
+ set(${arg_OUT_VAR_VERIFY_INCLUDES} "${verify_includes}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Clears cmake include files for the current project from the global properties.
+function(_qt_internal_sbom_clear_cmake_include_files)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ # Clean up properties, so that they are empty for possible next repo in a top-level build.
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_before_checksum_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_after_checksum_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_post_generation_include_files${suffix} "")
+ set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files${suffix} "")
+endfunction()
+
+# Creates cmake build targets to create build-time SBOMs (for testing purposes only, because they
+# lack checksums for installed files).
+# Also creates the assemble_sbom cmake file that is used by both build-time and install-time
+# sbom generation.
+function(_qt_internal_sbom_create_build_time_sbom_targets)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE
+ SBOM_BUILD_OUTPUT_PATH
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT
+ SBOM_BUILD_OUTPUT_DIR
+ INCLUDES
+ POST_GENERATION_INCLUDES
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "")
+ set(extra_content "
+ set(QT_SBOM_PACKAGES \"\")
+ set(QT_SBOM_PACKAGES_WITH_VERIFICATION_CODES \"\")
+")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(final_message "Finalizing SPDX SBOM generation in build dir")
+ set(build_comment "SPDX document")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ set(extra_content "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ set(final_message "Finalizing Cyclone DX SBOM TOML generation in build dir")
+ set(build_comment "Cyclone DX document")
+ endif()
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(is_multi_config)
+ set(multi_config_suffix "-$<CONFIG>")
+ else()
+ set(multi_config_suffix "")
+ endif()
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(content "
+ # QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
+ # This allows reusing the same cmake file for both build and install.
+ if(NOT DEFINED QT_SBOM_BUILD_TIME)
+ set(QT_SBOM_BUILD_TIME TRUE)
+ endif()
+ if(NOT QT_SBOM_OUTPUT_PATH)
+ set(QT_SBOM_OUTPUT_DIR \"${arg_SBOM_BUILD_OUTPUT_DIR}\")
+ set(QT_SBOM_OUTPUT_PATH \"${arg_SBOM_BUILD_OUTPUT_PATH}\")
+ set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${arg_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT}\")
+ file(MAKE_DIRECTORY \"${arg_SBOM_BUILD_OUTPUT_DIR}\")
+ endif()
+ ${extra_content}
+ ${arg_INCLUDES}
+ if(QT_SBOM_BUILD_TIME)
+ message(STATUS \"${final_message}: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ ${arg_POST_GENERATION_INCLUDES}
+ endif()
+")
+ set(assemble_sbom "${sbom_dir}/assemble_sbom${suffix}${multi_config_suffix}.cmake")
+ file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
+
+ if(NOT TARGET sbom)
+ add_custom_target(sbom)
+ endif()
+
+ # Create a build target to create a build-time sbom (no verification codes or sha1s).
+ set(repo_sbom_target "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}${suffix}")
+ set(comment "")
+ string(APPEND comment "Assembling build time ${build_comment} without checksums for "
+ "${arg_REPO_PROJECT_NAME_LOWERCASE}. Just for testing.")
+ add_custom_target(${repo_sbom_target}
+ COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
+ COMMENT "${comment}"
+ VERBATIM
+ USES_TERMINAL # To avoid running two configs of the command in parallel
+ )
+
+ get_cmake_property(qt_repo_deps _qt_repo_deps_${arg_REAL_QT_REPO_PROJECT_NAME_LOWERCASE})
+ if(qt_repo_deps)
+ foreach(repo_dep IN LISTS qt_repo_deps)
+ set(repo_dep_sbom "sbom_${repo_dep}${suffix}")
+ if(TARGET "${repo_dep_sbom}")
+ add_dependencies(${repo_sbom_target} ${repo_dep_sbom})
+ endif()
+ endforeach()
+ endif()
+
+ add_dependencies(sbom ${repo_sbom_target})
+
+ set(${arg_OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH} "${assemble_sbom}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup install markers for multi-config generators.
+# Makes sure to wait for all configurations to finish installation before actually generating
+# the SBOM and removing the markers.
+function(_qt_internal_sbom_setup_multi_config_install_markers)
+ set(opt_args "")
+ set(single_args
+ SBOM_DIR
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+ OUT_VAR_EXTRA_CODE_BEGIN
+ OUT_VAR_EXTRA_CODE_INNER_END
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(extra_code_begin "")
+ set(extra_code_inner_end "")
+
+ get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
+ if(NOT is_multi_config)
+ set(${arg_OUT_VAR_EXTRA_CODE_BEGIN} "${extra_code_begin}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_EXTRA_CODE_INNER_END} "${extra_code_inner_end}" PARENT_SCOPE)
+ return()
+ endif()
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "_spdx")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ endif()
+
+ set(configs ${CMAKE_CONFIGURATION_TYPES})
+
+ set(install_markers_dir "${arg_SBOM_DIR}")
+ set(install_marker_path "${install_markers_dir}/finished_install${suffix}-$<CONFIG>.cmake")
+
+ set(install_marker_code "
+ message(STATUS \"Writing install marker for config $<CONFIG>: ${install_marker_path} \")
+ file(WRITE \"${install_marker_path}\" \"\")
+")
+
+ install(CODE "${install_marker_code}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${install_marker_code}"
+ COMPONENT "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}${suffix}"
+ EXCLUDE_FROM_ALL)
+ endif()
+
+ set(install_markers "")
+ foreach(config IN LISTS configs)
+ set(marker_path "${install_markers_dir}/finished_install${suffix}-${config}.cmake")
+ list(APPEND install_markers "${marker_path}")
+ # Remove the markers on reconfiguration, just in case there are stale ones.
+ if(EXISTS "${marker_path}")
+ file(REMOVE "${marker_path}")
+ endif()
+ endforeach()
+
+ # Escape the semicolons in install_makers, so they don't break argument parsing in
+ # _qt_internal_sbom_setup_sbom_install_code when they are forwarded there.
+ string(REPLACE ";" "\\;" install_markers "${install_markers}")
+
+ set(extra_code_begin "
+ set(QT_SBOM_INSTALL_MARKERS${suffix} \"${install_markers}\")
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS${suffix})
+ if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix} FALSE)
+ endif()
+ endforeach()
+")
+ set(extra_code_inner_end "
+ foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS${suffix})
+ message(STATUS
+ \"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
+ file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
+ endforeach()
+")
+
+ set(${arg_OUT_VAR_EXTRA_CODE_BEGIN} "${extra_code_begin}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_EXTRA_CODE_INNER_END} "${extra_code_inner_end}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup the fake checksum code snippet.
+function(_qt_internal_sbom_setup_fake_checksum)
+ set(opt_args "")
+ set(single_args
+ OUT_VAR_FAKE_CHECKSUM_CODE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ # Allow skipping checksum computation for testing purposes, while installing just the sbom
+ # documents, without requiring to build and install all the actual files.
+ set(extra_code "")
+ if(QT_SBOM_FAKE_CHECKSUM)
+ string(APPEND extra_code "
+ set(QT_SBOM_FAKE_CHECKSUM TRUE)")
+ endif()
+
+ set(${arg_OUT_VAR_FAKE_CHECKSUM_CODE} "${extra_code}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to setup the install-time SBOM generation code.
+function(_qt_internal_sbom_setup_sbom_install_code)
+ set(opt_args "")
+ set(single_args
+ SBOM_FORMAT
+ REPO_PROJECT_NAME_LOWERCASE
+
+ SBOM_INSTALL_OUTPUT_PATH
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT
+ SBOM_INSTALL_OUTPUT_DIR
+
+ ASSEMBLE_SBOM_INCLUDE_PATH
+
+ EXTRA_CODE_BEGIN
+ EXTRA_CODE_INNER_END
+ PROCESS_VERIFICATION_CODES
+
+ BEFORE_CHECKSUM_INCLUDES
+ AFTER_CHECKSUM_INCLUDES
+ POST_GENERATION_INCLUDES
+ VERIFY_INCLUDES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(arg_SBOM_FORMAT STREQUAL "SPDX_V2")
+ set(suffix "_spdx")
+ _qt_internal_get_staging_area_spdx_file_path(staging_area_file)
+ set(final_message "Finalizing SBOM generation in install dir")
+ set(process_verification_codes "
+ include(\"${arg_PROCESS_VERIFICATION_CODES}\")
+")
+ elseif(arg_SBOM_FORMAT STREQUAL "CYDX_V1_6")
+ set(suffix "_cydx")
+ set(final_message "Finalizing intermediate TOML generation in install dir")
+
+ set(process_verification_codes "")
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_file)
+ endif()
+
+ set(assemble_sbom_install "
+ set(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix} TRUE)
+ ${arg_EXTRA_CODE_BEGIN}
+ if(QT_SBOM_INSTALLED_ALL_CONFIGS${suffix})
+ set(QT_SBOM_BUILD_TIME FALSE)
+ set(QT_SBOM_OUTPUT_DIR \"${arg_SBOM_INSTALL_OUTPUT_DIR}\")
+ set(QT_SBOM_OUTPUT_PATH \"${arg_SBOM_INSTALL_OUTPUT_PATH}\")
+ set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${arg_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT}\")
+ file(MAKE_DIRECTORY \"${arg_SBOM_INSTALL_OUTPUT_DIR}\")
+ include(\"${arg_ASSEMBLE_SBOM_INCLUDE_PATH}\")
+ ${arg_BEFORE_CHECKSUM_INCLUDES}
+ ${process_verification_codes}
+ ${arg_AFTER_CHECKSUM_INCLUDES}
+ message(STATUS \"${final_message}: \${QT_SBOM_OUTPUT_PATH}\")
+ configure_file(\"${staging_area_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
+ ${arg_POST_GENERATION_INCLUDES}
+ ${arg_VERIFY_INCLUDES}
+ ${arg_EXTRA_CODE_INNER_END}
+ else()
+ message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
+ endif()
+")
+
+ install(CODE "${assemble_sbom_install}" COMPONENT sbom)
+ if(QT_SUPERBUILD)
+ install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${arg_REPO_PROJECT_NAME_LOWERCASE}"
+ EXCLUDE_FROM_ALL)
+ endif()
+endfunction()
diff --git a/cmake/QtPublicSbomCycloneDXHelpers.cmake b/cmake/QtPublicSbomCycloneDXHelpers.cmake
new file mode 100644
index 00000000000..a3dc52d4e39
--- /dev/null
+++ b/cmake/QtPublicSbomCycloneDXHelpers.cmake
@@ -0,0 +1,382 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Gets the helper python script name and relative dir in the source dir.
+function(_qt_internal_sbom_get_cyclone_dx_generator_script_name
+ out_var_generator_name
+ out_var_generator_relative_dir)
+ set(generator_name "qt_cyclonedx_generator.py")
+
+ _qt_internal_path_join(generator_relative_dir
+ "util" "sbom" "cyclonedx" "qt_cyclonedx_generator")
+
+ set(${out_var_generator_name} "${generator_name}" PARENT_SCOPE)
+ set(${out_var_generator_relative_dir} "${generator_relative_dir}" PARENT_SCOPE)
+endfunction()
+
+# Ges the path to the helper python script, which should be used to generate CycloneDX document.
+# Prefers the source path over the installed path, for easier development of the script.
+function(_qt_internal_sbom_get_cyclone_dx_generator_path out_var)
+ _qt_internal_sbom_get_cyclone_dx_generator_script_name(generator_name generator_relative_dir)
+
+ _qt_internal_path_join(qtbase_script_path
+ "${QT_SOURCE_TREE}" "${generator_relative_dir}" "${generator_name}")
+ _qt_internal_path_join(installed_script_path
+ "${QT6_INSTALL_PREFIX}" "${QT6_INSTALL_LIBEXECS}" "${generator_name}")
+
+ # qtbase sources available, always use them, regardless if it's a prefix or non-prefix build.
+ # Makes development easier.
+ if(EXISTS "${qtbase_script_path}")
+ set(script_path "${qtbase_script_path}")
+
+ # qtbase sources unavailable, use installed files.
+ elseif(EXISTS "${installed_script_path}")
+ set(script_path "${installed_script_path}")
+ else()
+ message(FATAL_ERROR "Can't find ${generator_name} file.")
+ endif()
+
+ set(${out_var} "${script_path}" PARENT_SCOPE)
+endfunction()
+
+# Parses the options for a single CYDX_PROPERTY_ENTRY, and creates a toml snippet to add a
+# CycloneDX property to the final toml document.
+function(_qt_internal_sbom_parse_cydx_property_entry_options)
+ set(opt_args "")
+ set(single_args
+ CYDX_PROPERTY_NAME
+ CYDX_PROPERTY_VALUE
+ OUT_VAR
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_CYDX_PROPERTY_NAME)
+ message(FATAL_ERROR "CYDX_PROPERTY_NAME is required.")
+ endif()
+
+ if(NOT arg_CYDX_PROPERTY_VALUE)
+ message(FATAL_ERROR "CYDX_PROPERTY_VALUE is required.")
+ endif()
+
+ if(NOT arg_OUT_VAR)
+ message(FATAL_ERROR "OUT_VAR is required.")
+ endif()
+
+ set(${arg_OUT_VAR} "
+[[components.properties]]
+name = \\\"${arg_CYDX_PROPERTY_NAME}\\\"
+value = \\\"${arg_CYDX_PROPERTY_VALUE}\\\"
+" PARENT_SCOPE)
+endfunction()
+
+# Processes a list of CycloneDX property entries, and creates their toml representation as output.
+function(_qt_internal_sbom_handle_cydx_properties)
+ set(opt_args "")
+ set(single_args
+ OUT_VAR_CYDX_PROPERTIES_STRING
+ )
+ set(multi_args
+ CYDX_PROPERTIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_OUT_VAR_CYDX_PROPERTIES_STRING)
+ message(FATAL_ERROR "OUT_VAR_CYDX_PROPERTIES_STRING is required.")
+ endif()
+
+ # Collect each CYDX_PROPERTY_ENTRY args into a separate variable.
+ set(prop_idx -1)
+ set(prop_entry_indices "")
+
+ foreach(prop_arg IN LISTS arg_CYDX_PROPERTIES)
+ if(prop_arg STREQUAL "CYDX_PROPERTY_ENTRY")
+ math(EXPR prop_idx "${prop_idx}+1")
+ list(APPEND prop_entry_indices "${prop_idx}")
+ elseif(prop_idx GREATER_EQUAL 0)
+ list(APPEND prop_${prop_idx}_args "${prop_arg}")
+ else()
+ message(FATAL_ERROR "Missing CYDX_PROPERTY_ENTRY keyword.")
+ endif()
+ endforeach()
+
+ set(properties_string "")
+
+ foreach(prop_idx IN LISTS prop_entry_indices)
+ _qt_internal_sbom_parse_cydx_property_entry_options(
+ ${prop_${prop_idx}_args}
+ OUT_VAR property_tuple
+ )
+
+ string(APPEND properties_string "${property_tuple}")
+ endforeach()
+
+ set(${arg_OUT_VAR_CYDX_PROPERTIES_STRING} "${properties_string}" PARENT_SCOPE)
+endfunction()
+
+# Outputs extra Cyclone DX properties based on the sbom entity type.
+function(_qt_internal_sbom_handle_qt_entity_cydx_properties)
+ set(opt_args "")
+ set(single_args
+ SBOM_ENTITY_TYPE
+ OUT_CYDX_PROPERTIES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_SBOM_ENTITY_TYPE)
+ message(FATAL_ERROR "SBOM_ENTITY_TYPE is required.")
+ endif()
+
+ if(NOT arg_OUT_CYDX_PROPERTIES)
+ message(FATAL_ERROR "OUT_CYDX_PROPERTIES is required.")
+ endif()
+
+ set(cydx_properties "")
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:entity_type"
+ CYDX_PROPERTY_VALUE "${arg_SBOM_ENTITY_TYPE}"
+ )
+
+ _qt_internal_sbom_is_qt_entity_type("${arg_SBOM_ENTITY_TYPE}" is_qt_entity_type)
+ if(is_qt_entity_type)
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:is_qt_entity_type"
+ CYDX_PROPERTY_VALUE "true"
+ )
+ endif()
+ _qt_internal_sbom_is_qt_3rd_party_entity_type("${arg_SBOM_ENTITY_TYPE}"
+ is_qt_3rd_party_entity_type)
+ if(is_qt_3rd_party_entity_type)
+ list(APPEND cydx_properties
+ CYDX_PROPERTY_ENTRY
+ CYDX_PROPERTY_NAME "qt:sbom:is_qt_3rd_party_entity_type"
+ CYDX_PROPERTY_VALUE "true"
+ )
+ endif()
+
+ set(${arg_OUT_CYDX_PROPERTIES} "${cydx_properties}" PARENT_SCOPE)
+endfunction()
+
+# Maps an sbom entity type to a cyclone dx component type.
+function(_qt_internal_sbom_get_cyclone_component_type out_var sbom_entity_type)
+ set(library_types
+ "QT_MODULE"
+ "QT_PLUGIN"
+ "QML_PLUGIN"
+ "QT_THIRD_PARTY_MODULE"
+ "QT_THIRD_PARTY_SOURCES"
+ "SYSTEM_LIBRARY"
+ "LIBRARY"
+ "THIRD_PARTY_LIBRARY"
+ "THIRD_PARTY_LIBRARY_WITH_FILES"
+ "THIRD_PARTY_SOURCES"
+ )
+
+ set(application_types
+ "QT_TOOL"
+ "QT_APP"
+ "EXECUTABLE"
+ )
+
+ if(sbom_entity_type IN_LIST library_types)
+ set(component_type "library")
+ elseif(sbom_entity_type IN_LIST application_types)
+ set(component_type "application")
+ else()
+ # Default to library for now, because it's unclear what would be a better default.
+ set(component_type "library")
+ endif()
+
+ set(${out_var} "${component_type}" PARENT_SCOPE)
+endfunction()
+
+# Generates a pseudo-unique serial number for a CycloneDX sbom document.
+#
+# The spec says that a BOM serial number must conform to RFC 4122, but doesn't specify which
+# kind of uuid version should be generated.
+# The upstream python library generates a version 4 uuid, which is fully random.
+# CMake can only generate version 3 and 5 uuids, which are fully deterministic based on the given
+# NAMESPACE and NAME values.
+# Generating a fully random uuid prevents build reproducibility. The maintainer of the Cyclone DX
+# spec even mentions that here:
+# https://github.com/CycloneDX/specification/issues/97#issuecomment-955904904
+# And yet to to do component-wise inter-document linking using the BOM-Link mechanism, you have to
+# use serial numbers.
+#
+# Because the spec doesn't explicitly prohibit it, we will generate a version 5 uuid based on the
+# SPDX_NAMESPACE passed to the function, which is supposed to be unique enough, because it contains
+# the project / document name (e.g. qtbase) and its version or git version.
+# This should alleviate the reproducibility problem as well.
+function(_qt_internal_sbom_get_cyclone_bom_serial_number)
+ set(opt_args "")
+ set(single_args
+ SPDX_NAMESPACE
+ OUT_VAR_UUID
+ OUT_VAR_SERIAL_NUMBER
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(SPDX_NAMESPACE "")
+
+ # This is a randomly generated uuid v4 value. To be used for all eternity. Until we change the
+ # implementation of the function.
+ set(uuid_namespace "c024642f-9853-45b2-9bfd-ab3f061a05bb")
+
+ string(UUID uuid NAMESPACE "${uuid_namespace}" NAME "${arg_SPDX_NAMESPACE}" TYPE SHA1)
+ set(cyclone_dx_serial_number "urn:cdx:${uuid}")
+
+ if(arg_OUT_VAR_UUID)
+ set("${arg_OUT_VAR_UUID}" "${uuid}" PARENT_SCOPE)
+ endif()
+ if(arg_OUT_VAR_SERIAL_NUMBER)
+ set("${arg_OUT_VAR_SERIAL_NUMBER}" "${cyclone_dx_serial_number}" PARENT_SCOPE)
+ endif()
+endfunction()
+
+# See https://github.com/CycloneDX/guides/blob/main/SBOM/en/0x52-Linking.md
+function(_qt_internal_sbom_get_cydx_external_bom_link target out_var)
+ get_target_property(spdx_id "${target}" _qt_sbom_spdx_id)
+ get_target_property(bom_serial_number "${target}" _qt_sbom_cydx_bom_serial_number_uuid)
+
+ set(bom_version "1")
+ set(bom_link "urn:cdx:${bom_serial_number}/${bom_version}#${spdx_id}")
+
+ set(${out_var} "${bom_link}" PARENT_SCOPE)
+endfunction()
+
+# Records necessary details of external target dependencies in global properties, to later create
+# the CycloneDX packages for them. The info collection needs to be done immediately in the directory
+# scope where the targets were found, because they might not be global, and thus can't be accessed
+# later.
+function(_qt_internal_sbom_record_external_target_dependecies)
+ set(opt_args "")
+ set(single_args "")
+ set(multi_args
+ TARGETS
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_TARGETS)
+ return()
+ endif()
+
+ get_property(existing_ids GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids)
+ if(NOT existing_ids)
+ set(existing_ids "")
+ endif()
+
+ foreach(target IN LISTS arg_TARGETS)
+ # Use the full spdx id (one prefixed with the containing DocumentRef-) because that's what
+ # our spdx dependency relationships use at the moment.
+ # Both Foo and FooPrivate map to the same spdx_id, so we need to avoid duplicates on spdx id
+ # level.
+ get_target_property(spdx_id "${target}" _qt_sbom_spdx_id)
+
+ if(spdx_id IN_LIST existing_ids)
+ continue()
+ endif()
+
+ list(APPEND existing_ids "${spdx_id}")
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_external_target_dep_ids "${spdx_id}")
+
+ # This is checked in _qt_internal_sbom_add_target, to prevent duplicate creation of
+ # system library targets.
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_external_target_dependencies
+ "${target}")
+
+ get_target_property(package_name "${target}" _qt_sbom_package_name)
+ get_target_property(sbom_entity_type "${target}" _qt_sbom_entity_type)
+ get_target_property(package_version "${target}" _qt_sbom_package_version)
+ _qt_internal_sbom_get_cydx_external_bom_link("${target}" external_bom_link)
+
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_target"
+ "${target}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_package_name"
+ "${package_name}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_sbom_entity_type"
+ "${sbom_entity_type}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_package_version"
+ "${package_version}")
+ set_property(GLOBAL
+ PROPERTY "_qt_internal_sbom_external_target_dep_${spdx_id}_external_bom_link"
+ "${external_bom_link}")
+ endforeach()
+endfunction()
+
+# Goes through the list of recorded external target dependencies collected during target
+# dependency analysis, and adds them as CycloneDX packages to the CycloneDX document.
+# This is different from SPDX v2.3, which doesn't require creating a package for dependencies that
+# are defined in a different document.
+function(_qt_internal_sbom_add_cydx_external_target_dependencies)
+ get_property(spdx_ids GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids)
+ if(NOT spdx_ids)
+ # Clean up external target dependencies, before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies "")
+ return()
+ endif()
+
+ # Just in case, don't add duplicates.
+ set(visited_spdx_ids "")
+
+ foreach(spdx_id IN LISTS spdx_ids)
+ if(spdx_id IN_LIST visited_spdx_ids)
+ continue()
+ endif()
+
+ get_cmake_property(package_name
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_package_name")
+ get_cmake_property(sbom_entity_type
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_sbom_entity_type")
+ get_cmake_property(package_version
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_package_version")
+ get_cmake_property(external_bom_link
+ "_qt_internal_sbom_external_target_dep_${spdx_id}_external_bom_link")
+
+ _qt_internal_sbom_generate_cyclone_add_package(
+ PACKAGE "${package_name}"
+ SPDXID "${spdx_id}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ VERSION "${package_version}"
+ EXTERNAL_BOM_LINK "${external_bom_link}"
+ )
+
+ list(APPEND visited_spdx_ids "${spdx_id}")
+ endforeach()
+
+ # Clean up external target dependencies, before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dep_ids "")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies "")
+endfunction()
+
+# Records a license id and its text in global properties, to be added to the CycloneDX document
+# later.
+function(_qt_internal_sbom_record_license_cydx)
+ set(opt_args "")
+ set(single_args
+ LICENSE_ID
+ EXTRACTED_TEXT
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_internal_sbom_cydx_licenses "${arg_LICENSE_ID}")
+ set_property(GLOBAL PROPERTY
+ _qt_internal_sbom_cydx_licenses_${arg_LICENSE_ID}_text "${arg_EXTRACTED_TEXT}"
+ )
+endfunction()
diff --git a/cmake/QtPublicSbomDepHelpers.cmake b/cmake/QtPublicSbomDepHelpers.cmake
index 32e3b6b832c..794f3f77db2 100644
--- a/cmake/QtPublicSbomDepHelpers.cmake
+++ b/cmake/QtPublicSbomDepHelpers.cmake
@@ -4,11 +4,17 @@
# Walks a target's direct dependencies and assembles a list of relationships between the packages
# of the target dependencies.
# Currently handles various Qt targets and system libraries.
+# For SPDX documents, it collects the relationships in OUT_SPDX_RELATIONSHIPS.
+# For CYDX documents, it collects the dependencies (not relationship info) in OUT_CYDX_DEPENDENCIES.
+# If a CYDX dependency is in an external docuemnt, the dependency is added to
+# OUT_EXTERNAL_TARGET_DEPENDENCIES instead.
function(_qt_internal_sbom_handle_target_dependencies target)
set(opt_args "")
set(single_args
SPDX_ID
- OUT_RELATIONSHIPS
+ OUT_CYDX_DEPENDENCIES
+ OUT_SPDX_RELATIONSHIPS
+ OUT_EXTERNAL_TARGET_DEPENDENCIES
)
set(multi_args
LIBRARIES
@@ -75,8 +81,12 @@ function(_qt_internal_sbom_handle_target_dependencies target)
set(all_direct_libraries ${libraries} ${public_libraries} ${sbom_dependencies})
list(REMOVE_DUPLICATES all_direct_libraries)
- set(spdx_dependencies "")
+ set(regular_cydx_dependencies "")
+ set(regular_spdx_dependencies "")
+
+ set(external_cydx_dependencies "")
set(external_spdx_dependencies "")
+ set(external_target_dependencies "")
# Go through each direct linked lib.
foreach(direct_lib IN LISTS all_direct_libraries)
@@ -108,7 +118,8 @@ function(_qt_internal_sbom_handle_target_dependencies target)
# Add a dependency on the vendored lib instead of the Wrap target.
if(is_3rdparty_bundled_lib AND lib_spdx_id)
- list(APPEND spdx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
set(bundled_targets_found TRUE)
endif()
endforeach()
@@ -148,30 +159,38 @@ function(_qt_internal_sbom_handle_target_dependencies target)
if(NOT is_dependency_in_external_document)
# If the target is not in the external document, it must be one built as part of the
# current project.
- list(APPEND spdx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_cydx_dependencies "${lib_spdx_id}")
+ list(APPEND regular_spdx_dependencies "${lib_spdx_id}")
else()
# Refer to the package in the external document. This can be the case
# in a top-level build, where a system library is reused across repos, or for any
# regular dependency that was built as part of a different project.
_qt_internal_sbom_add_external_target_dependency("${direct_lib}"
- extra_spdx_dependencies
+ OUT_CYDX_DEPENDENCIES extra_cydx_dependencies
+ OUT_SPDX_DEPENDENCIES extra_spdx_dependencies
+ OUT_TARGET_DEPENDENCIES extra_target_dependencies
)
if(extra_spdx_dependencies)
+ list(APPEND external_cydx_dependencies ${extra_cydx_dependencies})
list(APPEND external_spdx_dependencies ${extra_spdx_dependencies})
+ list(APPEND external_target_dependencies ${extra_target_dependencies})
endif()
endif()
endforeach()
- set(relationships "")
+ set(spdx_relationships "")
+
# Keep the external dependencies first, so they are neatly ordered.
- foreach(dep_spdx_id IN LISTS external_spdx_dependencies spdx_dependencies)
- set(relationship
- "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}"
- )
- list(APPEND relationships "${relationship}")
+ foreach(dep_spdx_id IN LISTS external_spdx_dependencies regular_spdx_dependencies)
+ set(relationship "${package_spdx_id} DEPENDS_ON ${dep_spdx_id}")
+ list(APPEND spdx_relationships "${relationship}")
endforeach()
- set(${arg_OUT_RELATIONSHIPS} "${relationships}" PARENT_SCOPE)
+ set(all_cydx_dependencies ${external_cydx_dependencies} ${regular_cydx_dependencies})
+
+ set(${arg_OUT_CYDX_DEPENDENCIES} "${all_cydx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_RELATIONSHIPS} "${spdx_relationships}" PARENT_SCOPE)
+ set(${arg_OUT_EXTERNAL_TARGET_DEPENDENCIES} "${external_target_dependencies}" PARENT_SCOPE)
endfunction()
# Checks whether the current target will have its sbom generated into the current repo sbom
@@ -205,18 +224,34 @@ function(_qt_internal_sbom_is_external_target_dependency target)
endfunction()
# Handles generating an external document reference SDPX element for each target package that is
-# located in a different spdx document.
-function(_qt_internal_sbom_add_external_target_dependency target out_spdx_dependencies)
+# located in a different spdx document, and collects the reference in OUT_SPDX_DEPENDENCIES.
+# In case of CycloneDX, we just collect the dependency spdx id (bom-ref) and the target name,
+# which are added to OUT_CYDX_DEPENDENCIES and OUT_TARGET_DEPENDENCIES respectively.
+function(_qt_internal_sbom_add_external_target_dependency target)
+ set(opt_args "")
+ set(single_args
+ OUT_CYDX_DEPENDENCIES
+ OUT_SPDX_DEPENDENCIES
+ OUT_TARGET_DEPENDENCIES
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
_qt_internal_sbom_get_spdx_id_for_target("${target}" dep_spdx_id)
if(NOT dep_spdx_id)
message(DEBUG "Could not add external target dependency on ${target} "
"because no spdx id could be found")
- set(${out_spdx_dependencies} "" PARENT_SCOPE)
+ set(${arg_OUT_CYDX_DEPENDENCIES} "" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_DEPENDENCIES} "" PARENT_SCOPE)
+ set(${arg_OUT_TARGET_DEPENDENCIES} "" PARENT_SCOPE)
return()
endif()
+ set(cydx_dependencies "")
set(spdx_dependencies "")
+ set(target_depdendencies "")
# Get the external document path and the repo it belongs to for the given target.
get_property(relative_installed_repo_document_path TARGET ${target}
@@ -232,9 +267,11 @@ function(_qt_internal_sbom_add_external_target_dependency target out_spdx_depend
get_cmake_property(known_external_document
_qt_known_external_documents_${external_document_ref})
- set(dependency "${external_document_ref}:${dep_spdx_id}")
+ set(spdx_dependency "${external_document_ref}:${dep_spdx_id}")
- list(APPEND spdx_dependencies "${dependency}")
+ list(APPEND cydx_dependencies "${dep_spdx_id}")
+ list(APPEND spdx_dependencies "${spdx_dependency}")
+ list(APPEND target_depdendencies "${target}")
# Only add a reference to the external document package, if we haven't done so already.
if(NOT known_external_document)
@@ -261,5 +298,7 @@ function(_qt_internal_sbom_add_external_target_dependency target out_spdx_depend
"Missing spdx document path for external target dependency: ${target}")
endif()
- set(${out_spdx_dependencies} "${spdx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_CYDX_DEPENDENCIES} "${cydx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_SPDX_DEPENDENCIES} "${spdx_dependencies}" PARENT_SCOPE)
+ set(${arg_OUT_TARGET_DEPENDENCIES} "${target_depdendencies}" PARENT_SCOPE)
endfunction()
diff --git a/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake b/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake
new file mode 100644
index 00000000000..59797d24b7d
--- /dev/null
+++ b/cmake/QtPublicSbomGenerationCycloneDXHelpers.cmake
@@ -0,0 +1,425 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+# Helper to return the path to staging cydx file, where content will be incrementally appended to.
+function(_qt_internal_get_staging_area_cydx_file_path out_var)
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ set(staging_area_cydx_file "${sbom_dir}/staging-${repo_project_name_lowercase}.cdx.in.toml")
+ set(${out_var} "${staging_area_cydx_file}" PARENT_SCOPE)
+endfunction()
+
+# Starts recording information for the generation of a CycloneDX sbom for a project.
+# Similar to _qt_internal_sbom_begin_project_generate which is the SPDX variant.
+function(_qt_internal_sbom_begin_project_generate_cyclone)
+ set(opt_args "")
+ set(single_args
+ OUTPUT
+ OUTPUT_RELATIVE_PATH
+ LICENSE
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ PROJECT
+ PROJECT_COMMENT
+ PROJECT_FOR_SPDX_ID
+ SUPPLIER
+ SUPPLIER_URL
+ NAMESPACE
+ BOM_SERIAL_NUMBER_UUID
+ CPE
+ OUT_VAR_PROJECT_SPDX_ID
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_forward_function_args(
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR common_project_args
+ FORWARD_SINGLE
+ ${single_args}
+ )
+
+ _qt_internal_sbom_get_common_project_variables(
+ ${common_project_args}
+ OUT_VAR_PROJECT_NAME arg_PROJECT
+ OUT_VAR_CURRENT_UTC current_utc
+ OUT_VAR_CURRENT_YEAR current_year
+ DEFAULT_SBOM_FILE_NAME_EXTENSION "cdx"
+ OUT_VAR_OUTPUT arg_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH arg_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID project_spdx_id
+ OUT_VAR_COPYRIGHT arg_COPYRIGHT
+ OUT_VAR_SUPPLIER arg_SUPPLIER
+ OUT_VAR_SUPPLIER_URL arg_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT project_comment
+ )
+ if(arg_OUT_VAR_PROJECT_SPDX_ID)
+ set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+ endif()
+
+ _qt_internal_sbom_get_git_version_vars()
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(BOM_SERIAL_NUMBER_UUID "")
+
+ if(arg_PROJECT_COMMENT)
+ string(APPEND project_comment "${arg_PROJECT_COMMENT}")
+ endif()
+
+ _qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "")
+ set(download_location_field "")
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location_field "download_location = \"${arg_DOWNLOAD_LOCATION}\"")
+ endif()
+
+ set(content "
+[root_component]
+name = \"${arg_PROJECT}\" # Required
+spdx_id = \"${project_spdx_id}\" # Required
+version = \"${QT_SBOM_GIT_VERSION}\" # Required
+supplier = '${arg_SUPPLIER}' # Required
+supplier_url = \"${arg_SUPPLIER_URL}\" # Required
+${download_location_field}
+build_date = \"${current_utc}\"
+serial_number_uuid = \"${arg_BOM_SERIAL_NUMBER_UUID}\" # Required
+description = '''${project_comment}
+'''
+
+[[project_build_tools]]
+name = \"cmake\"
+version = \"${CMAKE_VERSION}\"
+component_type = \"application\"
+description = \"Build system tool used to build the project.\"
+")
+
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_create_sbom_staging_file(
+ CONTENT "${content}"
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_CREATE_STAGING_FILE create_staging_file
+ OUT_VAR_SBOM_DIR sbom_dir
+ )
+
+ _qt_internal_sbom_save_project_info_in_global_properties(
+ SUPPLIER "${arg_SUPPLIER}"
+ SUPPLIER_URL "${arg_SUPPLIER_URL}"
+ NAMESPACE "${arg_NAMESPACE}"
+ PROJECT "${arg_PROJECT}"
+ PROJECT_SPDX_ID "${project_spdx_id}"
+ )
+
+ _qt_internal_sbom_save_common_path_variables_in_global_properties(
+ OUTPUT "${arg_OUTPUT}"
+ OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
+ SBOM_DIR "${sbom_dir}"
+ PROPERTY_SUFFIX _cydx
+ )
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files_cydx "${create_staging_file}")
+endfunction()
+
+# Finalizes the CycloneDX sbom generation for a project.
+function(_qt_internal_sbom_end_project_generate_cyclone)
+ _qt_internal_sbom_get_common_path_variables_from_global_properties(
+ SBOM_FORMAT "CYDX_V1_6"
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH sbom_install_output_path
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT sbom_install_output_path_without_ext
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR sbom_install_output_dir
+ )
+
+ if(NOT sbom_build_output_path)
+ message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
+ endif()
+
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
+
+ # Process licenses before getting the includes.
+ _qt_internal_sbom_add_recorded_licenses_cydx()
+
+ _qt_internal_sbom_get_cmake_include_files(
+ SBOM_FORMAT "CYDX_V1_6"
+ OUT_VAR_INCLUDES includes
+ OUT_VAR_POST_GENERATION_INCLUDES post_generation_includes
+ )
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+
+ set(build_time_args "")
+ if(includes)
+ list(APPEND build_time_args INCLUDES "${includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ _qt_internal_sbom_create_build_time_sbom_targets(
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
+ SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT "${sbom_build_output_path_without_ext}"
+ SBOM_BUILD_OUTPUT_DIR "${sbom_build_output_dir}"
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH assemble_sbom
+ ${build_time_args}
+ )
+
+ _qt_internal_sbom_setup_multi_config_install_markers(
+ SBOM_DIR "${sbom_dir}"
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
+ OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
+ )
+
+ _qt_internal_sbom_setup_fake_checksum(
+ OUT_VAR_FAKE_CHECKSUM_CODE extra_code_begin_fake_checksum
+ )
+ if(extra_code_begin_fake_checksum)
+ string(APPEND extra_code_begin "${extra_code_begin_fake_checksum}")
+ endif()
+
+ set(setup_sbom_install_args "")
+ if(extra_code_begin)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_BEGIN "${extra_code_begin}")
+ endif()
+ if(extra_code_inner_end)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_INNER_END "${extra_code_inner_end}")
+ endif()
+ if(before_checksum_includes)
+ list(APPEND setup_sbom_install_args BEFORE_CHECKSUM_INCLUDES "${before_checksum_includes}")
+ endif()
+ if(after_checksum_includes)
+ list(APPEND setup_sbom_install_args AFTER_CHECKSUM_INCLUDES "${after_checksum_includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND setup_sbom_install_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ if(verify_includes)
+ list(APPEND setup_sbom_install_args VERIFY_INCLUDES "${verify_includes}")
+ endif()
+
+ _qt_internal_sbom_setup_sbom_install_code(
+ SBOM_FORMAT "CYDX_V1_6"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
+ SBOM_INSTALL_OUTPUT_DIR "${sbom_install_output_dir}"
+ ASSEMBLE_SBOM_INCLUDE_PATH "${assemble_sbom}"
+ ${setup_sbom_install_args}
+ )
+
+ _qt_internal_sbom_clear_cmake_include_files(
+ SBOM_FORMAT "CYDX_V1_6"
+ )
+endfunction()
+
+# Helper to add info about a package to the sbom. Usually a package is a mapping to a cmake target.
+function(_qt_internal_sbom_generate_cyclone_add_package)
+ set(opt_args "")
+ set(single_args
+ PACKAGE
+ VERSION
+ LICENSE_DECLARED
+ LICENSE_CONCLUDED
+ COPYRIGHT
+ DOWNLOAD_LOCATION
+ SPDXID
+ COMMENT
+
+ # Additions compared to spdx function signature.
+ CYDX_SUPPLIER
+ SBOM_ENTITY_TYPE
+ CONTAINING_COMPONENT
+ EXTERNAL_BOM_LINK
+ )
+ set(multi_args
+ CPE
+
+ # Additions compared to spdx function signature.
+ PURL_VALUES
+ DEPENDENCIES
+ CYDX_PROPERTIES
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ _qt_internal_sbom_set_default_option_value_and_error_if_empty(PACKAGE "")
+
+ set(check_option "")
+ if(arg_SPDXID)
+ set(check_option "CHECK" "${arg_SPDXID}")
+ endif()
+
+ _qt_internal_sbom_get_and_check_spdx_id(
+ VARIABLE arg_SPDXID
+ ${check_option}
+ HINTS "SPDXRef-${arg_PACKAGE}"
+ )
+
+ _qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "")
+ _qt_internal_sbom_set_default_option_value(VERSION "")
+ _qt_internal_sbom_set_default_option_value(SUPPLIER "")
+ _qt_internal_sbom_set_default_option_value(LICENSE_CONCLUDED "")
+ _qt_internal_sbom_set_default_option_value(COPYRIGHT "")
+
+ set(cpe_field "")
+ set(cpe_list ${arg_CPE})
+ if(cpe_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM cpe_list PREPEND "\\\"")
+ list(TRANSFORM cpe_list APPEND "\\\"")
+ list(JOIN cpe_list ", " cpe_string)
+ set(cpe_field "cpe_list = [${cpe_string}]")
+ endif()
+
+ set(purl_field "")
+ set(purl_list ${arg_PURL_VALUES})
+ if(purl_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM purl_list PREPEND "\\\"")
+ list(TRANSFORM purl_list APPEND "\\\"")
+ list(JOIN purl_list ", " purl_string)
+ set(purl_field "purl_list = [${purl_string}]")
+ endif()
+
+ set(name_field "name = \\\"${arg_PACKAGE}\\\"")
+ set(spdx_id_field "spdx_id = \\\"${arg_SPDXID}\\\"")
+ set(sbom_entity_type_field "sbom_entity_type = \\\"${arg_SBOM_ENTITY_TYPE}\\\"")
+
+ _qt_internal_sbom_get_cyclone_component_type(component_type "${arg_SBOM_ENTITY_TYPE}")
+ set(component_type_field "component_type = \\\"${component_type}\\\"")
+
+ set(version_field "")
+ if(arg_VERSION)
+ set(version_field "version = \\\"${arg_VERSION}\\\"")
+ endif()
+
+ set(dependency_field "")
+ set(dependency_list ${arg_DEPENDENCIES})
+ if(dependency_list)
+ # Wrap values in double quotes.
+ list(TRANSFORM dependency_list PREPEND "\\\"")
+ list(TRANSFORM dependency_list APPEND "\\\"")
+ list(JOIN dependency_list ", " dependency_string)
+ set(dependency_field "dependencies = [${dependency_string}]")
+ endif()
+
+ set(copyright_field "")
+ if(arg_COPYRIGHT)
+ set(copyright_field "copyright = '''${arg_COPYRIGHT}'''")
+ endif()
+
+ set(download_location_field "")
+ if(arg_DOWNLOAD_LOCATION)
+ set(download_location_field "download_location = \\\"${arg_DOWNLOAD_LOCATION}\\\"")
+ endif()
+
+ set(license_concluded_field "")
+ if(arg_LICENSE_CONCLUDED)
+ set(license_concluded_field
+ "license_concluded_expression = \\\"${arg_LICENSE_CONCLUDED}\\\"")
+ endif()
+
+ set(supplier_field "")
+ if(arg_CYDX_SUPPLIER)
+ set(supplier_field "supplier = '${arg_CYDX_SUPPLIER}'")
+ endif()
+
+ set(containing_component_field "")
+ if(arg_CONTAINING_COMPONENT)
+ set(containing_component_field "containing_component = \\\"${arg_CONTAINING_COMPONENT}\\\"")
+ endif()
+
+ set(external_bom_link_field "")
+ if(arg_EXTERNAL_BOM_LINK)
+ set(external_bom_link_field "external_bom_link = \\\"${arg_EXTERNAL_BOM_LINK}\\\"")
+ endif()
+
+ set(properties_field "")
+ if(arg_CYDX_PROPERTIES)
+ _qt_internal_sbom_handle_cydx_properties(
+ CYDX_PROPERTIES ${arg_CYDX_PROPERTIES}
+ OUT_VAR_CYDX_PROPERTIES_STRING properties_field
+ )
+ endif()
+
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_spdx_file)
+
+ set(content "
+file(APPEND \"${staging_area_spdx_file}\"
+\"
+
+[[components]]
+${name_field}
+${spdx_id_field}
+${sbom_entity_type_field}
+${component_type_field}
+${version_field}
+${download_location_field}
+${copyright_field}
+${supplier_field}
+${containing_component_field}
+${external_bom_link_field}
+${cpe_field}
+${purl_field}
+${license_concluded_field}
+${dependency_field}
+${properties_field}
+\"
+)
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(package_sbom "${sbom_dir}/cydx_${arg_SPDXID}.cmake")
+ file(GENERATE OUTPUT "${package_sbom}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files_cydx "${package_sbom}")
+endfunction()
+
+# Adds all recorded custom licenses to the toml file.
+function(_qt_internal_sbom_add_recorded_licenses_cydx)
+ get_property(license_ids GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses)
+ if(NOT license_ids)
+ return()
+ endif()
+
+ set(content "")
+
+ foreach(license_id IN LISTS license_ids)
+ get_property(license_text GLOBAL PROPERTY
+ _qt_internal_sbom_cydx_licenses_${license_id}_text)
+
+ string(APPEND content "
+[[licenses]]
+license_id = \\\"${license_id}\\\"
+text = '''${license_text}
+'''
+")
+ endforeach()
+
+ _qt_internal_get_staging_area_cydx_file_path(staging_area_spdx_file)
+
+ set(content "
+file(APPEND \"${staging_area_spdx_file}\"
+\"
+${content}
+\"
+)
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(snippet "${sbom_dir}/cydx_license_info.cmake")
+ file(GENERATE OUTPUT "${snippet}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_end_include_files_cydx "${snippet}")
+
+ # Clean up before configuring next repo project.
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses "")
+ foreach(license_id IN LISTS license_ids)
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_cydx_licenses_${license_id}_text "")
+ endforeach()
+endfunction()
diff --git a/cmake/QtPublicSbomGenerationHelpers.cmake b/cmake/QtPublicSbomGenerationHelpers.cmake
index 89179e77a1b..1ade46c950e 100644
--- a/cmake/QtPublicSbomGenerationHelpers.cmake
+++ b/cmake/QtPublicSbomGenerationHelpers.cmake
@@ -74,37 +74,36 @@ function(_qt_internal_sbom_begin_project_generate)
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
- if(QT_SBOM_FAKE_TIMESTAMP)
- set(current_utc "2590-01-01T11:33:55Z")
- set(current_year "2590")
- else()
- string(TIMESTAMP current_utc UTC)
- string(TIMESTAMP current_year "%Y" UTC)
- endif()
-
- _qt_internal_sbom_set_default_option_value(PROJECT "${PROJECT_NAME}")
-
- _qt_internal_sbom_get_git_version_vars()
-
- _qt_internal_path_join(default_sbom_file_name
- "${arg_PROJECT}" "${arg_PROJECT}-sbom-${QT_SBOM_GIT_VERSION_PATH}.spdx")
+ _qt_internal_forward_function_args(
+ FORWARD_PREFIX arg
+ FORWARD_OUT_VAR common_project_args
+ FORWARD_SINGLE
+ ${single_args}
+ )
- _qt_internal_path_join(default_install_sbom_path
- "\${CMAKE_INSTALL_PREFIX}/" "${CMAKE_INSTALL_DATAROOTDIR}" "${default_sbom_file_name}"
+ _qt_internal_sbom_get_common_project_variables(
+ ${common_project_args}
+ OUT_VAR_PROJECT_NAME arg_PROJECT
+ OUT_VAR_CURRENT_UTC current_utc
+ OUT_VAR_CURRENT_YEAR current_year
+ DEFAULT_SBOM_FILE_NAME_EXTENSION "spdx"
+ OUT_VAR_OUTPUT arg_OUTPUT
+ OUT_VAR_OUTPUT_RELATIVE_PATH arg_OUTPUT_RELATIVE_PATH
+ OUT_VAR_PROJECT_FOR_SPDX_ID project_spdx_id
+ OUT_VAR_COPYRIGHT arg_COPYRIGHT
+ OUT_VAR_SUPPLIER arg_SUPPLIER
+ OUT_VAR_SUPPLIER_URL arg_SUPPLIER_URL
+ OUT_VAR_DEFAULT_PROJECT_COMMENT project_comment
)
+ if(arg_OUT_VAR_PROJECT_SPDX_ID)
+ set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
+ endif()
- _qt_internal_sbom_set_default_option_value(OUTPUT "${default_install_sbom_path}")
- _qt_internal_sbom_set_default_option_value(OUTPUT_RELATIVE_PATH
- "${default_sbom_file_name}")
+ _qt_internal_sbom_get_git_version_vars()
- _qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
- _qt_internal_sbom_set_default_option_value(PROJECT_FOR_SPDX_ID "Package-${arg_PROJECT}")
- _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER "")
- _qt_internal_sbom_set_default_option_value(COPYRIGHT "${current_year} ${arg_SUPPLIER}")
- _qt_internal_sbom_set_default_option_value_and_error_if_empty(SUPPLIER_URL
- "${PROJECT_HOMEPAGE_URL}")
_qt_internal_sbom_set_default_option_value(NAMESPACE
"${arg_SUPPLIER}/spdxdocs/${arg_PROJECT}-${QT_SBOM_GIT_VERSION}")
+ _qt_internal_sbom_set_default_option_value(LICENSE "NOASSERTION")
_qt_internal_sbom_set_default_option_value(DOCUMENT_CREATOR_TOOL "Qt Build System")
if(arg_DOCUMENT_CREATOR_TOOL)
@@ -132,34 +131,10 @@ ExternalRef: PACKAGE-MANAGER purl ${purl_generic_id}")
PackageVersion: ${QT_SBOM_GIT_VERSION}")
endif()
- string(REGEX REPLACE "[^A-Za-z0-9.]+" "-" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
- string(REGEX REPLACE "-+$" "" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
- # Prevent collision with other generated SPDXID with -[0-9]+ suffix.
- string(REGEX REPLACE "-([0-9]+)$" "\\1" arg_PROJECT_FOR_SPDX_ID "${arg_PROJECT_FOR_SPDX_ID}")
-
- set(project_spdx_id "SPDXRef-${arg_PROJECT_FOR_SPDX_ID}")
- if(arg_OUT_VAR_PROJECT_SPDX_ID)
- set(${arg_OUT_VAR_PROJECT_SPDX_ID} "${project_spdx_id}" PARENT_SCOPE)
- endif()
-
get_filename_component(doc_name "${arg_OUTPUT}" NAME_WLE)
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(cmake_configs "${CMAKE_CONFIGURATION_TYPES}")
- else()
- set(cmake_configs "${CMAKE_BUILD_TYPE}")
- endif()
-
_qt_internal_sbom_set_default_option_value(DOWNLOAD_LOCATION "NOASSERTION")
- set(cmake_version "Built by CMake ${CMAKE_VERSION}")
- set(system_name_and_processor "${CMAKE_SYSTEM_NAME} (${CMAKE_SYSTEM_PROCESSOR})")
- set(default_project_comment
- "${cmake_version} with ${cmake_configs} configuration for ${system_name_and_processor}")
-
- set(project_comment "${default_project_comment}")
-
if(arg_PROJECT_COMMENT)
string(APPEND project_comment "${arg_PROJECT_COMMENT}")
endif()
@@ -206,82 +181,30 @@ BuiltDate: ${current_utc}
Relationship: SPDXRef-DOCUMENT DESCRIBES ${project_spdx_id}
")
- # Create the directory that will contain all sbom related files.
- _qt_internal_get_current_project_sbom_dir(sbom_dir)
- file(MAKE_DIRECTORY "${sbom_dir}")
- set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
-
- # Generate project document intro spdx file.
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- set(document_intro_file_name
- "${sbom_dir}/SPDXRef-DOCUMENT-${repo_project_name_lowercase}.spdx.in")
- file(GENERATE OUTPUT "${document_intro_file_name}" CONTENT "${content}")
-
- # This is the file that will be incrementally assembled by having content appended to it.
- _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
-
- get_filename_component(output_file_name_without_ext "${arg_OUTPUT}" NAME_WLE)
- get_filename_component(output_file_ext "${arg_OUTPUT}" LAST_EXT)
-
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(multi_config_suffix "-$<CONFIG>")
- else()
- set(multi_config_suffix "")
- endif()
-
- set(computed_sbom_file_name_without_ext "${output_file_name_without_ext}${multi_config_suffix}")
- set(computed_sbom_file_name "${output_file_name_without_ext}${output_file_ext}")
-
- # In a super build and in a no-prefix build, put all the build time sboms into the same dir in,
- # in the qtbase build dir.
- if(QT_BUILDING_QT AND (QT_SUPERBUILD OR (NOT QT_WILL_INSTALL)))
- set(build_sbom_root_dir "${QT_BUILD_DIR}")
- else()
- set(build_sbom_root_dir "${sbom_dir}")
- endif()
-
- get_filename_component(output_relative_dir "${arg_OUTPUT_RELATIVE_PATH}" DIRECTORY)
-
- set(build_sbom_dir "${build_sbom_root_dir}/${output_relative_dir}")
- set(build_sbom_path "${build_sbom_dir}/${computed_sbom_file_name}")
- set(build_sbom_path_without_ext
- "${build_sbom_dir}/${computed_sbom_file_name_without_ext}")
-
- set(install_sbom_path "${arg_OUTPUT}")
-
- get_filename_component(install_sbom_dir "${install_sbom_path}" DIRECTORY)
- set(install_sbom_path_without_ext "${install_sbom_dir}/${output_file_name_without_ext}")
-
- # Create cmake file to append the document intro spdx to the staging file.
- set(create_staging_file "${sbom_dir}/append_document_to_staging${multi_config_suffix}.cmake")
- set(content "
- cmake_minimum_required(VERSION 3.16)
- message(STATUS \"Starting SBOM generation in build dir: ${staging_area_spdx_file}\")
- set(QT_SBOM_EXTERNAL_DOC_REFS \"\")
- file(READ \"${document_intro_file_name}\" content)
- # Override any previous file because we're starting from scratch.
- file(WRITE \"${staging_area_spdx_file}\" \"\${content}\")
-")
- file(GENERATE OUTPUT "${create_staging_file}" CONTENT "${content}")
-
-
- set_property(GLOBAL PROPERTY _qt_sbom_project_supplier "${arg_SUPPLIER}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_supplier_url "${arg_SUPPLIER_URL}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_namespace "${arg_NAMESPACE}")
+ _qt_internal_sbom_create_sbom_staging_file(
+ CONTENT "${content}"
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_CREATE_STAGING_FILE create_staging_file
+ OUT_VAR_SBOM_DIR sbom_dir
+ )
- set_property(GLOBAL PROPERTY _qt_sbom_project_name "${arg_PROJECT}")
- set_property(GLOBAL PROPERTY _qt_sbom_project_spdx_id "${project_spdx_id}")
+ set_property(GLOBAL APPEND PROPERTY _qt_internal_sbom_dirs "${sbom_dir}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_path "${build_sbom_path}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_path_without_ext
- "${build_sbom_path_without_ext}")
- set_property(GLOBAL PROPERTY _qt_sbom_build_output_dir "${build_sbom_dir}")
+ _qt_internal_sbom_save_project_info_in_global_properties(
+ SUPPLIER "${arg_SUPPLIER}"
+ SUPPLIER_URL "${arg_SUPPLIER_URL}"
+ NAMESPACE "${arg_NAMESPACE}"
+ PROJECT "${arg_PROJECT}"
+ PROJECT_SPDX_ID "${project_spdx_id}"
+ )
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_path "${install_sbom_path}")
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_path_without_ext
- "${install_sbom_path_without_ext}")
- set_property(GLOBAL PROPERTY _qt_sbom_install_output_dir "${install_sbom_dir}")
+ _qt_internal_sbom_save_common_path_variables_in_global_properties(
+ OUTPUT "${arg_OUTPUT}"
+ OUTPUT_RELATIVE_PATH "${arg_OUTPUT_RELATIVE_PATH}"
+ SBOM_DIR "${sbom_dir}"
+ )
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_include_files "${create_staging_file}")
@@ -293,116 +216,51 @@ endfunction()
# Creates an 'sbom' custom target to generate an incomplete sbom at build time (no checksums).
# Creates install rules to install a complete (with checksums) sbom.
function(_qt_internal_sbom_end_project_generate)
- get_property(sbom_build_output_path GLOBAL PROPERTY _qt_sbom_build_output_path)
- get_property(sbom_build_output_path_without_ext GLOBAL PROPERTY
- _qt_sbom_build_output_path_without_ext)
- get_property(sbom_build_output_dir GLOBAL PROPERTY _qt_sbom_build_output_dir)
-
- get_property(sbom_install_output_path GLOBAL PROPERTY _qt_sbom_install_output_path)
- get_property(sbom_install_output_path_without_ext GLOBAL PROPERTY
- _qt_sbom_install_output_path_without_ext)
- get_property(sbom_install_output_dir GLOBAL PROPERTY _qt_sbom_install_output_dir)
+ _qt_internal_sbom_get_common_path_variables_from_global_properties(
+ SBOM_FORMAT "SPDX_V2"
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH sbom_build_output_path
+ OUT_VAR_SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT sbom_build_output_path_without_ext
+ OUT_VAR_SBOM_BUILD_OUTPUT_DIR sbom_build_output_dir
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH sbom_install_output_path
+ OUT_VAR_SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT sbom_install_output_path_without_ext
+ OUT_VAR_SBOM_INSTALL_OUTPUT_DIR sbom_install_output_dir
+ )
if(NOT sbom_build_output_path)
message(FATAL_ERROR "Call _qt_internal_sbom_begin_project() first")
endif()
- _qt_internal_get_staging_area_spdx_file_path(staging_area_spdx_file)
-
- _qt_internal_sbom_collect_cmake_include_files(includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_include_files _qt_sbom_cmake_end_include_files
- )
-
- # Before checksum includes are included after the verification codes have been collected
- # and before their merged checksum(s) has been computed.
- _qt_internal_sbom_collect_cmake_include_files(before_checksum_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_before_checksum_include_files
- )
-
- # After checksum includes are included after the checksum has been computed and written to the
- # QT_SBOM_VERIFICATION_CODE variable.
- _qt_internal_sbom_collect_cmake_include_files(after_checksum_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_after_checksum_include_files
- )
-
- # Post generation includes are included for both build and install time sboms, after
- # sbom generation has finished.
- _qt_internal_sbom_collect_cmake_include_files(post_generation_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_post_generation_include_files
- )
+ _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
+ _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
- # Verification only makes sense on installation, where the checksums are present.
- _qt_internal_sbom_collect_cmake_include_files(verify_includes
- JOIN_WITH_NEWLINES
- PROPERTIES _qt_sbom_cmake_verify_include_files
+ _qt_internal_sbom_get_cmake_include_files(
+ SBOM_FORMAT "SPDX_V2"
+ OUT_VAR_INCLUDES includes
+ OUT_VAR_BEFORE_CHECKSUM_INCLUDES before_checksum_includes
+ OUT_VAR_AFTER_CHECKSUM_INCLUDES after_checksum_includes
+ OUT_VAR_POST_GENERATION_INCLUDES post_generation_includes
+ OUT_VAR_VERIFY_INCLUDES verify_includes
)
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(multi_config_suffix "-$<CONFIG>")
- else()
- set(multi_config_suffix "")
- endif()
-
_qt_internal_get_current_project_sbom_dir(sbom_dir)
- set(content "
- # QT_SBOM_BUILD_TIME be set to FALSE at install time, so don't override if it's set.
- # This allows reusing the same cmake file for both build and install.
- if(NOT DEFINED QT_SBOM_BUILD_TIME)
- set(QT_SBOM_BUILD_TIME TRUE)
- endif()
- if(NOT QT_SBOM_OUTPUT_PATH)
- set(QT_SBOM_OUTPUT_DIR \"${sbom_build_output_dir}\")
- set(QT_SBOM_OUTPUT_PATH \"${sbom_build_output_path}\")
- set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_build_output_path_without_ext}\")
- file(MAKE_DIRECTORY \"${sbom_build_output_dir}\")
- endif()
- set(QT_SBOM_PACKAGES \"\")
- set(QT_SBOM_PACKAGES_WITH_VERIFICATION_CODES \"\")
- ${includes}
- if(QT_SBOM_BUILD_TIME)
- message(STATUS \"Finalizing SBOM generation in build dir: \${QT_SBOM_OUTPUT_PATH}\")
- configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
- ${post_generation_includes}
- endif()
-")
- set(assemble_sbom "${sbom_dir}/assemble_sbom${multi_config_suffix}.cmake")
- file(GENERATE OUTPUT "${assemble_sbom}" CONTENT "${content}")
- if(NOT TARGET sbom)
- add_custom_target(sbom)
+ set(build_time_args "")
+ if(includes)
+ list(APPEND build_time_args INCLUDES "${includes}")
endif()
-
- _qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- _qt_internal_sbom_get_qt_repo_project_name_lower_case(real_qt_repo_project_name_lowercase)
-
- # Create a build target to create a build-time sbom (no verification codes or sha1s).
- set(repo_sbom_target "sbom_${repo_project_name_lowercase}")
- set(comment "")
- string(APPEND comment "Assembling build time SPDX document without checksums for "
- "${repo_project_name_lowercase}. Just for testing.")
- add_custom_target(${repo_sbom_target}
- COMMAND "${CMAKE_COMMAND}" -P "${assemble_sbom}"
- COMMENT "${comment}"
- VERBATIM
- USES_TERMINAL # To avoid running two configs of the command in parallel
- )
-
- get_cmake_property(qt_repo_deps _qt_repo_deps_${real_qt_repo_project_name_lowercase})
- if(qt_repo_deps)
- foreach(repo_dep IN LISTS qt_repo_deps)
- set(repo_dep_sbom "sbom_${repo_dep}")
- if(TARGET "${repo_dep_sbom}")
- add_dependencies(${repo_sbom_target} ${repo_dep_sbom})
- endif()
- endforeach()
+ if(post_generation_includes)
+ list(APPEND build_time_args POST_GENERATION_INCLUDES "${post_generation_includes}")
endif()
-
- add_dependencies(sbom ${repo_sbom_target})
+ _qt_internal_sbom_create_build_time_sbom_targets(
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ REAL_QT_REPO_PROJECT_NAME_LOWERCASE "${real_qt_repo_project_name_lowercase}"
+ SBOM_BUILD_OUTPUT_PATH "${sbom_build_output_path}"
+ SBOM_BUILD_OUTPUT_PATH_WITHOUT_EXT "${sbom_build_output_path_without_ext}"
+ SBOM_BUILD_OUTPUT_DIR "${sbom_build_output_dir}"
+ OUT_VAR_ASSEMBLE_SBOM_INCLUDE_PATH assemble_sbom
+ ${build_time_args}
+ )
# Add 'reuse lint' per-repo custom targets.
if(arg_LINT_SOURCE_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
@@ -420,59 +278,19 @@ function(_qt_internal_sbom_end_project_generate)
add_dependencies(reuse_lint ${repo_sbom_target}_reuse_lint)
endif()
- set(extra_code_begin "")
- set(extra_code_inner_end "")
-
- get_cmake_property(is_multi_config GENERATOR_IS_MULTI_CONFIG)
- if(is_multi_config)
- set(configs ${CMAKE_CONFIGURATION_TYPES})
-
- set(install_markers_dir "${sbom_dir}")
- set(install_marker_path "${install_markers_dir}/finished_install-$<CONFIG>.cmake")
-
- set(install_marker_code "
- message(STATUS \"Writing install marker for config $<CONFIG>: ${install_marker_path} \")
- file(WRITE \"${install_marker_path}\" \"\")
-")
-
- install(CODE "${install_marker_code}" COMPONENT sbom)
- if(QT_SUPERBUILD)
- install(CODE "${install_marker_code}" COMPONENT "sbom_${repo_project_name_lowercase}"
- EXCLUDE_FROM_ALL)
- endif()
-
- set(install_markers "")
- foreach(config IN LISTS configs)
- set(marker_path "${install_markers_dir}/finished_install-${config}.cmake")
- list(APPEND install_markers "${marker_path}")
- # Remove the markers on reconfiguration, just in case there are stale ones.
- if(EXISTS "${marker_path}")
- file(REMOVE "${marker_path}")
- endif()
- endforeach()
-
- set(extra_code_begin "
- set(QT_SBOM_INSTALL_MARKERS \"${install_markers}\")
- foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
- if(NOT EXISTS \"\${QT_SBOM_INSTALL_MARKER}\")
- set(QT_SBOM_INSTALLED_ALL_CONFIGS FALSE)
- endif()
- endforeach()
-")
- set(extra_code_inner_end "
- foreach(QT_SBOM_INSTALL_MARKER IN LISTS QT_SBOM_INSTALL_MARKERS)
- message(STATUS
- \"Removing install marker: \${QT_SBOM_INSTALL_MARKER} \")
- file(REMOVE \"\${QT_SBOM_INSTALL_MARKER}\")
- endforeach()
-")
- endif()
+ _qt_internal_sbom_setup_multi_config_install_markers(
+ SBOM_DIR "${sbom_dir}"
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ OUT_VAR_EXTRA_CODE_BEGIN extra_code_begin
+ OUT_VAR_EXTRA_CODE_INNER_END extra_code_inner_end
+ )
- # Allow skipping checksum computation for testing purposes, while installing just the sbom
- # documents, without requiring to build and install all the actual files.
- if(QT_SBOM_FAKE_CHECKSUM)
- string(APPEND extra_code_begin "
- set(QT_SBOM_FAKE_CHECKSUM TRUE)")
+ _qt_internal_sbom_setup_fake_checksum(
+ OUT_VAR_FAKE_CHECKSUM_CODE extra_code_begin_fake_checksum
+ )
+ if(extra_code_begin_fake_checksum)
+ string(APPEND extra_code_begin "${extra_code_begin_fake_checksum}")
endif()
set(verification_codes_content "
@@ -496,42 +314,40 @@ unset(_verification_code)
set(process_verification_codes "${sbom_dir}/process_verification_codes.cmake")
file(GENERATE OUTPUT "${process_verification_codes}" CONTENT "${verification_codes_content}")
- set(assemble_sbom_install "
- set(QT_SBOM_INSTALLED_ALL_CONFIGS TRUE)
- ${extra_code_begin}
- if(QT_SBOM_INSTALLED_ALL_CONFIGS)
- set(QT_SBOM_BUILD_TIME FALSE)
- set(QT_SBOM_OUTPUT_DIR \"${sbom_install_output_dir}\")
- set(QT_SBOM_OUTPUT_PATH \"${sbom_install_output_path}\")
- set(QT_SBOM_OUTPUT_PATH_WITHOUT_EXT \"${sbom_install_output_path_without_ext}\")
- file(MAKE_DIRECTORY \"${sbom_install_output_dir}\")
- include(\"${assemble_sbom}\")
- ${before_checksum_includes}
- include(\"${process_verification_codes}\")
- ${after_checksum_includes}
- message(STATUS \"Finalizing SBOM generation in install dir: \${QT_SBOM_OUTPUT_PATH}\")
- configure_file(\"${staging_area_spdx_file}\" \"\${QT_SBOM_OUTPUT_PATH}\")
- ${post_generation_includes}
- ${verify_includes}
- ${extra_code_inner_end}
- else()
- message(STATUS \"Skipping SBOM finalization because not all configs were installed.\")
- endif()
-")
-
- install(CODE "${assemble_sbom_install}" COMPONENT sbom)
- if(QT_SUPERBUILD)
- install(CODE "${assemble_sbom_install}" COMPONENT "sbom_${repo_project_name_lowercase}"
- EXCLUDE_FROM_ALL)
+ set(setup_sbom_install_args "")
+ if(extra_code_begin)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_BEGIN "${extra_code_begin}")
+ endif()
+ if(extra_code_inner_end)
+ list(APPEND setup_sbom_install_args EXTRA_CODE_INNER_END "${extra_code_inner_end}")
+ endif()
+ if(before_checksum_includes)
+ list(APPEND setup_sbom_install_args BEFORE_CHECKSUM_INCLUDES "${before_checksum_includes}")
+ endif()
+ if(after_checksum_includes)
+ list(APPEND setup_sbom_install_args AFTER_CHECKSUM_INCLUDES "${after_checksum_includes}")
+ endif()
+ if(post_generation_includes)
+ list(APPEND setup_sbom_install_args POST_GENERATION_INCLUDES "${post_generation_includes}")
+ endif()
+ if(verify_includes)
+ list(APPEND setup_sbom_install_args VERIFY_INCLUDES "${verify_includes}")
endif()
- # Clean up properties, so that they are empty for possible next repo in a top-level build.
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_end_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_before_checksum_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_after_checksum_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_post_generation_include_files "")
- set_property(GLOBAL PROPERTY _qt_sbom_cmake_verify_include_files "")
+ _qt_internal_sbom_setup_sbom_install_code(
+ SBOM_FORMAT "SPDX_V2"
+ REPO_PROJECT_NAME_LOWERCASE "${repo_project_name_lowercase}"
+ SBOM_INSTALL_OUTPUT_PATH "${sbom_install_output_path}"
+ SBOM_INSTALL_OUTPUT_PATH_WITHOUT_EXT "${sbom_install_output_path_without_ext}"
+ SBOM_INSTALL_OUTPUT_DIR "${sbom_install_output_dir}"
+ ASSEMBLE_SBOM_INCLUDE_PATH "${assemble_sbom}"
+ PROCESS_VERIFICATION_CODES "${process_verification_codes}"
+ ${setup_sbom_install_args}
+ )
+
+ _qt_internal_sbom_clear_cmake_include_files(
+ SBOM_FORMAT "SPDX_V2"
+ )
endfunction()
# Gets a list of cmake include file paths, joins them as include() statements and returns the
diff --git a/cmake/QtPublicSbomHelpers.cmake b/cmake/QtPublicSbomHelpers.cmake
index 1a8702bf594..3d66e7ff783 100644
--- a/cmake/QtPublicSbomHelpers.cmake
+++ b/cmake/QtPublicSbomHelpers.cmake
@@ -85,7 +85,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_root_project_name_for_spdx_id(repo_project_name_for_spdx_id)
_qt_internal_sbom_get_root_project_name_lower_case(repo_project_name_lowercase)
- set(begin_project_generate_args "")
+ set(begin_project_generate_args_spdx "")
+ set(begin_project_generate_args_cydx "")
if(arg_SUPPLIER_URL)
set(repo_supplier_url "${arg_SUPPLIER_URL}")
@@ -93,7 +94,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_default_supplier_url(repo_supplier_url)
endif()
if(repo_supplier_url)
- list(APPEND begin_project_generate_args SUPPLIER_URL "${repo_supplier_url}")
+ list(APPEND begin_project_generate_args_spdx SUPPLIER_URL "${repo_supplier_url}")
+ list(APPEND begin_project_generate_args_cydx SUPPLIER_URL "${repo_supplier_url}")
endif()
set(sbom_project_version_args "")
@@ -121,6 +123,15 @@ function(_qt_internal_sbom_begin_project)
)
endif()
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_get_cyclone_bom_serial_number(
+ SPDX_NAMESPACE "${repo_spdx_namespace}"
+ OUT_VAR_UUID cyclone_dx_bom_serial_number_uuid
+ )
+ list(APPEND begin_project_generate_args_cydx
+ BOM_SERIAL_NUMBER_UUID "${cyclone_dx_bom_serial_number_uuid}")
+ endif()
+
if(arg_INSTALL_SBOM_DIR)
set(install_sbom_dir "${arg_INSTALL_SBOM_DIR}")
elseif(INSTALL_SBOMDIR)
@@ -143,17 +154,28 @@ function(_qt_internal_sbom_begin_project)
list(APPEND compute_project_file_name_args VERSION_SUFFIX "${explicit_version}")
endif()
- _qt_internal_sbom_compute_project_file_name(repo_project_file_name
+ _qt_internal_sbom_compute_project_file_name(repo_project_file_name_spdx
+ SPDX_TAG_VALUE
+ PROJECT_NAME "${repo_project_name_lowercase}"
+ ${compute_project_file_name_args}
+ )
+
+ _qt_internal_sbom_compute_project_file_name(repo_project_file_name_cydx
+ CYCLONEDX_TOML
PROJECT_NAME "${repo_project_name_lowercase}"
${compute_project_file_name_args}
)
- _qt_internal_path_join(repo_spdx_relative_install_path
- "${arg_INSTALL_SBOM_DIR}" "${repo_project_file_name}")
+ _qt_internal_path_join(repo_spdx_relative_install_path_spdx
+ "${install_sbom_dir}" "${repo_project_file_name_spdx}")
+ _qt_internal_path_join(repo_spdx_relative_install_path_cydx
+ "${install_sbom_dir}" "${repo_project_file_name_cydx}")
# Prepend DESTDIR, to allow relocating installed sbom. Needed for CI.
- _qt_internal_path_join(repo_spdx_install_path
- "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path}")
+ _qt_internal_path_join(repo_spdx_install_path_spdx
+ "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path_spdx}")
+ _qt_internal_path_join(repo_spdx_install_path_cydx
+ "\$ENV{DESTDIR}${install_prefix}" "${repo_spdx_relative_install_path_cydx}")
if(arg_LICENSE_EXPRESSION)
set(repo_license "${arg_LICENSE_EXPRESSION}")
@@ -164,7 +186,8 @@ function(_qt_internal_sbom_begin_project)
set(repo_license "")
endif()
if(repo_license)
- list(APPEND begin_project_generate_args LICENSE "${repo_license}")
+ list(APPEND begin_project_generate_args_spdx LICENSE "${repo_license}")
+ list(APPEND begin_project_generate_args_cydx LICENSE "${repo_license}")
endif()
if(arg_COPYRIGHTS)
@@ -174,7 +197,8 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_default_qt_copyright_header(repo_copyright)
endif()
if(repo_copyright)
- list(APPEND begin_project_generate_args COPYRIGHT "${repo_copyright}")
+ list(APPEND begin_project_generate_args_spdx COPYRIGHT "${repo_copyright}")
+ list(APPEND begin_project_generate_args_cydx COPYRIGHT "${repo_copyright}")
endif()
if(arg_SUPPLIER)
@@ -184,7 +208,8 @@ function(_qt_internal_sbom_begin_project)
endif()
if(repo_supplier)
# This must not contain spaces!
- list(APPEND begin_project_generate_args SUPPLIER "${repo_supplier}")
+ list(APPEND begin_project_generate_args_spdx SUPPLIER "${repo_supplier}")
+ list(APPEND begin_project_generate_args_cydx SUPPLIER "${repo_supplier}")
endif()
if(arg_CPE)
@@ -195,7 +220,8 @@ function(_qt_internal_sbom_begin_project)
set(qt_cpe "")
endif()
if(qt_cpe)
- list(APPEND begin_project_generate_args CPE "${qt_cpe}")
+ list(APPEND begin_project_generate_args_spdx CPE "${qt_cpe}")
+ list(APPEND begin_project_generate_args_cydx CPE "${qt_cpe}")
endif()
if(arg_DOWNLOAD_LOCATION)
@@ -204,11 +230,12 @@ function(_qt_internal_sbom_begin_project)
_qt_internal_sbom_get_qt_repo_source_download_location(download_location)
endif()
if(download_location)
- list(APPEND begin_project_generate_args DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND begin_project_generate_args_spdx DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND begin_project_generate_args_cydx DOWNLOAD_LOCATION "${download_location}")
endif()
if(arg_DOCUMENT_CREATOR_TOOL)
- list(APPEND begin_project_generate_args
+ list(APPEND begin_project_generate_args_spdx
DOCUMENT_CREATOR_TOOL "${arg_DOCUMENT_CREATOR_TOOL}")
endif()
@@ -229,24 +256,42 @@ function(_qt_internal_sbom_begin_project)
set(project_comment PROJECT_COMMENT "${project_comment}")
endif()
- _qt_internal_sbom_begin_project_generate(
- OUTPUT "${repo_spdx_install_path}"
- OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path}"
- PROJECT "${repo_project_name_lowercase}"
- ${project_comment}
- PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
- NAMESPACE "${repo_spdx_namespace}"
- ${begin_project_generate_args}
- OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_begin_project_generate(
+ OUTPUT "${repo_spdx_install_path_spdx}"
+ OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path_spdx}"
+ PROJECT "${repo_project_name_lowercase}"
+ ${project_comment}
+ PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
+ NAMESPACE "${repo_spdx_namespace}"
+ ${begin_project_generate_args_spdx}
+ OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_begin_project_generate_cyclone(
+ OUTPUT "${repo_spdx_install_path_cydx}"
+ OUTPUT_RELATIVE_PATH "${repo_spdx_relative_install_path_cydx}"
+ PROJECT "${repo_project_name_lowercase}"
+ ${project_comment}
+ PROJECT_FOR_SPDX_ID "${repo_project_name_for_spdx_id}"
+ NAMESPACE "${repo_spdx_namespace}"
+ ${begin_project_generate_args_cydx}
+ OUT_VAR_PROJECT_SPDX_ID repo_project_spdx_id
+ )
+ endif()
set_property(GLOBAL PROPERTY _qt_internal_project_attribution_files "")
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace
"${repo_spdx_namespace}")
+ set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid
+ "${cyclone_dx_bom_serial_number_uuid}")
+
set_property(GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path
- "${repo_spdx_relative_install_path}")
+ "${repo_spdx_relative_install_path_spdx}")
set_property(GLOBAL PROPERTY _qt_internal_sbom_repo_project_name_lowercase
"${repo_project_name_lowercase}")
@@ -312,7 +357,10 @@ endfunction()
function(_qt_internal_sbom_setup_project_ops)
set(options "")
- if(QT_SBOM_GENERATE_JSON OR QT_INTERNAL_SBOM_GENERATE_JSON OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_GENERATE_JSON
+ OR QT_SBOM_GENERATE_SPDX_V2_JSON
+ OR QT_INTERNAL_SBOM_GENERATE_JSON
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options GENERATE_JSON)
endif()
@@ -320,20 +368,48 @@ function(_qt_internal_sbom_setup_project_ops)
# The user can explicitly request to fail the build if dependencies are not found.
# error out. For internal options that the CI uses, we always want to fail the build if the
# deps are not found.
- if(QT_SBOM_REQUIRE_GENERATE_JSON OR QT_INTERNAL_SBOM_GENERATE_JSON
+ if(QT_SBOM_REQUIRE_GENERATE_JSON
+ OR QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON
+ OR QT_INTERNAL_SBOM_GENERATE_JSON
OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options GENERATE_JSON_REQUIRED)
endif()
- if(QT_SBOM_VERIFY OR QT_INTERNAL_SBOM_VERIFY OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_VERIFY
+ OR QT_SBOM_VERIFY_SPDX_V2
+ OR QT_INTERNAL_SBOM_VERIFY
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_SBOM)
endif()
# Do the same requirement check for SBOM verification.
- if(QT_SBOM_REQUIRE_VERIFY OR QT_INTERNAL_SBOM_VERIFY OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
+ if(QT_SBOM_REQUIRE_VERIFY
+ OR QT_SBOM_REQUIRE_VERIFY_SPDX_V2
+ OR QT_INTERNAL_SBOM_VERIFY
+ OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_SBOM_REQUIRED)
endif()
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ list(APPEND options GENERATE_CYCLONE_DX_V1_6)
+ endif()
+
+ if(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+ list(APPEND options GENERATE_CYCLONE_DX_V1_6_REQUIRED)
+ endif()
+
+ if(QT_SBOM_VERIFY_CYDX_V1_6)
+ list(APPEND options VERIFY_CYCLONE_DX_V1_6)
+ endif()
+
+ if(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6)
+ list(APPEND options VERIFY_CYCLONE_DX_V1_6_REQUIRED)
+ endif()
+
+ if(QT_SBOM_VERBOSE_CYDX_V1_6)
+ list(APPEND options VERBOSE_CYCLONE_DX_V1_6)
+ endif()
+
if(QT_SBOM_VERIFY_NTIA_COMPLIANT
OR QT_INTERNAL_SBOM_VERIFY_NTIA_COMPLIANT OR QT_INTERNAL_SBOM_DEFAULT_CHECKS)
list(APPEND options VERIFY_NTIA_COMPLIANT)
@@ -367,12 +443,20 @@ function(_qt_internal_sbom_setup_project_ops)
endfunction()
# Sets up SBOM generation and verification options.
-# By default SBOM generation is disabled.
-# By default JSON generation and SBOM verification are enabled by default, if the dependencies
-# are present, otherwise they will be silently skipped. Unless the user explicitly requests to
-# fail the build if the dependencies are not found.
#
-# The QT_GENERATE_SBOM_DEFAULT option can be set by a project to change the default value.
+# By default, the main toggle for SBOM generation is disabled. The GENERATE_SBOM_DEFAULT option
+# overrides that value and can be set by a project that sets up SBOM generation.
+#
+# If the main toggle gets enabled, we enable SPDX V2.3 tag:value generation, and try to enable
+# CycloneDX V1.6 generation. Try, because CDX generation needs python dependencies. If they are
+# not found, the generation is silently skipped.
+#
+# By default SPDX v2.3 JSON generation and verification is enabled, if the python dependencies
+# are found. Otherwise they will be silently skipped.
+# Unless the user explicitly requests to fail the build if the dependencies are not found.
+# The same can be done for CycloneDX generation.
+#
+# Some older variables that were added pre-CycloneDX generation are deprecated.
function(_qt_internal_setup_sbom)
set(opt_args "")
set(single_args
@@ -388,22 +472,159 @@ function(_qt_internal_setup_sbom)
set(default_value "${arg_GENERATE_SBOM_DEFAULT}")
endif()
- option(QT_GENERATE_SBOM "Generate SBOM documents in SPDX v2.3 tag:value format."
- "${default_value}")
+ # Main SBOM toggle. Used to be the toggle for SPDX v2.3 only, but now would also enable Cyclone
+ # DX as well.
+ set(sbom_help_string "Generate SBOM.")
+ option(QT_GENERATE_SBOM "${sbom_help_string}" "${default_value}")
+
+
+ # Toggle for SPDX V2.3 generation.
+ set(spdx_v2_help_string "Generate SBOM documents in SPDX v2.3 tag:value format.")
+ option(QT_SBOM_GENERATE_SPDX_V2 "${spdx_v2_help_string}" ON)
+
+
+ # Toggles for CycloneDX V1.6 generation.
+ set(cydx_help_string "Generate SBOM documents in CycloneDX v1.6 JSON format.")
+ option(QT_SBOM_GENERATE_CYDX_V1_6 "${cydx_help_string}" ON)
- string(CONCAT help_string
+ set(cydx_require_help_string
+ "Error out if CycloneDX SBOM generation dependencies are not found.")
+ option(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 "${cydx_require_help_string}" OFF)
+
+
+ # Options for SPDX v2.3 JSON generation and verification.
+
+ string(CONCAT spdx_v23_json_help_string
"Generate SBOM documents in SPDX v2.3 JSON format if required python dependency "
- "spdx-tools is available"
+ "spdx-tools is available."
)
- option(QT_SBOM_GENERATE_JSON
- "${help_string}" ON)
- option(QT_SBOM_REQUIRE_GENERATE_JSON
- "Error out if JSON SBOM generation depdendency is not found." OFF)
+ set(spdx_v23_json_require_help_string
+ "Error out if JSON SBOM generation depdendency is not found.")
+
+ set(spdx_v23_verify_help_string
+ "Verify generated SBOM documents using python spdx-tools package.")
- option(QT_SBOM_VERIFY "Verify generated SBOM documents using python spdx-tools package." ON)
- option(QT_SBOM_REQUIRE_VERIFY
- "Error out if SBOM verification dependencies are not found." OFF)
+ set(spdx_v23_verify_require_help_string
+ "Error out if SBOM verification dependencies are not found.")
+
+ option(QT_SBOM_GENERATE_SPDX_V2_JSON "${spdx_v23_json_help_string}" ON)
+ option(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON "${spdx_v23_json_require_help_string}" OFF)
+
+ option(QT_SBOM_VERIFY_SPDX_V2 "${spdx_v23_verify_help_string}" ON)
+ option(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 "${spdx_v23_verify_require_help_string}" OFF)
+
+
+ # Options for CycloneDX verification and verbosity.
+
+ set(cydx_verify_help_string
+ "Verify generated CycloneDX document against its json schema.")
+ option(QT_SBOM_VERIFY_CYDX_V1_6 "${cydx_verify_help_string}" ON)
+
+ set(cydx_verify_require_help_string
+ "Error out if SBOM verification dependencies are not found.")
+ option(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 "${cydx_verify_require_help_string}" OFF)
+
+ set(cydx_verbose_help_string
+ "Enable verbose output for CycloneDX generation.")
+ option(QT_SBOM_VERBOSE_CYDX_V1_6 "${cydx_verbose_help_string}" OFF)
+
+
+ # Deprecated options, superseded by the options above.
+ # Only add them if the values was previously defined, but update the doc string.
+
+ if(DEFINED QT_SBOM_GENERATE_JSON)
+ option(QT_SBOM_GENERATE_JSON "Deprecated: ${spdx_v23_json_help_string}" ON)
+ endif()
+ if(DEFINED QT_SBOM_REQUIRE_GENERATE_JSON)
+ option(QT_SBOM_REQUIRE_GENERATE_JSON "Deprecated: ${spdx_v23_json_require_help_string}" OFF)
+ endif()
+ if(DEFINED QT_SBOM_VERIFY)
+ option(QT_SBOM_VERIFY "Deprecated: ${spdx_v23_verify_help_string}" ON)
+ endif()
+ if(DEFINED QT_SBOM_REQUIRE_VERIFY)
+ option(QT_SBOM_REQUIRE_VERIFY "Deprecated: ${spdx_v23_verify_require_help_string}" OFF)
+ endif()
+
+ # Semi-public, undocumented options to allow enabling all SBOM stuff, for easier testing.
+ if(QT_SBOM_GENERATE_AND_VERIFY_ALL)
+ set(QT_SBOM_GENERATE_ALL ON)
+ set(QT_SBOM_GENERATE_REQUIRED_ALL ON)
+ set(QT_SBOM_VERIFY_REQUIRED_ALL ON)
+ endif()
+
+ if(QT_SBOM_GENERATE_ALL)
+ set(QT_GENERATE_SBOM ON CACHE BOOL "${sbom_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_SPDX_V2 ON CACHE BOOL "${spdx_v2_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_SPDX_V2_JSON ON CACHE BOOL "${spdx_v23_json_help_string}" FORCE)
+ set(QT_SBOM_GENERATE_CYDX_V1_6 ON CACHE BOOL "${cydx_help_string}" FORCE)
+ unset(QT_SBOM_GENERATE_ALL CACHE)
+ unset(QT_SBOM_GENERATE_ALL)
+ endif()
+
+ if(QT_SBOM_GENERATE_REQUIRED_ALL)
+ set(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON ON CACHE BOOL
+ "${spdx_v23_json_require_help_string}" FORCE)
+ set(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 ON CACHE BOOL "${cydx_require_help_string}" FORCE)
+
+ unset(QT_SBOM_GENERATE_REQUIRED_ALL CACHE)
+ unset(QT_SBOM_GENERATE_REQUIRED_ALL)
+ endif()
+
+ if(QT_SBOM_VERIFY_REQUIRED_ALL)
+ set(QT_SBOM_VERIFY_SPDX_V2 ON CACHE BOOL "${spdx_v23_verify_help_string}" FORCE)
+ set(QT_SBOM_VERIFY_CYDX_V1_6 ON CACHE BOOL "${cydx_verify_help_string}" FORCE)
+
+ set(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 ON CACHE BOOL "${spdx_v23_verify_require_help_string}"
+ FORCE)
+ set(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 ON CACHE BOOL "${cydx_verify_require_help_string}"
+ FORCE)
+
+ unset(QT_SBOM_VERIFY_ALL CACHE)
+ unset(QT_SBOM_VERIFY_ALL)
+ endif()
+
+ # Various sanity checks.
+
+ # Disable SPDX v2.3 JSON generation if tag:value generation is disabled.
+ if(QT_GENERATE_SBOM
+ AND QT_SBOM_GENERATE_SPDX_V2_JSON
+ AND NOT QT_SBOM_GENERATE_SPDX_V2)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling SPDX v2.3 SBOM JSON generation because tag:value generation is "
+ "disabled and that is a requirement for JSON generation.")
+ endif()
+ set(QT_SBOM_GENERATE_SPDX_V2_JSON OFF CACHE BOOL "${spdx_v23_json_help_string}" FORCE)
+ set(QT_SBOM_VERIFY_SPDX_V2 OFF CACHE BOOL "${spdx_v23_verify_help_string}" FORCE)
+ endif()
+
+ # Disable CycloneDX generation if dependencies are not found and it wasn't required.
+ if(QT_GENERATE_SBOM
+ AND QT_SBOM_GENERATE_CYDX_V1_6
+ AND NOT QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_find_cydx_dependencies(OUT_VAR_DEPS_FOUND deps_found)
+ if(NOT deps_found)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling Cyclone DX SBOM generation because dependencies were not found, "
+ "and generation was not marked as required.")
+ endif()
+ set(QT_SBOM_GENERATE_CYDX_V1_6 OFF CACHE BOOL "${cydx_help_string}" FORCE)
+ endif()
+ endif()
+
+ # Disable sbom generation if none of the formats are enabled. Failing to do so will cause
+ # errors in _qt_internal_sbom_begin_project.
+ if(QT_GENERATE_SBOM
+ AND NOT QT_SBOM_GENERATE_SPDX_V2
+ AND NOT QT_SBOM_GENERATE_CYDX_V1_6)
+ if(NOT QT_NO_SBOM_INFORMATIONAL_MESSAGES)
+ message(STATUS
+ "Disabling SBOM generation because none of the supported formats were enabled.")
+ endif()
+ set(QT_GENERATE_SBOM OFF CACHE BOOL "${sbom_help_string}" FORCE)
+ endif()
endfunction()
# Ends repo sbom project generation.
@@ -415,10 +636,6 @@ function(_qt_internal_sbom_end_project)
return()
endif()
- # Now that we know which system libraries are linked against because we added all
- # subdirectories, we can add the recorded system libs to the sbom.
- _qt_internal_sbom_add_recorded_system_libraries()
-
# Run sbom finalization for targets that had it scheduled, but haven't run yet.
# This can happen when _qt_internal_sbom_end_project is called within the same
# subdirectory scope as where the targets are meant to be finalized, but that would be too late
@@ -462,7 +679,24 @@ function(_qt_internal_sbom_end_project)
endif()
endwhile()
- _qt_internal_sbom_end_project_generate()
+ # Now that we know which system libraries are linked against because we added all
+ # subdirectories and finalized all targets, we can add the recorded system libs to the sbom.
+ _qt_internal_sbom_add_recorded_system_libraries()
+
+ # Add any external target dependencies, for CycloneDX generation.
+ # E.g. For QtSvg, we need to create a QtCore component in the QtSvg document, so that we
+ # can declare a dependency on it.
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_add_cydx_external_target_dependencies()
+ endif()
+
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_end_project_generate()
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_end_project_generate_cyclone()
+ endif()
# Clean up external document ref properties, because each repo needs to start from scratch
# in a top-level build.
@@ -777,7 +1011,8 @@ function(_qt_internal_sbom_add_target target)
set_target_properties(${target} PROPERTIES _qt_sbom_is_qt_module TRUE)
endif()
- set(project_package_options "")
+ set(project_package_options_spdx "")
+ set(project_package_options_cydx "")
if(arg_FRIENDLY_PACKAGE_NAME)
set(package_name_for_spdx_id "${arg_FRIENDLY_PACKAGE_NAME}")
@@ -887,7 +1122,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(license_expression)
- list(APPEND project_package_options LICENSE_CONCLUDED "${license_expression}")
+ list(APPEND project_package_options_spdx LICENSE_CONCLUDED "${license_expression}")
+ list(APPEND project_package_options_cydx LICENSE_CONCLUDED "${license_expression}")
endif()
if(license_expression AND
@@ -898,7 +1134,9 @@ function(_qt_internal_sbom_add_target target)
LICENSE_CONCLUDED_EXPRESSION "${license_expression}"
OUT_VAR qt_entity_license_declared_expression)
if(qt_entity_license_declared_expression)
- list(APPEND project_package_options
+ list(APPEND project_package_options_spdx
+ LICENSE_DECLARED "${qt_entity_license_declared_expression}")
+ list(APPEND project_package_options_cydx
LICENSE_DECLARED "${qt_entity_license_declared_expression}")
endif()
endif()
@@ -923,7 +1161,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(copyrights)
list(JOIN copyrights "\n" copyrights)
- list(APPEND project_package_options COPYRIGHT "<text>${copyrights}</text>")
+ list(APPEND project_package_options_spdx COPYRIGHT "<text>${copyrights}</text>")
+ list(APPEND project_package_options_cydx COPYRIGHT "${copyrights}")
endif()
set(package_version "")
@@ -943,7 +1182,12 @@ function(_qt_internal_sbom_add_target target)
endif()
if(package_version)
- list(APPEND project_package_options VERSION "${package_version}")
+ list(APPEND project_package_options_spdx VERSION "${package_version}")
+ list(APPEND project_package_options_cydx VERSION "${package_version}")
+
+ # Also export the value in a target property, to make it available for cydx generation.
+ set_property(TARGET "${target}" PROPERTY _qt_sbom_package_version "${package_version}")
+ set_property(TARGET "${target}" APPEND PROPERTY EXPORT_PROPERTIES _qt_sbom_package_version)
endif()
set(supplier "")
@@ -961,7 +1205,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(supplier)
- list(APPEND project_package_options SUPPLIER "Organization: ${supplier}")
+ list(APPEND project_package_options_spdx SUPPLIER "Organization: ${supplier}")
+ list(APPEND project_package_options_cydx CYDX_SUPPLIER "${supplier}")
endif()
set(download_location "")
@@ -1002,11 +1247,12 @@ function(_qt_internal_sbom_add_target target)
endif()
if(download_location)
- list(APPEND project_package_options DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND project_package_options_spdx DOWNLOAD_LOCATION "${download_location}")
+ list(APPEND project_package_options_cydx DOWNLOAD_LOCATION "${download_location}")
endif()
_qt_internal_sbom_get_package_purpose("${sbom_entity_type}" package_purpose)
- list(APPEND project_package_options PURPOSE "${package_purpose}")
+ list(APPEND project_package_options_spdx PURPOSE "${package_purpose}")
set(cpe_values "")
@@ -1046,7 +1292,8 @@ function(_qt_internal_sbom_add_target target)
endif()
if(cpe_values)
- list(APPEND project_package_options CPE ${cpe_values})
+ list(APPEND project_package_options_spdx CPE ${cpe_values})
+ list(APPEND project_package_options_cydx CPE ${cpe_values})
endif()
# Assemble arguments to forward to the function that handles purl options.
@@ -1088,12 +1335,16 @@ function(_qt_internal_sbom_add_target target)
list(APPEND purl_args PURL_VALUES ${qa_purls_replaced})
endif()
- list(APPEND purl_args OUT_VAR purl_package_options)
+ list(APPEND purl_args
+ OUT_VAR_PURL_VALUES purl_values
+ OUT_VAR_SPDX_EXT_REF_VALUES spdx_ext_ref_values
+ )
_qt_internal_sbom_handle_purl_values(${target} ${purl_args})
- if(purl_package_options)
- list(APPEND project_package_options ${purl_package_options})
+ if(spdx_ext_ref_values)
+ list(APPEND project_package_options_spdx ${spdx_ext_ref_values})
+ list(APPEND project_package_options_cydx PURL_VALUES ${purl_values})
endif()
if(arg_USE_ATTRIBUTION_FILES)
@@ -1136,32 +1387,75 @@ function(_qt_internal_sbom_add_target target)
endif()
if(package_comment)
- list(APPEND project_package_options COMMENT "<text>\n${package_comment}</text>")
+ list(APPEND project_package_options_spdx COMMENT "<text>\n${package_comment}</text>")
+ list(APPEND project_package_options_cydx COMMENT "\n${package_comment}")
endif()
_qt_internal_sbom_handle_target_dependencies("${target}"
SPDX_ID "${package_spdx_id}"
LIBRARIES "${arg_LIBRARIES}"
PUBLIC_LIBRARIES "${arg_PUBLIC_LIBRARIES}"
- OUT_RELATIONSHIPS relationships
+ OUT_CYDX_DEPENDENCIES cydx_dependencies
+ OUT_SPDX_RELATIONSHIPS spdx_relationships
+ OUT_EXTERNAL_TARGET_DEPENDENCIES external_target_dependencies
)
+ if(cydx_dependencies)
+ list(APPEND project_package_options_cydx DEPENDENCIES ${cydx_dependencies})
+ endif()
+
+ # These are processed at the end of document generation.
+ if(external_target_dependencies)
+ _qt_internal_sbom_record_external_target_dependecies(
+ TARGETS ${external_target_dependencies}
+ )
+ endif()
get_cmake_property(project_spdx_id _qt_internal_sbom_project_spdx_id)
- list(APPEND relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
+ list(APPEND spdx_relationships "${project_spdx_id} CONTAINS ${package_spdx_id}")
if(arg_SBOM_RELATIONSHIPS)
- list(APPEND relationships "${arg_SBOM_RELATIONSHIPS}")
+ list(APPEND spdx_relationships "${arg_SBOM_RELATIONSHIPS}")
endif()
- list(REMOVE_DUPLICATES relationships)
- list(JOIN relationships "\nRelationship: " relationships)
- list(APPEND project_package_options RELATIONSHIP "${relationships}")
+ list(REMOVE_DUPLICATES spdx_relationships)
+ list(JOIN spdx_relationships "\nRelationship: " relationships)
+ list(APPEND project_package_options_spdx RELATIONSHIP "${relationships}")
- _qt_internal_sbom_generate_add_package(
- PACKAGE "${package_name_for_spdx_id}"
- SPDXID "${package_spdx_id}"
- ${project_package_options}
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_generate_add_package(
+ PACKAGE "${package_name_for_spdx_id}"
+ SPDXID "${package_spdx_id}"
+ ${project_package_options_spdx}
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ get_property(external_targets
+ GLOBAL PROPERTY _qt_internal_sbom_external_target_dependencies)
+
+ # Prevent against case when a system library is also an external target dependency,
+ # which would lead to its creation twice, once via
+ # _qt_internal_sbom_add_recorded_system_libraries
+ # and second time via
+ # _qt_internal_sbom_add_cydx_external_target_dependencies.
+ # Skip the case when it's done via the first function, and only allow the second.
+ # TODO: Can this be done better somehow?
+ if(NOT target IN_LIST external_targets)
+ _qt_internal_sbom_handle_qt_entity_cydx_properties(
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ OUT_CYDX_PROPERTIES cydx_properties
+ )
+
+ _qt_internal_sbom_generate_cyclone_add_package(
+ PACKAGE "${package_name_for_spdx_id}"
+ SPDXID "${package_spdx_id}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ ${project_package_options_cydx}
+ CONTAINING_COMPONENT "${project_spdx_id}"
+ CYDX_PROPERTIES ${cydx_properties}
+ )
+ endif()
+ endif()
set(no_install_option "")
if(arg_NO_INSTALL)
@@ -1199,25 +1493,27 @@ function(_qt_internal_sbom_add_target target)
set(license_option LICENSE_EXPRESSION "${license_expression}")
endif()
- _qt_internal_sbom_handle_target_binary_files("${target}"
- ${no_install_option}
- ${framework_option}
- ${install_prefix_option}
- SBOM_ENTITY_TYPE "${sbom_entity_type}"
- ${target_binary_multi_config_args}
- SPDX_ID "${package_spdx_id}"
- ${copyrights_option}
- ${license_option}
- )
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_handle_target_binary_files("${target}"
+ ${no_install_option}
+ ${framework_option}
+ ${install_prefix_option}
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ ${target_binary_multi_config_args}
+ SPDX_ID "${package_spdx_id}"
+ ${copyrights_option}
+ ${license_option}
+ )
- _qt_internal_sbom_handle_target_custom_files("${target}"
- ${no_install_option}
- ${install_prefix_option}
- PACKAGE_TYPE "${sbom_entity_type}"
- PACKAGE_SPDX_ID "${package_spdx_id}"
- ${copyrights_option}
- ${license_option}
- )
+ _qt_internal_sbom_handle_target_custom_files("${target}"
+ ${no_install_option}
+ ${install_prefix_option}
+ PACKAGE_TYPE "${sbom_entity_type}"
+ PACKAGE_SPDX_ID "${package_spdx_id}"
+ ${copyrights_option}
+ ${license_option}
+ )
+ endif()
endfunction()
# Helper to add sbom information for a possibly non-existing target.
@@ -1576,7 +1872,11 @@ function(_qt_internal_sbom_record_target_spdx_id target)
SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
PACKAGE_NAME "${package_name_for_spdx_id}"
)
- _qt_internal_sbom_save_spdx_id_for_target("${target}" "${package_spdx_id}")
+ _qt_internal_sbom_save_spdx_id_for_target("${target}"
+ SPDX_ID "${package_spdx_id}"
+ PACKAGE_NAME "${package_name_for_spdx_id}"
+ SBOM_ENTITY_TYPE "${arg_SBOM_ENTITY_TYPE}"
+ )
_qt_internal_sbom_is_qt_entity_type("${arg_SBOM_ENTITY_TYPE}" is_qt_entity_type)
_qt_internal_sbom_save_spdx_id_for_qt_entity_type(
@@ -1615,10 +1915,36 @@ function(_qt_internal_sbom_generate_target_package_spdx_id out_var)
endfunction()
# Save a spdx id for a target inside its target properties.
-# Also saves the repo document namespace and relative installed repo document path.
# These are used when generating a SPDX external document reference for exported targets, to
# include them in relationships.
-function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
+# Also saves the repo document namespace and relative installed repo document path.
+# Also saves the sbom entity type and package name, because it's needed when creating CycloneDX
+# components where the target is in an external document.
+function(_qt_internal_sbom_save_spdx_id_for_target target)
+ set(opt_args "")
+ set(single_args
+ SPDX_ID
+ PACKAGE_NAME
+ SBOM_ENTITY_TYPE
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ if(NOT arg_SPDX_ID)
+ message(FATAL_ERROR "arg_SPDX_ID must be set")
+ endif()
+
+ if(NOT arg_PACKAGE_NAME)
+ message(FATAL_ERROR "PACKAGE_NAME must be set")
+ endif()
+
+ if(NOT arg_SBOM_ENTITY_TYPE)
+ message(FATAL_ERROR "SBOM_ENTITY_TYPE must be set")
+ endif()
+
+ set(spdx_id "${arg_SPDX_ID}")
+
message(DEBUG "Saving spdx id for target ${target}: ${spdx_id}")
set(target_unaliased "${target}")
@@ -1631,6 +1957,9 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
get_property(repo_document_namespace
GLOBAL PROPERTY _qt_internal_sbom_repo_document_namespace)
+ get_property(bom_serial_number_uuid
+ GLOBAL PROPERTY _qt_internal_sbom_repo_cyclone_dx_bom_serial_number_uuid)
+
get_property(relative_installed_repo_document_path
GLOBAL PROPERTY _qt_internal_sbom_relative_installed_repo_document_path)
@@ -1643,6 +1972,10 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
"${repo_document_namespace}")
set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_cydx_bom_serial_number_uuid
+ "${bom_serial_number_uuid}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
_qt_sbom_spdx_relative_installed_repo_document_path
"${relative_installed_repo_document_path}")
@@ -1650,11 +1983,22 @@ function(_qt_internal_sbom_save_spdx_id_for_target target spdx_id)
_qt_sbom_spdx_repo_project_name_lowercase
"${project_name_lowercase}")
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_package_name
+ "${arg_PACKAGE_NAME}")
+
+ set_property(TARGET ${target_unaliased} PROPERTY
+ _qt_sbom_entity_type
+ "${arg_SBOM_ENTITY_TYPE}")
+
# Export the properties, so they can be queried by other repos.
# We also do it for versionless targets.
set(export_properties
+ _qt_sbom_entity_type
+ _qt_sbom_package_name
_qt_sbom_spdx_id
_qt_sbom_spdx_repo_document_namespace
+ _qt_sbom_cydx_bom_serial_number_uuid
_qt_sbom_spdx_relative_installed_repo_document_path
_qt_sbom_spdx_repo_project_name_lowercase
)
@@ -1719,11 +2063,18 @@ function(_qt_internal_sbom_save_spdx_id_for_qt_entity_type target is_qt_entity_t
versionless_target
versionless_private_target
)
+
+ get_property(package_name TARGET "${target}" PROPERTY _qt_sbom_package_name)
+ get_property(sbom_entity_type TARGET "${target}" PROPERTY _qt_sbom_entity_type)
endif()
foreach(target_name IN LISTS ${target_names})
if(TARGET "${target_name}")
- _qt_internal_sbom_save_spdx_id_for_target("${target_name}" "${package_spdx_id}")
+ _qt_internal_sbom_save_spdx_id_for_target("${target_name}"
+ SPDX_ID "${package_spdx_id}"
+ PACKAGE_NAME "${package_name}"
+ SBOM_ENTITY_TYPE "${sbom_entity_type}"
+ )
endif()
endforeach()
endfunction()
@@ -2025,7 +2376,12 @@ endfunction()
function(_qt_internal_sbom_compute_project_file_name out_var)
set(opt_args
- EXTENSION_JSON
+ SPDX_TAG_VALUE
+ SPDX_JSON
+ CYCLONEDX_JSON
+ CYCLONEDX_TOML
+
+ EXTENSION_JSON # deprecated, used by WebEngine
)
set(single_args
PROJECT_NAME
@@ -2040,6 +2396,16 @@ function(_qt_internal_sbom_compute_project_file_name out_var)
message(FATAL_ERROR "PROJECT_NAME must be set")
endif()
+ if(NOT arg_SPDX_JSON
+ AND NOT arg_SPDX_TAG_VALUE
+ AND NOT arg_CYCLONEDX_TOML
+ AND NOT arg_CYCLONEDX_JSON
+ AND NOT arg_EXTENSION_JSON
+ )
+ message(FATAL_ERROR "One of the following options should be set: "
+ "SPDX_TAG_VALUE, SPDX_JSON, CYCLONEDX_JSON, CYCLONEDX_TOML")
+ endif()
+
string(TOLOWER "${arg_PROJECT_NAME}" project_name_lowercase)
set(version_suffix "")
@@ -2050,10 +2416,16 @@ function(_qt_internal_sbom_compute_project_file_name out_var)
set(version_suffix "-${QT_REPO_MODULE_VERSION}")
endif()
- if(arg_EXTENSION_JSON)
+ if(arg_SPDX_TAG_VALUE)
+ set(extension "spdx")
+ elseif(arg_SPDX_JSON OR arg_EXTENSION_JSON)
set(extension "spdx.json")
+ elseif(arg_CYCLONEDX_TOML)
+ set(extension "cdx.toml")
+ elseif(arg_CYCLONEDX_JSON)
+ set(extension "cdx.json")
else()
- set(extension "spdx")
+ message(FATAL_ERROR "Unknown file extension for SBOM generation.")
endif()
set(result
diff --git a/cmake/QtPublicSbomLicenseHelpers.cmake b/cmake/QtPublicSbomLicenseHelpers.cmake
index a91f4c2cefa..a9529fbe3d7 100644
--- a/cmake/QtPublicSbomLicenseHelpers.cmake
+++ b/cmake/QtPublicSbomLicenseHelpers.cmake
@@ -44,10 +44,20 @@ function(_qt_internal_sbom_add_license)
set(license_id "LicenseRef-${license_id}")
endif()
- _qt_internal_sbom_generate_add_license(
- LICENSE_ID "${license_id}"
- EXTRACTED_TEXT "<text>${text}</text>"
- )
+
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ _qt_internal_sbom_generate_add_license(
+ LICENSE_ID "${license_id}"
+ EXTRACTED_TEXT "<text>${text}</text>"
+ )
+ endif()
+
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ _qt_internal_sbom_record_license_cydx(
+ LICENSE_ID "${license_id}"
+ EXTRACTED_TEXT "${text}"
+ )
+ endif()
endfunction()
# Get a qt spdx license expression given the id.
diff --git a/cmake/QtPublicSbomOpsHelpers.cmake b/cmake/QtPublicSbomOpsHelpers.cmake
index 58af2a57f1f..78c4eaaa5e6 100644
--- a/cmake/QtPublicSbomOpsHelpers.cmake
+++ b/cmake/QtPublicSbomOpsHelpers.cmake
@@ -6,14 +6,28 @@
# like NTIA validation, auditing, json generation, etc.
function(_qt_internal_sbom_setup_project_ops_generation)
set(opt_args
+ # spdx options
GENERATE_JSON
GENERATE_JSON_REQUIRED
+
+ # cydx options
+ GENERATE_CYCLONE_DX_V1_6
+ GENERATE_CYCLONE_DX_V1_6_REQUIRED
+ VERIFY_CYCLONE_DX_V1_6
+ VERIFY_CYCLONE_DX_V1_6_REQUIRED
+ VERBOSE_CYCLONE_DX_V1_6
+
+ # source spdx options
GENERATE_SOURCE_SBOM
+ LINT_SOURCE_SBOM
+ LINT_SOURCE_SBOM_NO_ERROR
+
+ # Extra spdx verification
VERIFY_SBOM
VERIFY_SBOM_REQUIRED
VERIFY_NTIA_COMPLIANT
- LINT_SOURCE_SBOM
- LINT_SOURCE_SBOM_NO_ERROR
+
+ # Extra spdx info
SHOW_TABLE
AUDIT
AUDIT_NO_ERROR
@@ -38,6 +52,33 @@ function(_qt_internal_sbom_setup_project_ops_generation)
endif()
endif()
+ if(arg_GENERATE_CYCLONE_DX_V1_6 AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
+ set(op_args
+ OUT_VAR_DEPS_FOUND deps_found
+ )
+ if(arg_GENERATE_CYCLONE_DX_V1_6_REQUIRED)
+ list(APPEND op_args REQUIRED)
+ endif()
+
+ set(gen_args "")
+ if(arg_VERIFY_CYCLONE_DX_V1_6)
+ list(APPEND gen_args VERIFY)
+ endif()
+
+ if(arg_VERIFY_CYCLONE_DX_V1_6_REQUIRED)
+ list(APPEND gen_args VERIFY_REQUIRED)
+ endif()
+
+ if(arg_VERBOSE_CYCLONE_DX_V1_6)
+ list(APPEND gen_args VERBOSE)
+ endif()
+
+ _qt_internal_sbom_find_cydx_dependencies(${op_args})
+ if(deps_found)
+ _qt_internal_sbom_generate_cydx_json(${gen_args})
+ endif()
+ endif()
+
if(arg_VERIFY_SBOM AND NOT QT_INTERNAL_NO_SBOM_PYTHON_OPS)
set(op_args
OP_KEY "VERIFY_SBOM"
@@ -90,12 +131,119 @@ function(_qt_internal_sbom_setup_project_ops_generation)
endif()
endfunction()
+# Tries to find the dependencies for generating CycloneDX v1.6 SBOMs.
+# Sets OUT_VAR_DEPS_FOUND to TRUE if found, FALSE otherwise.
+function(_qt_internal_sbom_find_cydx_dependencies)
+ set(opt_args
+ REQUIRED
+ )
+ set(single_args
+ OUT_VAR_DEPS_FOUND
+ )
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(NOT arg_OUT_VAR_DEPS_FOUND)
+ message(FATAL_ERROR "OUT_VAR_DEPS_FOUND is required")
+ endif()
+
+ set(op_args
+ OP_KEY "GENERATE_CYCLONE_DX_V1_6"
+ OUT_VAR_DEPS_FOUND deps_found
+ )
+
+ if(arg_REQUIRED)
+ list(APPEND op_args REQUIRED)
+ endif()
+
+ _qt_internal_sbom_find_and_handle_sbom_op_dependencies(${op_args})
+
+ set(${arg_OUT_VAR_DEPS_FOUND} "${deps_found}" PARENT_SCOPE)
+endfunction()
+
+# Helper to output a debug or error message when python or one of its dependencies are not found.
+# When found, it also caches the found python interpreter path and version, as well as the
+# DEPS_FOUND_FOR_$OP_KEY var.
+function(_qt_internal_sbom_verify_if_sbom_op_dependencies_are_found)
+ set(opt_args
+ REQUIRED
+ PYTHON_FOUND
+ DEP_FOUND
+ EVERYTHING_FOUND
+ )
+ set(single_args
+ REQUIRED_VERSION
+ PYTHON_PATH
+ PYTHON_VERSION
+ DEP_PACKAGE_NAME
+ DEP_FIND_OUTPUT
+ OP_KEY
+ )
+ set(multi_args
+ PYTHON_COMMON_ARGS
+ )
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+
+ if(arg_EVERYTHING_FOUND)
+ message(DEBUG "Python Dependencies found. "
+ "Using Python '${arg_PYTHON_PATH}' for running SBOM op '${arg_OP_KEY}'.")
+
+ if(NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
+ message(DEBUG "Setting QT_INTERNAL_SBOM_PYTHON_EXECUTABLE to ${arg_PYTHON_PATH}")
+ message(DEBUG "Setting QT_INTERNAL_SBOM_PYTHON_VERSION to ${arg_PYTHON_VERSION}")
+ set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${arg_PYTHON_PATH}" CACHE INTERNAL
+ "Python interpeter used for SBOM generation.")
+ set(QT_INTERNAL_SBOM_PYTHON_VERSION "${arg_PYTHON_VERSION}" CACHE INTERNAL
+ "Python interpeter version used for SBOM generation.")
+ endif()
+
+ set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
+ "All dependencies found to run SBOM op '${arg_OP_KEY}'")
+ return()
+ endif()
+
+ if(arg_REQUIRED)
+ set(message_type "FATAL_ERROR")
+ else()
+ set(message_type "DEBUG")
+ endif()
+
+ if(NOT arg_PYTHON_FOUND)
+ # Look for python one more time, this time without QUIET, to show an error why it
+ # wasn't found.
+ if(arg_REQUIRED)
+ _qt_internal_sbom_find_python_helper(${arg_PYTHON_COMMON_ARGS}
+ OUT_VAR_PYTHON_PATH unused_python
+ OUT_VAR_PYTHON_FOUND unused_found
+ )
+ endif()
+ message(${message_type}
+ "Python ${arg_REQUIRED_VERSION} for running SBOM op '${arg_OP_KEY}' NOT found.")
+ elseif(NOT arg_DEP_FOUND)
+
+ set(extra_install_message "")
+ if(message_type STREQUAL "FATAL_ERROR")
+ set(extra_install_message
+ "Install it using pip install '${arg_DEP_PACKAGE_NAME}' \n")
+ endif()
+ message(${message_type}
+ "Python dependency for running SBOM op '${arg_OP_KEY}' NOT found:\n"
+ ${extra_install_message}
+ "Details:\n"
+ "Python interpreter: ${arg_PYTHON_PATH}\n"
+ "Error output: \n${arg_DEP_FIND_OUTPUT}\n\n"
+ )
+ endif()
+endfunction()
+
# Helper to find a python interpreter and a specific python dependency, e.g. to be able to generate
# a SPDX JSON SBOM, or run post-installation steps like NTIA verification.
# The exact dependency should be specified as the OP_KEY.
#
# Caches the found python executable in a separate cache var QT_INTERNAL_SBOM_PYTHON_EXECUTABLE, to
# avoid conflicts with any other found python interpreter.
+# Also caches the python version found in QT_INTERNAL_SBOM_PYTHON_VERSION, so we can show it
+# in the configure summary.
function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
set(opt_args
REQUIRED
@@ -112,12 +260,22 @@ function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
message(FATAL_ERROR "OP_KEY is required")
endif()
- set(supported_ops "GENERATE_JSON" "VERIFY_SBOM" "RUN_NTIA")
+ set(supported_ops
+ "GENERATE_JSON"
+ "GENERATE_CYCLONE_DX_V1_6"
+ "VERIFY_SBOM"
+ "RUN_NTIA"
+ )
if(arg_OP_KEY STREQUAL "GENERATE_JSON" OR arg_OP_KEY STREQUAL "VERIFY_SBOM")
set(import_statement "import spdx_tools.spdx.clitools.pyspdxtools")
+ set(dep_package_name "spdx-tools")
+ elseif(arg_OP_KEY STREQUAL "GENERATE_CYCLONE_DX_V1_6")
+ set(import_statement "from cyclonedx.output.json import JsonV1Dot6")
+ set(dep_package_name "cyclonedx-python-lib[json-validation]")
elseif(arg_OP_KEY STREQUAL "RUN_NTIA")
set(import_statement "import ntia_conformance_checker.main")
+ set(dep_package_name "ntia-conformance-checker")
else()
message(FATAL_ERROR "OP_KEY must be one of ${supported_ops}")
endif()
@@ -145,48 +303,81 @@ function(_qt_internal_sbom_find_and_handle_sbom_op_dependencies)
# non-framework python found.
if(CMAKE_HOST_APPLE)
set(extra_python_args SEARCH_IN_FRAMEWORKS QUIET)
+ message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}' "
+ "in the system framework location first.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
+ if(NOT everything_found)
+ # Assemble args to show what wasn't found.
+ set(verify_args "")
+ if(python_found)
+ list(APPEND verify_args PYTHON_FOUND)
+ endif()
+ if(dep_found)
+ list(APPEND verify_args DEP_FOUND)
+ endif()
+ if(everything_found)
+ list(APPEND verify_args EVERYTHING_FOUND)
+ endif()
+ if(python_path)
+ list(APPEND verify_args PYTHON_PATH "${python_path}")
+ endif()
+ if(python_version)
+ list(APPEND verify_args PYTHON_VERSION "${python_version}")
+ endif()
+ if(dep_find_output)
+ list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
+ endif()
+
+ _qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
+ ${verify_args}
+ REQUIRED_VERSION "${required_version}"
+ OP_KEY "${arg_OP_KEY}"
+ DEP_PACKAGE_NAME "${dep_package_name}"
+ PYTHON_COMMON_ARGS ${python_common_args}
+ )
+
+ message(DEBUG "Initial Apple-specific SBOM Python search did not find all dependencies "
+ "for op ${arg_OP_KEY}, looking again outside of the system framework location.")
+ endif()
endif()
+ # Looking again if something wasn't found, or a search was not done yet.
if(NOT everything_found)
set(extra_python_args QUIET)
+ message(DEBUG "Looking for Python and dependencies for op '${arg_OP_KEY}'.")
_qt_internal_sbom_find_python_and_dependency_helper_lambda()
endif()
- # Always save the python interpreter path if it is found, even if the dependencies are not
- # found. This improves the error message workflow.
- if(python_found AND NOT QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
- set(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE "${python_path}" CACHE INTERNAL
- "Python interpeter used for SBOM generation.")
+ # Assemble args to show what was or wasn't found.
+ set(verify_args "")
+ if(arg_REQUIRED)
+ list(APPEND verify_args REQUIRED)
endif()
-
- if(NOT everything_found)
- if(arg_REQUIRED)
- set(message_type "FATAL_ERROR")
- else()
- set(message_type "DEBUG")
- endif()
-
- if(NOT python_found)
- # Look for python one more time, this time without QUIET, to show an error why it
- # wasn't found.
- if(arg_REQUIRED)
- _qt_internal_sbom_find_python_helper(${python_common_args}
- OUT_VAR_PYTHON_PATH unused_python
- OUT_VAR_PYTHON_FOUND unused_found
- )
- endif()
- message(${message_type} "Python ${required_version} for running SBOM ops not found.")
- elseif(NOT dep_found)
- message(${message_type} "Python dependency for running SBOM op ${arg_OP_KEY} "
- "not found:\n Python: ${python_path} \n Output: \n${dep_find_output}")
- endif()
- else()
- message(DEBUG "Using Python ${python_path} for running SBOM ops.")
-
- set(QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY} "TRUE" CACHE INTERNAL
- "All dependencies found to run SBOM OP ${arg_OP_KEY}")
+ if(python_found)
+ list(APPEND verify_args PYTHON_FOUND)
+ endif()
+ if(dep_found)
+ list(APPEND verify_args DEP_FOUND)
+ endif()
+ if(everything_found)
+ list(APPEND verify_args EVERYTHING_FOUND)
+ endif()
+ if(python_path)
+ list(APPEND verify_args PYTHON_PATH "${python_path}")
endif()
+ if(python_version)
+ list(APPEND verify_args PYTHON_VERSION "${python_version}")
+ endif()
+ if(dep_find_output)
+ list(APPEND verify_args DEP_FIND_OUTPUT "${dep_find_output}")
+ endif()
+ _qt_internal_sbom_verify_if_sbom_op_dependencies_are_found(
+ ${verify_args}
+ REQUIRED_VERSION "${required_version}"
+ OP_KEY "${arg_OP_KEY}"
+ DEP_PACKAGE_NAME "${dep_package_name}"
+ PYTHON_COMMON_ARGS ${python_common_args}
+ )
if(arg_OUT_VAR_DEPS_FOUND)
set(${arg_OUT_VAR_DEPS_FOUND} "${QT_INTERNAL_SBOM_DEPS_FOUND_FOR_${arg_OP_KEY}}"
@@ -298,7 +489,7 @@ function(_qt_internal_sbom_check_python_dependency_available)
_qt_internal_validate_all_args_are_parsed(arg)
set(failure_message
- "Required Python dependencies not found: ")
+ "Required Python dependencies NOT found: ")
if(arg_FAILURE_MESSAGE_PREFIX)
list(PREPEND failure_message ${arg_FAILURE_MESSAGE_PREFIX})
@@ -393,6 +584,67 @@ function(_qt_internal_sbom_generate_json)
set_property(GLOBAL APPEND PROPERTY _qt_sbom_cmake_verify_include_files "${verify_sbom}")
endfunction()
+# Helper to generate a CycloneDX JSON file from the intermediate .toml file created by the build
+# system.
+function(_qt_internal_sbom_generate_cydx_json)
+ set(opt_args
+ VERIFY
+ VERIFY_REQUIRED
+ VERBOSE
+ )
+ set(single_args "")
+ set(multi_args "")
+ cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
+ _qt_internal_validate_all_args_are_parsed(arg)
+
+ set(error_message_prefix "Failed to generate a CycloneDX json file.")
+ _qt_internal_sbom_assert_python_interpreter_available("${error_message_prefix}")
+ _qt_internal_sbom_assert_python_dependency_available(GENERATE_CYCLONE_DX_V1_6
+ "cyclonedx" ${error_message_prefix})
+
+ _qt_internal_sbom_get_cyclone_dx_generator_path(generator_path)
+
+ set(extra_args "")
+ if(arg_VERIFY)
+ list(APPEND extra_args "--validate-json")
+ endif()
+ if(arg_VERIFY_REQUIRED)
+ list(APPEND extra_args "--validate-json-required")
+ endif()
+ if(arg_VERBOSE)
+ list(APPEND extra_args "--verbose")
+ endif()
+ list(JOIN extra_args " " extra_args)
+
+ set(content "
+ message(STATUS
+ \"Generating final CycloneDX JSON: \${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.json\")
+ execute_process(
+ COMMAND \"${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE}\" \"${generator_path}\"
+ --input-path \"\${QT_SBOM_OUTPUT_PATH}\"
+ --output-path \"\${QT_SBOM_OUTPUT_PATH_WITHOUT_EXT}.json\"
+ ${extra_args}
+ RESULT_VARIABLE res
+ )
+ if(NOT res EQUAL 0)
+ message(FATAL_ERROR \"CycloneDX JSON generation failed: \${res}\")
+ endif()
+ # Remove the intermediate toml file, there's no point in installing it.
+ # Keep the one in the build dir, it's useful for debugging.
+ if(NOT QT_SBOM_BUILD_TIME)
+ message(STATUS \"Removing intermediate TOML file \${QT_SBOM_OUTPUT_PATH}\")
+ file(REMOVE \"\${QT_SBOM_OUTPUT_PATH}\")
+ endif()
+")
+
+ _qt_internal_get_current_project_sbom_dir(sbom_dir)
+ set(generate_cydx "${sbom_dir}/generate_cyclone_dx.cmake")
+ file(GENERATE OUTPUT "${generate_cydx}" CONTENT "${content}")
+
+ set_property(GLOBAL APPEND PROPERTY
+ _qt_sbom_cmake_post_generation_include_files_cydx "${generate_cydx}")
+endfunction()
+
# Helper to query whether the all required dependencies are available to generate a tag / value
# document from a json one.
function(_qt_internal_sbom_verify_deps_for_generate_tag_value_spdx_document)
diff --git a/cmake/QtPublicSbomPurlHelpers.cmake b/cmake/QtPublicSbomPurlHelpers.cmake
index 6f91ea36fed..f6738a2431f 100644
--- a/cmake/QtPublicSbomPurlHelpers.cmake
+++ b/cmake/QtPublicSbomPurlHelpers.cmake
@@ -86,20 +86,31 @@ endmacro()
# default purls will be generated.
#
# There is no limit to the number of purls that can be added to a target.
+# The created purls are saved in:
+# - OUT_VAR_PURL_VALUES as plain purl values, to be used for CycloneDX genereation.
+# - OUT_VAR_SPDX_EXT_REF_VALUES as SPDX ExtRef entries, to be used for SPDX v2.3 generation.
function(_qt_internal_sbom_handle_purl_values target)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
- list(APPEND single_args OUT_VAR)
+ list(APPEND single_args
+ OUT_VAR_SPDX_EXT_REF_VALUES
+ OUT_VAR_PURL_VALUES
+ )
cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
- if(NOT arg_OUT_VAR)
- message(FATAL_ERROR "OUT_VAR must be set")
+ if(NOT arg_OUT_VAR_PURL_VALUES)
+ message(FATAL_ERROR "OUT_VAR_PURL_VALUES must be set")
+ endif()
+
+ if(NOT arg_OUT_VAR_SPDX_EXT_REF_VALUES)
+ message(FATAL_ERROR "OUT_VAR_SPDX_EXT_REF_VALUES must be set")
endif()
_qt_internal_get_sbom_purl_parsing_options(purl_opt_args purl_single_args purl_multi_args)
- set(project_package_options "")
+ set(purl_values "")
+ set(spdx_ext_ref_values "")
# Collect each PURL_ENTRY args into a separate variable.
set(purl_idx -1)
@@ -140,6 +151,16 @@ function(_qt_internal_sbom_handle_purl_values target)
endif()
endif()
+ set(qt_entity_cydx_purl_values "")
+ set(qt_entity_spdx_purl_ext_refs "")
+
+ # When generating purls for Qt entities targeting Cyclone DX, we prefer the generic purl first,
+ # otherwise DependencyTrack gets confused with lots of components having the same purls,
+ # because it doesn't take into account the '#' part of the purl.
+ # Keep these separate and append them in the right order later.
+ set(qt_entity_cydx_purl_for_github_id "")
+ set(qt_entity_cydx_purl_for_generic_id "")
+
foreach(purl_idx IN LISTS purl_entry_indices)
# Clear previous values.
foreach(option_name IN LISTS purl_opt_args purl_single_args purl_multi_args)
@@ -172,11 +193,13 @@ function(_qt_internal_sbom_handle_purl_values target)
${purl_multi_args}
)
+ set(is_qt_entity_purl FALSE)
# Qt entity types get special treatment to gather the required args.
if(arg___QT_INTERNAL_HANDLE_QT_ENTITY_TYPE_PURL
AND arg_PURL_ID
AND arg_PURL_ID IN_LIST qt_purl_ids)
+ set(is_qt_entity_purl TRUE)
_qt_internal_sbom_handle_qt_entity_purl("${target}"
${purl_handling_args}
PURL_ID "${arg_PURL_ID}"
@@ -189,29 +212,57 @@ function(_qt_internal_sbom_handle_purl_values target)
_qt_internal_sbom_assemble_purl(${target}
${purl_args}
- OUT_VAR package_manager_external_ref
+ OUT_VAR purl_bare
+ OUT_VAR_SPDX_EXT_REF package_manager_external_ref_purl
)
- list(APPEND project_package_options ${package_manager_external_ref})
+
+ if(is_qt_entity_purl)
+ if(arg_PURL_ID STREQUAL "GENERIC")
+ set(qt_entity_cydx_purl_for_generic_id "${purl_bare}")
+ elseif(arg_PURL_ID STREQUAL "GITHUB")
+ set(qt_entity_cydx_purl_for_github_id "${purl_bare}")
+ else()
+ list(APPEND purl_values "${purl_bare}")
+ endif()
+ else()
+ list(APPEND purl_values "${purl_bare}")
+ endif()
+
+ list(APPEND spdx_ext_ref_values ${package_manager_external_ref_purl})
endforeach()
+ # Add the custom qt entity purls at the front in the right order for CycloneDX.
+ # If they are empty (for non-Qt entities), nothing will be prepended.
+ set(qt_entity_cydx_purl_values
+ ${qt_entity_cydx_purl_for_generic_id}
+ ${qt_entity_cydx_purl_for_github_id}
+ )
+ list(PREPEND purl_values ${qt_entity_cydx_purl_values})
+
foreach(purl_value IN LISTS arg_PURL_VALUES)
- _qt_internal_sbom_get_purl_value_extref(
- VALUE "${purl_value}" OUT_VAR package_manager_external_ref)
+ _qt_internal_sbom_get_purl_value_extref(VALUE "${purl_value}"
+ OUT_VAR package_manager_external_ref_purl)
# The order in which the purls are generated, matters for tools that consume the SBOM.
# Some tools can only handle one PURL per package, so the first one should be the
# important one.
# For now, I deem that the directly specified ones (probably via a qt_attribution.json
# file) are the more important ones. So we prepend them.
- list(PREPEND project_package_options ${package_manager_external_ref})
+ list(PREPEND purl_values ${purl_value})
+ list(PREPEND spdx_ext_ref_values ${package_manager_external_ref_purl})
endforeach()
- set(${arg_OUT_VAR} "${project_package_options}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_PURL_VALUES} "${purl_values}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SPDX_EXT_REF_VALUES} "${spdx_ext_ref_values}" PARENT_SCOPE)
endfunction()
# Assembles an external reference purl identifier.
+#
# PURL_TYPE and PURL_NAME are required.
-# Stores the result in the OUT_VAR.
+#
+# Stores the bare purl in the OUT_VAR.
+# Stores the SPDX External Reference purl in the OUT_VAR_SPDX_EXT_REF.
+#
# Accepted options:
# PURL_TYPE
# PURL_NAME
@@ -223,6 +274,7 @@ function(_qt_internal_sbom_assemble_purl target)
set(opt_args "")
set(single_args
OUT_VAR
+ OUT_VAR_SPDX_EXT_REF
)
set(multi_args "")
@@ -273,9 +325,10 @@ function(_qt_internal_sbom_assemble_purl target)
string(APPEND purl "#${arg_PURL_SUBPATH}")
endif()
- _qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR result)
+ _qt_internal_sbom_get_purl_value_extref(VALUE "${purl}" OUT_VAR ext_ref_result)
- set(${arg_OUT_VAR} "${result}" PARENT_SCOPE)
+ set(${arg_OUT_VAR} "${purl}" PARENT_SCOPE)
+ set(${arg_OUT_VAR_SPDX_EXT_REF} "${ext_ref_result}" PARENT_SCOPE)
endfunction()
# Takes a PURL VALUE and returns an SBOM purl external reference in OUT_VAR.
diff --git a/cmake/QtPublicSbomPythonHelpers.cmake b/cmake/QtPublicSbomPythonHelpers.cmake
index f83314916f9..fef87af9ee4 100644
--- a/cmake/QtPublicSbomPythonHelpers.cmake
+++ b/cmake/QtPublicSbomPythonHelpers.cmake
@@ -12,6 +12,7 @@ macro(_qt_internal_sbom_find_python_and_dependency_helper_lambda)
DEPENDENCY_IMPORT_STATEMENT "${import_statement}"
OUT_VAR_PYTHON_PATH python_path
OUT_VAR_PYTHON_FOUND python_found
+ OUT_VAR_PYTHON_VERSION python_version
OUT_VAR_DEP_FOUND dep_found
OUT_VAR_PYTHON_AND_DEP_FOUND everything_found
OUT_VAR_DEP_FIND_OUTPUT dep_find_output
@@ -27,6 +28,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
set(single_args
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_FOUND
+ OUT_VAR_PYTHON_VERSION
OUT_VAR_DEP_FOUND
OUT_VAR_PYTHON_AND_DEP_FOUND
OUT_VAR_DEP_FIND_OUTPUT
@@ -65,6 +67,7 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
${arg_PYTHON_ARGS}
OUT_VAR_PYTHON_PATH python_path_inner
OUT_VAR_PYTHON_FOUND python_found_inner
+ OUT_VAR_PYTHON_VERSION python_version_inner
)
if(python_found_inner AND python_path_inner)
@@ -82,6 +85,9 @@ function(_qt_internal_sbom_find_python_and_dependency_helper)
set(${arg_OUT_VAR_PYTHON_PATH} "${python_path_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FOUND} "${python_found_inner}" PARENT_SCOPE)
+ if(arg_OUT_VAR_PYTHON_VERSION)
+ set(${arg_OUT_VAR_PYTHON_VERSION} "${python_version_inner}" PARENT_SCOPE)
+ endif()
set(${arg_OUT_VAR_DEP_FOUND} "${dep_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_AND_DEP_FOUND} "${everything_found_inner}" PARENT_SCOPE)
set(${arg_OUT_VAR_DEP_FIND_OUTPUT} "${dep_find_output_inner}" PARENT_SCOPE)
@@ -90,7 +96,8 @@ endfunction()
# Tries to find the python intrepreter, given the QT_SBOM_PYTHON_INTERP path hint, as well as
# other options.
# Ignores any previously found python.
-# Returns the python interpreter path and whether it was successfully found.
+# Returns the python interpreter path and whether it was successfully found, along with the version
+# found.
#
# This is intentionally a function, and not a macro, to prevent overriding the Python3_EXECUTABLE
# non-cache variable in a global scope in case if a different python is found and used for a
@@ -109,6 +116,7 @@ function(_qt_internal_sbom_find_python_helper)
VERSION
OUT_VAR_PYTHON_PATH
OUT_VAR_PYTHON_FOUND
+ OUT_VAR_PYTHON_VERSION
)
set(multi_args "")
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
@@ -154,6 +162,9 @@ function(_qt_internal_sbom_find_python_helper)
set(${arg_OUT_VAR_PYTHON_PATH} "${Python3_EXECUTABLE}" PARENT_SCOPE)
set(${arg_OUT_VAR_PYTHON_FOUND} "${Python3_Interpreter_FOUND}" PARENT_SCOPE)
+ if(arg_OUT_VAR_PYTHON_VERSION)
+ set(${arg_OUT_VAR_PYTHON_VERSION} "${Python3_VERSION}" PARENT_SCOPE)
+ endif()
endfunction()
# Helper that takes an python import statement to run using the given python interpreter path,
@@ -186,7 +197,7 @@ function(_qt_internal_sbom_find_python_dependency_helper)
set(python_path "${arg_PYTHON_PATH}")
execute_process(
COMMAND
- ${python_path} -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
+ "${python_path}" -c "${arg_DEPENDENCY_IMPORT_STATEMENT}"
RESULT_VARIABLE res
OUTPUT_VARIABLE output
ERROR_VARIABLE output
@@ -197,7 +208,7 @@ function(_qt_internal_sbom_find_python_dependency_helper)
set(output "${output}")
else()
set(found FALSE)
- string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} not found. "
+ string(CONCAT output "SBOM Python dependency ${arg_DEPENDENCY_IMPORT_STATEMENT} NOT found. "
"Error:\n${output}")
endif()
@@ -245,6 +256,6 @@ function(_qt_internal_sbom_find_python_dependency_program)
set(message_type "STATUS")
set(prefix "Optional ")
endif()
- message(${message_type} "${prefix}SBOM python program '${program_name}' not found.")
+ message(${message_type} "${prefix}SBOM python program '${program_name}' NOT found.")
endif()
endfunction()
diff --git a/cmake/QtPublicSbomQtEntityHelpers.cmake b/cmake/QtPublicSbomQtEntityHelpers.cmake
index dfe3bf2be0a..d8d61eef42c 100644
--- a/cmake/QtPublicSbomQtEntityHelpers.cmake
+++ b/cmake/QtPublicSbomQtEntityHelpers.cmake
@@ -393,9 +393,11 @@ endfunction()
function(_qt_internal_sbom_handle_qt_entity_purl_entries)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
list(APPEND single_args
- OUT_VAR # This is unused, but added by the calling function.
+ OUT_VAR_SPDX_EXT_REF_VALUES # This is unused, but added by the calling function.
+ OUT_VAR_PURL_VALUES # This is unused, but added by the calling function.
OUT_VAR_IDS
)
+
cmake_parse_arguments(PARSE_ARGV 0 arg "${opt_args}" "${single_args}" "${multi_args}")
_qt_internal_validate_all_args_are_parsed(arg)
@@ -418,7 +420,8 @@ endfunction()
function(_qt_internal_sbom_handle_qt_entity_purl target)
_qt_internal_get_sbom_purl_handling_options(opt_args single_args multi_args)
list(APPEND single_args
- OUT_VAR # This is unused, but added by the calling function.
+ OUT_VAR_SPDX_EXT_REF_VALUES # This is unused, but added by the calling function.
+ OUT_VAR_PURL_VALUES # This is unused, but added by the calling function.
OUT_PURL_ARGS
PURL_ID
)
diff --git a/cmake/QtSbomHelpers.cmake b/cmake/QtSbomHelpers.cmake
index 75694179fd1..c3ddc581427 100644
--- a/cmake/QtSbomHelpers.cmake
+++ b/cmake/QtSbomHelpers.cmake
@@ -194,3 +194,101 @@ function(qt_internal_extend_qt_entity_sbom target)
qt_internal_sbom_get_default_sbom_args("${target}" sbom_extra_args ${ARGN})
_qt_internal_extend_sbom(${target} ${ARGN} ${sbom_extra_args})
endfunction()
+
+# Helper function to convert a boolean SBOM option into a "yes" / "no" string.
+function(qt_internal_is_sbom_option_enabled var_name out_var)
+ if("${${var_name}}")
+ set(value "yes")
+ else()
+ set(value "no")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+# Helper function to get a summary suffix for SBOM options that are enabled, but might be skipped
+# if their dependencies are missing.
+function(qt_internal_get_sbom_option_required_suffix var_name out_var)
+ if("${${var_name}}")
+ set(value "")
+ else()
+ set(value " (skipped if dependencies are missing)")
+ endif()
+ set(${out_var} "${value}" PARENT_SCOPE)
+endfunction()
+
+# Adds SBOM summary info to the configuration summary.
+function(qt_internal_add_sbom_summary_info)
+ qt_configure_add_summary_section(NAME "SBOM")
+
+ # Build SBOM info.
+ qt_internal_is_sbom_option_enabled(QT_GENERATE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Generate SBOM" TYPE "message" MESSAGE "${value}")
+
+ # Only show the details if generation is enabled.
+ if(QT_GENERATE_SBOM)
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_SPDX_V2 value)
+ qt_configure_add_summary_entry(ARGS "Generate SPDX v2.3"
+ TYPE "message" MESSAGE "${value}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_SPDX_V2_JSON value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON suffix)
+ qt_configure_add_summary_entry(ARGS "Generate SPDX v2.3 JSON"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_VERIFY_SPDX_V2 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_VERIFY_SPDX_V2 suffix)
+ qt_configure_add_summary_entry(ARGS "Verify SPDX v2.3 JSON"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_GENERATE_CYDX_V1_6 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6 suffix)
+ qt_configure_add_summary_entry(ARGS "Generate CyloneDX v1.6"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ qt_internal_is_sbom_option_enabled(QT_SBOM_VERIFY_CYDX_V1_6 value)
+ qt_internal_get_sbom_option_required_suffix(QT_SBOM_REQUIRE_VERIFY_CYDX_V1_6 suffix)
+ qt_configure_add_summary_entry(ARGS "Verify CyloneDX v1.6"
+ TYPE "message" MESSAGE "${value}${suffix}")
+
+ # Python interpreter info.
+ if(QT_INTERNAL_SBOM_PYTHON_EXECUTABLE)
+ set(value "${QT_INTERNAL_SBOM_PYTHON_EXECUTABLE}")
+ string(APPEND value " (version ${QT_INTERNAL_SBOM_PYTHON_VERSION})")
+ else()
+ set(value "Not found")
+ endif()
+ qt_configure_add_summary_entry(ARGS "SBOM Python interpreter"
+ TYPE "message" MESSAGE "${value}")
+
+ # These are kinda internal, so only show them when found.
+ if(QT_SBOM_PROGRAM_SBOM2DOC)
+ set(value "${QT_SBOM_PROGRAM_SBOM2DOC}")
+ qt_configure_add_summary_entry(ARGS "sbom2doc path" TYPE "message" MESSAGE "${value}")
+ endif()
+
+ if(QT_SBOM_PROGRAM_SBOMAUDIT)
+ set(value "${QT_SBOM_PROGRAM_SBOMAUDIT}")
+ qt_configure_add_summary_entry(ARGS "sbomaudit path" TYPE "message" MESSAGE "${value}")
+ endif()
+ endif()
+
+ # Source SBOM info.
+ qt_internal_is_sbom_option_enabled(QT_GENERATE_SOURCE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Generate source SPDX SBOM"
+ TYPE "message" MESSAGE "${value}")
+
+ if(QT_GENERATE_SOURCE_SBOM)
+ qt_internal_is_sbom_option_enabled(QT_LINT_SOURCE_SBOM value)
+ qt_configure_add_summary_entry(ARGS "Verify source SPDX SBOM"
+ TYPE "message" MESSAGE "${value}")
+
+ if(QT_SBOM_PROGRAM_REUSE)
+ set(value "${QT_SBOM_PROGRAM_REUSE}")
+ else()
+ set(value "Not found")
+ endif()
+ qt_configure_add_summary_entry(ARGS "reuse path" TYPE "message" MESSAGE "${value}")
+ endif()
+
+ qt_configure_end_summary_section()
+endfunction()
diff --git a/cmake/QtTargetHelpers.cmake b/cmake/QtTargetHelpers.cmake
index 632fb5b5644..7b2d51d69a7 100644
--- a/cmake/QtTargetHelpers.cmake
+++ b/cmake/QtTargetHelpers.cmake
@@ -841,13 +841,12 @@ function(qt_internal_export_additional_targets_file)
qt_internal_append_export_additional_targets()
- set_property(GLOBAL APPEND PROPERTY _qt_export_additional_targets_ids "${id}")
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_targets_export_name_prefix_${id} "${arg_EXPORT_NAME_PREFIX}")
set_property(GLOBAL APPEND
PROPERTY _qt_export_additional_targets_config_install_dir_${id} "${arg_CONFIG_INSTALL_DIR}")
- qt_add_list_file_finalizer(qt_internal_export_additional_targets_file_finalizer)
+ qt_add_list_file_finalizer(qt_internal_export_additional_targets_file_finalizer "${id}")
endfunction()
function(qt_internal_get_export_additional_targets_id export_name out_var)
@@ -913,19 +912,9 @@ function(qt_internal_validate_export_additional_targets)
set(arg_TARGET_EXPORT_NAMES "${arg_TARGET_EXPORT_NAMES}" PARENT_SCOPE)
endfunction()
-# The finalizer might be called multiple times in the same scope, but only the first one will
-# process all the ids.
-function(qt_internal_export_additional_targets_file_finalizer)
- get_property(ids GLOBAL PROPERTY _qt_export_additional_targets_ids)
-
- foreach(id ${ids})
- qt_internal_export_additional_targets_file_handler("${id}")
- endforeach()
-
- set_property(GLOBAL PROPERTY _qt_export_additional_targets_ids "")
-endfunction()
-
-function(qt_internal_export_additional_targets_file_handler id)
+# The finalizer might be called multiple times in the same directory scope, but it will only process
+# one specific id.
+function(qt_internal_export_additional_targets_file_finalizer id)
get_property(arg_EXPORT_NAME_PREFIX GLOBAL PROPERTY
_qt_export_additional_targets_export_name_prefix_${id})
get_property(arg_CONFIG_INSTALL_DIR GLOBAL PROPERTY
diff --git a/cmake/QtWrapperScriptHelpers.cmake b/cmake/QtWrapperScriptHelpers.cmake
index ecdc043c49c..f76d52bca91 100644
--- a/cmake/QtWrapperScriptHelpers.cmake
+++ b/cmake/QtWrapperScriptHelpers.cmake
@@ -235,6 +235,7 @@ export CMAKE_GENERATOR=Xcode
qt_internal_create_qt_configure_part_wrapper_script("STANDALONE_TESTS")
qt_internal_create_qt_configure_part_wrapper_script("STANDALONE_EXAMPLES")
+ qt_internal_create_cyclone_dx_sbom_generator_script()
if(NOT CMAKE_CROSSCOMPILING)
qt_internal_create_qt_android_runner_wrapper_script()
@@ -381,6 +382,23 @@ function(qt_internal_create_qt_configure_redo_script)
set_property(GLOBAL PROPERTY _qt_configure_redo_script_created TRUE)
endfunction()
+function(qt_internal_create_cyclone_dx_sbom_generator_script)
+ _qt_internal_sbom_get_cyclone_dx_generator_script_name(generator_name generator_relative_dir)
+
+ qt_path_join(build_dir_destination "${QT_BUILD_DIR}" "${INSTALL_LIBEXECDIR}")
+ qt_path_join(install_destination "${QT_INSTALL_DIR}" "${INSTALL_LIBEXECDIR}")
+ qt_path_join(script_path
+ "${CMAKE_CURRENT_SOURCE_DIR}" "${generator_relative_dir}" "${generator_name}")
+
+ if(QT_WILL_INSTALL)
+ file(COPY "${script_path}"
+ DESTINATION "${build_dir_destination}"
+ )
+ endif()
+
+ qt_copy_or_install(PROGRAMS "${script_path}" DESTINATION "${install_destination}")
+endfunction()
+
function(qt_internal_create_qt_android_runner_wrapper_script)
qt_path_join(android_runner_destination "${QT_INSTALL_DIR}" "${INSTALL_LIBEXECDIR}")
qt_path_join(android_runner "${CMAKE_CURRENT_SOURCE_DIR}" "libexec" "qt-android-runner.py")
diff --git a/cmake/configure-cmake-mapping.md b/cmake/configure-cmake-mapping.md
index fadc03c831b..72a30cd87b3 100644
--- a/cmake/configure-cmake-mapping.md
+++ b/cmake/configure-cmake-mapping.md
@@ -45,11 +45,16 @@ The following table describes the mapping of configure options to CMake argument
| -device-option <key=value> | -DQT_QMAKE_DEVICE_OPTIONS=key1=value1;key2=value2 | Only used for generation qmake-compatibility files. |
| | | The device options are written into mkspecs/qdevice.pri. |
| -appstore-compliant | -DFEATURE_appstore_compliant=ON | |
-| -sbom | -DQT_GENERATE_SBOM=ON | Enables generation and installation of a SPDX SBOM documents |
-| -sbom-json | -DQT_SBOM_GENERATE_JSON=ON | Enables generation of SPDX SBOM in JSON format |
-| -sbom-json-required | -DQT_SBOM_REQUIRE_GENERATE_JSON=ON | Fails the build if Python deps are not found |
-| -sbom-verify | -DQT_SBOM_VERIFY=ON | Enables verification of generated SBOMs |
-| -sbom-verify-required | -DQT_SBOM_REQUIRE_VERIFY=ON | Fails the build if Python deps are not found |
+| -sbom | -DQT_GENERATE_SBOM=ON | Enables generation of SBOM documents (multiple formats) |
+| -sbom-spdx-v2 | -DQT_SBOM_GENERATE_SPDX_V2=ON | Enables generation of SPDX v2.3 SBOM in tag:value format |
+| -sbom-cyclonedx-v1_6 | -DQT_SBOM_GENERATE_CYDX_V1_6=ON | Enables generation of CycloneDX v1.6 SBOM in JSON format |
+| -sbom-cyclonedx-v1_6-required | -DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON | Fails the build if Cyclone DX Python deps are not found |
+| -sbom-cyclonedx-v1_6-verify | -DQT_SBOM_VERIFY_CYDX_V1_6=ON | Enables verification of generated CycloneDX v1.6 SBOMs |
+| -sbom-cyclonedx-v1_6-verify-required | -DQT_SBOM_REQUIRE_VERIFY_CYDX_V1_6=ON | Fails the build if Cyclone DX Python verify deps are not found |
+| -sbom-json | -DQT_SBOM_GENERATE_SPDX_V2_JSON=ON | Enables generation of SPDX v2.3 SBOM in JSON format |
+| -sbom-json-required | -DQT_SBOM_REQUIRE_GENERATE_SPDX_V2_JSON=ON | Fails the build if SPDX Python deps are not found |
+| -sbom-verify | -DQT_SBOM_VERIFY_SPDX_V2=ON | Enables verification of generated SPDX v2.3 SBOMs |
+| -sbom-verify-required | -DQT_SBOM_REQUIRE_VERIFY_SPDX_V2=ON | Fails the build if SPDX Python verify deps are not found |
| -sbomdir <dir> | -DINSTALL_SBOMDIR=<dir> | Installation location of SBOM files. |
| -qtinlinenamespace | -DQT_INLINE_NAMESPACE=ON | Make the namespace specified by -qtnamespace an inline one. |
| -qtnamespace <name> | -DQT_NAMESPACE=<name> | |
diff --git a/coin/instructions/prepare_building_env.yaml b/coin/instructions/prepare_building_env.yaml
index 2cc63b136e4..e1d30ce64b4 100644
--- a/coin/instructions/prepare_building_env.yaml
+++ b/coin/instructions/prepare_building_env.yaml
@@ -487,15 +487,16 @@ instructions:
property: host.os
contains_value: Windows
- # Enable warnings are errors
+ # Enable warnings are errors, including qml linting warnings for examples that should not regress
+ # in qmllint warnings
- type: Group
instructions:
- type: AppendToEnvironmentVariable
variableName: COMMON_CMAKE_ARGS
- variableValue: " -DWARNINGS_ARE_ERRORS=ON"
+ variableValue: " -DWARNINGS_ARE_ERRORS=ON -DQT_LINT_EXAMPLES=ON"
- type: AppendToEnvironmentVariable
variableName: COMMON_TARGET_CMAKE_ARGS
- variableValue: " -DWARNINGS_ARE_ERRORS=ON"
+ variableValue: " -DWARNINGS_ARE_ERRORS=ON -DQT_LINT_EXAMPLES=ON"
enable_if:
condition: property
property: features
@@ -549,18 +550,25 @@ instructions:
property: features
contains_value: GenerateSBOM
instructions:
+ - type: EnvironmentVariable
+ variableName: SBOM_COMMON_ARGS
+ variableValue: >-
+ -DQT_GENERATE_SBOM=ON
+ -DQT_GENERATE_SOURCE_SBOM=ON
+ -DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON
+ -DQT_SBOM_VERIFY_CYDX_V1_6=ON
- type: AppendToEnvironmentVariable
variableName: COMMON_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_NON_QTBASE_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_TARGET_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
- type: AppendToEnvironmentVariable
variableName: COMMON_NON_QTBASE_TARGET_CMAKE_ARGS
- variableValue: " -DQT_GENERATE_SBOM=ON -DQT_GENERATE_SOURCE_SBOM=ON"
+ variableValue: " {{.Env.SBOM_COMMON_ARGS}} "
# SBOM Python apps path. On Windows python-installed apps are
# in the same directory where pip is, aka Scripts sub-directory.
diff --git a/coin/instructions/vxworks_test_env_setup.yaml b/coin/instructions/vxworks_test_env_setup.yaml
index 9ff710fbc7f..9e40b21acc4 100644
--- a/coin/instructions/vxworks_test_env_setup.yaml
+++ b/coin/instructions/vxworks_test_env_setup.yaml
@@ -67,6 +67,7 @@ instructions:
env_vars["QTEST_FUNCTION_TIMEOUT"]="1000000"
env_vars["QT_PLUGIN_PATH"]="/home/qt/work/install/target/plugins"
env_vars["QT_QPA_PLATFORM_PLUGIN_PATH"]="/home/qt/work/install/target/plugins/platforms"
+ env_vars["TZ"]="UTC"
for i in "${!hosts[@]}"
do
diff --git a/doc/global/config.qdocconf b/doc/global/config.qdocconf
index 0adfe36f516..0baf9a15478 100644
--- a/doc/global/config.qdocconf
+++ b/doc/global/config.qdocconf
@@ -42,5 +42,8 @@ qhp = true
# Disable writing host-specific paths into .index files
locationinfo = false
+# Automatically mark classes declared in private headers as \internal
+internalfilepatterns = *_p.h
+
# Include the warninglimit used for documentation testing in CI
include(warninglimit.qdocconf)
diff --git a/examples/widgets/doc/images/treemodel-structure.svg b/examples/widgets/doc/images/treemodel-structure.svg
index f8bc24803bf..8f86ff33862 100644
--- a/examples/widgets/doc/images/treemodel-structure.svg
+++ b/examples/widgets/doc/images/treemodel-structure.svg
@@ -1,93 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
- preserveAspectRatio="none"
- width="192"
- height="350"
- viewBox="-25 -30 153.6 280"
- version="1.1"
- xmlns="http://www.w3.org/2000/svg">
+ viewBox="0 0 165 300"
+ width="165"
+ height="300"
+ version="1.1"
+ id="svg25"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+
+<style>
+ svg .line-style { stroke: black; fill: none }
+ svg .text-style { font: 12px arial; fill: black }
+ svg .bold-style { font: 12px arial; fill: black; font-weight: bold }
+ svg .item-style { font: 16px arial; fill: black }
+
+ [data-theme="dark"] svg .line-style { stroke: #f2f2f2; fill: none }
+ [data-theme="dark"] svg .text-style { font: 12px arial; fill: #f2f2f2 }
+ [data-theme="dark"] svg .bold-style { font: 12px arial; fill: #f2f2f2; font-weight: bold }
+ [data-theme="dark"] svg .item-style { font: 14px arial; fill: #f2f2f2 }
+
+ [data-theme="light"] svg .line-style { stroke: black; fill: none }
+ [data-theme="light"] svg .text-style { font: 12px arial; fill: black }
+ [data-theme="light"] svg .bold-style { font: 12px arial; fill: black; font-weight: bold }
+ [data-theme="light"] svg .item-style { font: 14px arial; fill: black }
+</style>
<!-- Root node -->
-<g transform="translate(0, 0)">
-<path fill="none" stroke="#333" stroke-dasharray="4" d="M -10 -10 l 20 0 l 0 20 l -20 0 z" />
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="left" dominant-baseline="central" font-weight="bold">
- Root item (empty)
-</text>
-</g>
+<text x="50" y="29" class="bold-style">Root item (empty)</text>
+<path d="M 10,10 L 40,10 L 40,40 L 10,40 Z" class="line-style"
+ stroke-dasharray="5 5" />
<!-- Root spine vertical line solid part -->
-<path fill="none" stroke="#333" d="M 0 10 l 0 190" />
-
+<path d="M 25,40 L 25,265" class="line-style" />
<!-- Root spine vertical line dotted continuation -->
-<path fill="none" stroke="#333" stroke-dasharray="4" d="M 0 200 l 0 30" />
+<path d="M 25,265 L 25,290" class="line-style" stroke-dasharray="3 3" />
-<!-- Horizontal lines from Root spine to children A, E, F -->
-<path fill="none" stroke="#333" d="M 0 40 l 20 0" />
-<path fill="none" stroke="#333" d="M 0 160 l 20 0" />
-<path fill="none" stroke="#333" d="M 0 185 l 20 0" />
+<!-- Horizontal lines from Root spine to children A, C, ... -->
+<path d="M 25,65 L 45,65" class="line-style" />
+<path d="M 25,225 L 45,225" class="line-style" />
+<path d="M 25,265 L 45,265" class="line-style" />
<!-- Node A with label [0] -->
-<g transform="translate(30, 40)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="0" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="middle" dominant-baseline="central">
- A
-</text>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 0
-</text>
-</g>
+<path d="M 45,50 L 75,50 L 75,80 L 45,80 Z" class="line-style" />
+<text x="55" y="70" class="item-style">A</text>
+<text x="82" y="69" class="text-style">row = 0</text>
<!-- Vertical line from A to its children -->
-<path fill="none" stroke="#333" d="M 30 50 l 0 80" />
+<path d="M 60,80 L 60,185" class="line-style" />
-<!-- Horizontal lines from A's spine to B, C, D -->
-<path fill="none" stroke="#333" d="M 30 70 l 20 0" />
-<path fill="none" stroke="#333" d="M 30 100 l 20 0" />
-<path fill="none" stroke="#333" d="M 30 130 l 20 0" />
+<!-- Horizontal lines from A's spine to ..., B, ... -->
+<path d="M 60,105 L 80,105" class="line-style" />
+<path d="M 60,145 L 80,145" class="line-style" />
+<path d="M 60,185 L 80,185" class="line-style" />
-<!-- Node B -->
-<g transform="translate(60, 70)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 0
-</text>
-</g>
+<!-- First unnamed child node of A -->
+<path d="M 80,90 L 110,90 L 110,120 L 80,120 Z" class="line-style" />
+<text x="117" y="109" class="text-style">row = 0</text>
-<!-- Node C -->
-<g transform="translate(60, 100)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="0" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="middle" dominant-baseline="central">
- B
-</text>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 1
-</text>
-</g>
-
-<!-- Node D -->
-<g transform="translate(60, 130)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 2
-</text>
-</g>
+<!-- Node B -->
+<path d="M 80,130 L 110,130 L 110,160 L 80,160 Z" class="line-style" />
+<text x="90" y="150" class="item-style">B</text>
+<text x="117" y="149" class="text-style">row = 1</text>
-<!-- Node E -->
-<g transform="translate(30, 160)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="0" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="middle" dominant-baseline="central">
- C
-</text>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 1
-</text>
-</g>
+<!-- Last unnamed child node of A -->
+<path d="M 80,170 L 110,170 L 110,200 L 80,200 Z" class="line-style" />
+<text x="117" y="189" class="text-style">row = 2</text>
-<!-- Node F -->
-<g transform="translate(30, 185)">
-<path fill="none" stroke="#333" d="M -10 -10 l 20 0 l 0 20 l -20 0 z"/>
-<text x="15" y="0" font-size="12" font-family="Arial" fill="#333" text-anchor="start" dominant-baseline="central">
- row = 2
-</text>
-</g>
+<!-- Node C -->
+<path d="M 45,210 L 75,210 L 75,240 L 45,240 Z" class="line-style" />
+<text x="55" y="230" class="item-style">C</text>
+<text x="82" y="229" class="text-style">row = 1</text>
+<!-- Last visible child node of the root node -->
+<path d="M 45,250 L 75,250 L 75,280 L 45,280 Z" class="line-style" />
+<text x="82" y="269" class="text-style">row = 2</text>
</svg>
diff --git a/licenseRule.json b/licenseRule.json
index 3bc58bddca9..8c4029f91e1 100644
--- a/licenseRule.json
+++ b/licenseRule.json
@@ -377,6 +377,11 @@
"file type": "util",
"spdx": ["LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0"]
},
+ "util/sbom/cyclonedx/pyproject.toml": {
+ "comment": "Default",
+ "file type": "util",
+ "spdx": ["LicenseRef-Qt-Commercial OR BSD-3-Clause"]
+ },
"util/unicode/data/.*\\.txt": {
"comment": "Exception.",
"file type": "3rd party",
diff --git a/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch b/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch
new file mode 100644
index 00000000000..2dd58d4b818
--- /dev/null
+++ b/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch
@@ -0,0 +1,40 @@
+From c75f7f48c8a3d9c6aaaeb13a48fb3c051b46ccab Mon Sep 17 00:00:00 2001
+From: Ivan Solovev <[email protected]>
+Date: Mon, 3 Nov 2025 12:33:26 +0100
+Subject: [PATCH] fix decimal_point initialization to suppress warnings in GCC
+ 14
+
+This patch was submitted upstream as [0], but there was no new release
+yet.
+
+[0]: https://github.com/google/double-conversion/commit/4aecc844c566d84a939fc35f4e62d58bd693f18d
+
+Change-Id: I97cc3103ff758f4c65b23d2f4c0fd82932e36df7
+---
+ .../double-conversion/double-conversion/double-to-string.cc | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
+index 215eaa96d47..9ea3d18d5f7 100644
+--- a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
++++ b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
+@@ -180,7 +180,7 @@ bool DoubleToStringConverter::ToShortestIeeeNumber(
+ return HandleSpecialValues(value, result_builder);
+ }
+
+- int decimal_point;
++ int decimal_point = 0;
+ bool sign;
+ const int kDecimalRepCapacity = kBase10MaximalLength + 1;
+ char decimal_rep[kDecimalRepCapacity];
+@@ -405,6 +405,7 @@ void DoubleToStringConverter::DoubleToAscii(double v,
+ if (mode == PRECISION && requested_digits == 0) {
+ vector[0] = '\0';
+ *length = 0;
++ *point = 0;
+ return;
+ }
+
+--
+2.44.0
+
diff --git a/src/3rdparty/double-conversion/REUSE.toml b/src/3rdparty/double-conversion/REUSE.toml
index a7ebffce4f0..14e6e9b6706 100644
--- a/src/3rdparty/double-conversion/REUSE.toml
+++ b/src/3rdparty/double-conversion/REUSE.toml
@@ -1,7 +1,7 @@
version = 1
[[annotations]]
-path = ["double-conversion/*"]
+path = ["double-conversion/*", "0001-fix-decimal_point-initialization-to-suppress-warning.patch"]
precedence = "closest"
SPDX-FileCopyrightText = "Copyright 2006-2012, the V8 project authors"
SPDX-License-Identifier = "BSD-3-Clause"
diff --git a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
index 215eaa96d47..9ea3d18d5f7 100644
--- a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
+++ b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc
@@ -180,7 +180,7 @@ bool DoubleToStringConverter::ToShortestIeeeNumber(
return HandleSpecialValues(value, result_builder);
}
- int decimal_point;
+ int decimal_point = 0;
bool sign;
const int kDecimalRepCapacity = kBase10MaximalLength + 1;
char decimal_rep[kDecimalRepCapacity];
@@ -405,6 +405,7 @@ void DoubleToStringConverter::DoubleToAscii(double v,
if (mode == PRECISION && requested_digits == 0) {
vector[0] = '\0';
*length = 0;
+ *point = 0;
return;
}
diff --git a/src/3rdparty/pcre2/qt_attribution.json b/src/3rdparty/pcre2/qt_attribution.json
index ef29ad7d951..0468bca1b3e 100644
--- a/src/3rdparty/pcre2/qt_attribution.json
+++ b/src/3rdparty/pcre2/qt_attribution.json
@@ -30,7 +30,7 @@
"Homepage": "http://www.pcre.org/",
"Version": "10.47",
"DownloadLocation": "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2",
- "PURL": "pkg:github/PCRE2Project/pcre2@$<VERSION>",
+ "PURL": "pkg:github/PCRE2Project/pcre2@pcre2-$<VERSION>",
"CPE": "cpe:2.3:a:pcre:pcre2:$<VERSION>:*:*:*:*:*:*:*",
"License": "BSD 2-clause \"Simplified\" License",
"LicenseId": "BSD-2-Clause",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index babf5bc31d2..2920c743243 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -71,7 +71,6 @@ add_subdirectory(tools)
if(QT_FEATURE_gui)
add_subdirectory(gui)
- add_subdirectory(assets)
if(QT_FEATURE_opengl)
add_subdirectory(opengl)
diff --git a/src/assets/CMakeLists.txt b/src/assets/CMakeLists.txt
deleted file mode 100644
index ab07d27696e..00000000000
--- a/src/assets/CMakeLists.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# Copyright (C) 2024 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-if (NOT INTEGRITY AND TARGET Qt6::Network AND TARGET Qt6::Concurrent)
- add_subdirectory(downloader)
-endif()
diff --git a/src/assets/downloader/CMakeLists.txt b/src/assets/downloader/CMakeLists.txt
deleted file mode 100644
index 6b0564e72af..00000000000
--- a/src/assets/downloader/CMakeLists.txt
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (C) 2024 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-qt_internal_add_module(ExamplesAssetDownloaderPrivate
- CONFIG_MODULE_NAME examples_asset_downloader
- STATIC
- INTERNAL_MODULE
- SOURCES
- assetdownloader.cpp assetdownloader.h
- tasking/barrier.cpp tasking/barrier.h
- tasking/concurrentcall.h
- tasking/conditional.cpp tasking/conditional.h
- tasking/networkquery.cpp tasking/networkquery.h
- tasking/qprocesstask.cpp tasking/qprocesstask.h
- tasking/tasking_global.h
- tasking/tasktree.cpp tasking/tasktree.h
- tasking/tasktreerunner.cpp tasking/tasktreerunner.h
- tasking/tcpsocket.cpp tasking/tcpsocket.h
- DEFINES
- QT_NO_CAST_FROM_ASCII
- LIBRARIES
- Qt6::CorePrivate
- PUBLIC_LIBRARIES
- Qt6::Concurrent
- Qt6::Core
- Qt6::Network
- NO_GENERATE_CPP_EXPORTS
-)
-
-if (NOT QT_FEATURE_process)
- set_source_files_properties(tasking/qprocesstask.h PROPERTIES SKIP_AUTOMOC TRUE)
-endif()
diff --git a/src/assets/downloader/assetdownloader.cpp b/src/assets/downloader/assetdownloader.cpp
deleted file mode 100644
index 7c2f525a66d..00000000000
--- a/src/assets/downloader/assetdownloader.cpp
+++ /dev/null
@@ -1,592 +0,0 @@
-// Copyright (C) 2024 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 "assetdownloader.h"
-
-#include "tasking/concurrentcall.h"
-#include "tasking/networkquery.h"
-#include "tasking/tasktreerunner.h"
-
-#include <QtCore/private/qzipreader_p.h>
-
-#include <QtCore/QDir>
-#include <QtCore/QFile>
-#include <QtCore/QJsonArray>
-#include <QtCore/QJsonDocument>
-#include <QtCore/QJsonObject>
-#include <QtCore/QStandardPaths>
-#include <QtCore/QTemporaryDir>
-#include <QtCore/QTemporaryFile>
-
-using namespace Tasking;
-
-QT_BEGIN_NAMESPACE
-
-namespace Assets::Downloader {
-
-struct DownloadableAssets
-{
- QUrl remoteUrl;
- QList<QUrl> files;
-};
-
-class AssetDownloaderPrivate
-{
-public:
- AssetDownloaderPrivate(AssetDownloader *q) : m_q(q) {}
- AssetDownloader *m_q = nullptr;
-
- std::unique_ptr<QNetworkAccessManager> m_manager;
- std::unique_ptr<QTemporaryDir> m_temporaryDir;
- TaskTreeRunner m_taskTreeRunner;
- QString m_lastProgressText;
- QDir m_localDownloadDir;
-
- QString m_jsonFileName;
- QString m_zipFileName;
- QDir m_preferredLocalDownloadDir =
- QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
- QUrl m_offlineAssetsFilePath;
- QUrl m_downloadBase;
- QStringList m_networkErrors;
- QStringList m_sslErrors;
-
- void setLocalDownloadDir(const QDir &dir)
- {
- if (m_localDownloadDir != dir) {
- m_localDownloadDir = dir;
- emit m_q->localDownloadDirChanged(QUrl::fromLocalFile(m_localDownloadDir.absolutePath()));
- }
- }
- void setProgress(int progressValue, int progressMaximum, const QString &progressText)
- {
- m_lastProgressText = progressText;
- emit m_q->progressChanged(progressValue, progressMaximum, progressText);
- }
- void updateProgress(int progressValue, int progressMaximum)
- {
- setProgress(progressValue, progressMaximum, m_lastProgressText);
- }
- void clearProgress(const QString &progressText)
- {
- setProgress(0, 0, progressText);
- }
-
- void setupDownload(NetworkQuery *query, const QString &progressText)
- {
- query->setNetworkAccessManager(m_manager.get());
- clearProgress(progressText);
- QObject::connect(query, &NetworkQuery::started, query, [this, query] {
- QNetworkReply *reply = query->reply();
- QObject::connect(reply, &QNetworkReply::downloadProgress,
- query, [this](qint64 bytesReceived, qint64 totalBytes) {
- updateProgress((totalBytes > 0) ? 100.0 * bytesReceived / totalBytes : 0, 100);
- });
- QObject::connect(reply, &QNetworkReply::errorOccurred, query, [this, reply] {
- m_networkErrors << reply->errorString();
- });
-#if QT_CONFIG(ssl)
- QObject::connect(reply, &QNetworkReply::sslErrors,
- query, [this](const QList<QSslError> &sslErrors) {
- for (const QSslError &sslError : sslErrors)
- m_sslErrors << sslError.errorString();
- });
-#endif
- });
- }
-};
-
-static bool isWritableDir(const QDir &dir)
-{
- if (dir.exists()) {
- QTemporaryFile file(dir.filePath(QString::fromLatin1("tmp")));
- return file.open();
- }
- return false;
-}
-
-static bool sameFileContent(const QFileInfo &first, const QFileInfo &second)
-{
- if (first.exists() ^ second.exists())
- return false;
-
- if (first.size() != second.size())
- return false;
-
- QFile firstFile(first.absoluteFilePath());
- QFile secondFile(second.absoluteFilePath());
-
- if (firstFile.open(QFile::ReadOnly) && secondFile.open(QFile::ReadOnly)) {
- char char1;
- char char2;
- int readBytes1 = 0;
- int readBytes2 = 0;
- while (!firstFile.atEnd()) {
- readBytes1 = firstFile.read(&char1, 1);
- readBytes2 = secondFile.read(&char2, 1);
- if (readBytes1 != readBytes2 || readBytes1 != 1)
- return false;
- if (char1 != char2)
- return false;
- }
- return true;
- }
-
- return false;
-}
-
-static bool createDirectory(const QDir &dir)
-{
- if (dir.exists())
- return true;
-
- if (!createDirectory(dir.absoluteFilePath(QString::fromUtf8(".."))))
- return false;
-
- return dir.mkpath(QString::fromUtf8("."));
-}
-
-static bool canBeALocalBaseDir(const QDir &dir)
-{
- if (dir.exists())
- return !dir.isEmpty() || isWritableDir(dir);
- return createDirectory(dir) && isWritableDir(dir);
-}
-
-static QDir baseLocalDir(const QDir &preferredLocalDir)
-{
- if (canBeALocalBaseDir(preferredLocalDir))
- return preferredLocalDir;
-
- qWarning().noquote() << "AssetDownloader: Cannot set \"" << preferredLocalDir
- << "\" as a local download directory!";
- return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
-}
-
-static QString pathFromUrl(const QUrl &url)
-{
- if (url.isLocalFile())
- return url.toLocalFile();
-
- if (url.scheme() == u"qrc")
- return u":" + url.path();
-
- return url.toString();
-}
-
-static QList<QUrl> filterDownloadableAssets(const QList<QUrl> &assetFiles, const QDir &expectedDir)
-{
- QList<QUrl> downloadList;
- std::copy_if(assetFiles.begin(), assetFiles.end(), std::back_inserter(downloadList),
- [&](const QUrl &assetPath) {
- return !QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString()));
- });
- return downloadList;
-}
-
-static bool allAssetsPresent(const QList<QUrl> &assetFiles, const QDir &expectedDir)
-{
- return std::all_of(assetFiles.begin(), assetFiles.end(), [&](const QUrl &assetPath) {
- return QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString()));
- });
-}
-
-AssetDownloader::AssetDownloader(QObject *parent)
- : QObject(parent)
- , d(new AssetDownloaderPrivate(this))
-{}
-
-AssetDownloader::~AssetDownloader() = default;
-
-QUrl AssetDownloader::downloadBase() const
-{
- return d->m_downloadBase;
-}
-
-void AssetDownloader::setDownloadBase(const QUrl &downloadBase)
-{
- if (d->m_downloadBase != downloadBase) {
- d->m_downloadBase = downloadBase;
- emit downloadBaseChanged(d->m_downloadBase);
- }
-}
-
-QUrl AssetDownloader::preferredLocalDownloadDir() const
-{
- return QUrl::fromLocalFile(d->m_preferredLocalDownloadDir.absolutePath());
-}
-
-void AssetDownloader::setPreferredLocalDownloadDir(const QUrl &localDir)
-{
- if (localDir.scheme() == u"qrc") {
- qWarning() << "Cannot set a qrc as preferredLocalDownloadDir";
- return;
- }
-
- const QString path = pathFromUrl(localDir);
- if (d->m_preferredLocalDownloadDir != path) {
- d->m_preferredLocalDownloadDir.setPath(path);
- emit preferredLocalDownloadDirChanged(preferredLocalDownloadDir());
- }
-}
-
-QUrl AssetDownloader::offlineAssetsFilePath() const
-{
- return d->m_offlineAssetsFilePath;
-}
-
-void AssetDownloader::setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath)
-{
- if (d->m_offlineAssetsFilePath != offlineAssetsFilePath) {
- d->m_offlineAssetsFilePath = offlineAssetsFilePath;
- emit offlineAssetsFilePathChanged(d->m_offlineAssetsFilePath);
- }
-}
-
-QString AssetDownloader::jsonFileName() const
-{
- return d->m_jsonFileName;
-}
-
-void AssetDownloader::setJsonFileName(const QString &jsonFileName)
-{
- if (d->m_jsonFileName != jsonFileName) {
- d->m_jsonFileName = jsonFileName;
- emit jsonFileNameChanged(d->m_jsonFileName);
- }
-}
-
-QString AssetDownloader::zipFileName() const
-{
- return d->m_zipFileName;
-}
-
-void AssetDownloader::setZipFileName(const QString &zipFileName)
-{
- if (d->m_zipFileName != zipFileName) {
- d->m_zipFileName = zipFileName;
- emit zipFileNameChanged(d->m_zipFileName);
- }
-}
-
-QUrl AssetDownloader::localDownloadDir() const
-{
- return QUrl::fromLocalFile(d->m_localDownloadDir.absolutePath());
-}
-
-QStringList AssetDownloader::networkErrors() const
-{
- return d->m_networkErrors;
-}
-
-QStringList AssetDownloader::sslErrors() const
-{
- return d->m_sslErrors;
-}
-
-static void precheckLocalFile(const QUrl &url)
-{
- if (url.isEmpty())
- return;
- QFile file(pathFromUrl(url));
- if (!file.open(QIODevice::ReadOnly))
- qWarning() << "Cannot open local file" << url;
-}
-
-static void readAssetsFileContent(QPromise<DownloadableAssets> &promise, const QByteArray &content)
-{
- const QJsonObject json = QJsonDocument::fromJson(content).object();
- const QJsonArray assetsArray = json[u"assets"].toArray();
- DownloadableAssets result;
- result.remoteUrl = json[u"url"].toString();
- for (const QJsonValue &asset : assetsArray) {
- if (promise.isCanceled())
- return;
- result.files.append(asset.toString());
- }
-
- if (result.files.isEmpty() || result.remoteUrl.isEmpty())
- promise.future().cancel();
- else
- promise.addResult(result);
-}
-
-static void unzip(QPromise<void> &promise, const QByteArray &content, const QDir &directory,
- const QString &fileName)
-{
- const QString zipFilePath = directory.absoluteFilePath(fileName);
- QFile zipFile(zipFilePath);
- if (!zipFile.open(QIODevice::WriteOnly)) {
- promise.future().cancel();
- return;
- }
- zipFile.write(content);
- zipFile.close();
-
- if (promise.isCanceled())
- return;
-
- QZipReader reader(zipFilePath);
- const bool extracted = reader.extractAll(directory.absolutePath());
- reader.close();
- if (extracted)
- QFile::remove(zipFilePath);
- else
- promise.future().cancel();
-}
-
-static void writeAsset(QPromise<void> &promise, const QByteArray &content, const QString &filePath)
-{
- const QFileInfo fileInfo(filePath);
- QFile file(fileInfo.absoluteFilePath());
- if (!createDirectory(fileInfo.dir()) || !file.open(QFile::WriteOnly)) {
- promise.future().cancel();
- return;
- }
-
- if (promise.isCanceled())
- return;
-
- file.write(content);
- file.close();
-}
-
-static void copyAndCheck(QPromise<void> &promise, const QString &sourcePath, const QString &destPath)
-{
- QFile sourceFile(sourcePath);
- QFile destFile(destPath);
- const QFileInfo sourceFileInfo(sourceFile.fileName());
- const QFileInfo destFileInfo(destFile.fileName());
-
- if (destFile.exists() && !destFile.remove()) {
- qWarning().noquote() << QString::fromLatin1("Unable to remove file \"%1\".")
- .arg(QFileInfo(destFile.fileName()).absoluteFilePath());
- promise.future().cancel();
- return;
- }
-
- if (!createDirectory(destFileInfo.absolutePath())) {
- qWarning().noquote() << QString::fromLatin1("Cannot create directory \"%1\".")
- .arg(destFileInfo.absolutePath());
- promise.future().cancel();
- return;
- }
-
- if (promise.isCanceled())
- return;
-
- if (!sourceFile.copy(destFile.fileName()) && !sameFileContent(sourceFileInfo, destFileInfo))
- promise.future().cancel();
-}
-
-void AssetDownloader::start()
-{
- if (d->m_taskTreeRunner.isRunning())
- return;
-
- struct StorageData
- {
- QDir tempDir;
- QByteArray jsonContent;
- DownloadableAssets assets;
- QList<QUrl> assetsToDownload;
- QByteArray zipContent;
- int doneCount = 0;
- };
-
- const Storage<StorageData> storage;
-
- const auto onSetup = [this, storage] {
- if (!d->m_manager)
- d->m_manager = std::make_unique<QNetworkAccessManager>();
- if (!d->m_temporaryDir)
- d->m_temporaryDir = std::make_unique<QTemporaryDir>();
- if (!d->m_temporaryDir->isValid()) {
- qWarning() << "Cannot create a temporary directory.";
- return SetupResult::StopWithError;
- }
- storage->tempDir = d->m_temporaryDir->path();
- d->setLocalDownloadDir(baseLocalDir(d->m_preferredLocalDownloadDir));
- d->m_networkErrors.clear();
- d->m_sslErrors.clear();
- precheckLocalFile(resolvedUrl(d->m_offlineAssetsFilePath));
- return SetupResult::Continue;
- };
-
- const auto onJsonDownloadSetup = [this](NetworkQuery &query) {
- query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_jsonFileName)));
- d->setupDownload(&query, tr("Downloading JSON file..."));
- };
- const auto onJsonDownloadDone = [this, storage](const NetworkQuery &query, DoneWith result) {
- if (result == DoneWith::Success) {
- storage->jsonContent = query.reply()->readAll();
- return DoneResult::Success;
- }
- qWarning() << "Cannot download" << d->m_downloadBase.resolved(d->m_jsonFileName)
- << query.reply()->errorString();
- if (d->m_offlineAssetsFilePath.isEmpty()) {
- qWarning() << "Also there is no local file as a replacement";
- return DoneResult::Error;
- }
-
- QFile file(pathFromUrl(resolvedUrl(d->m_offlineAssetsFilePath)));
- if (!file.open(QIODevice::ReadOnly)) {
- qWarning() << "Also failed to open" << d->m_offlineAssetsFilePath;
- return DoneResult::Error;
- }
-
- storage->jsonContent = file.readAll();
- return DoneResult::Success;
- };
-
- const auto onReadAssetsFileSetup = [storage](ConcurrentCall<DownloadableAssets> &async) {
- async.setConcurrentCallData(readAssetsFileContent, storage->jsonContent);
- };
- const auto onReadAssetsFileDone = [storage](const ConcurrentCall<DownloadableAssets> &async) {
- storage->assets = async.result();
- storage->assetsToDownload = storage->assets.files;
- };
-
- const auto onSkipIfAllAssetsPresent = [this, storage] {
- return allAssetsPresent(storage->assets.files, d->m_localDownloadDir)
- ? SetupResult::StopWithSuccess : SetupResult::Continue;
- };
-
- const auto onZipDownloadSetup = [this, storage](NetworkQuery &query) {
- if (d->m_zipFileName.isEmpty())
- return SetupResult::StopWithSuccess;
-
- query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_zipFileName)));
- d->setupDownload(&query, tr("Downloading zip file..."));
- return SetupResult::Continue;
- };
- const auto onZipDownloadDone = [storage](const NetworkQuery &query, DoneWith result) {
- if (result == DoneWith::Success)
- storage->zipContent = query.reply()->readAll();
- return DoneResult::Success; // Ignore zip download failure
- };
-
- const auto onUnzipSetup = [this, storage](ConcurrentCall<void> &async) {
- if (storage->zipContent.isEmpty())
- return SetupResult::StopWithSuccess;
-
- async.setConcurrentCallData(unzip, storage->zipContent, storage->tempDir, d->m_zipFileName);
- d->clearProgress(tr("Unzipping..."));
- return SetupResult::Continue;
- };
- const auto onUnzipDone = [storage](DoneWith result) {
- if (result == DoneWith::Success) {
- // Avoid downloading assets that are present in unzipped tree
- StorageData &storageData = *storage;
- storageData.assetsToDownload =
- filterDownloadableAssets(storageData.assets.files, storageData.tempDir);
- } else {
- qWarning() << "ZipFile failed";
- }
- return DoneResult::Success; // Ignore unzip failure
- };
-
- const LoopUntil downloadIterator([storage](int iteration) {
- return iteration < storage->assetsToDownload.count();
- });
-
- const Storage<QByteArray> assetStorage;
-
- const auto onAssetsDownloadGroupSetup = [this, storage] {
- d->setProgress(0, storage->assetsToDownload.size(), tr("Downloading assets..."));
- };
-
- const auto onAssetDownloadSetup = [this, storage, downloadIterator](NetworkQuery &query) {
- query.setNetworkAccessManager(d->m_manager.get());
- query.setRequest(QNetworkRequest(storage->assets.remoteUrl.resolved(
- storage->assetsToDownload.at(downloadIterator.iteration()))));
- };
- const auto onAssetDownloadDone = [assetStorage](const NetworkQuery &query, DoneWith result) {
- if (result == DoneWith::Success)
- *assetStorage = query.reply()->readAll();
- };
-
- const auto onAssetWriteSetup = [storage, downloadIterator, assetStorage](
- ConcurrentCall<void> &async) {
- const QString filePath = storage->tempDir.absoluteFilePath(
- storage->assetsToDownload.at(downloadIterator.iteration()).toString());
- async.setConcurrentCallData(writeAsset, *assetStorage, filePath);
- };
- const auto onAssetWriteDone = [this, storage](DoneWith result) {
- if (result != DoneWith::Success) {
- qWarning() << "Asset write failed";
- return;
- }
- StorageData &storageData = *storage;
- ++storageData.doneCount;
- d->updateProgress(storageData.doneCount, storageData.assetsToDownload.size());
- };
-
- const LoopUntil copyIterator([storage](int iteration) {
- return iteration < storage->assets.files.count();
- });
-
- const auto onAssetsCopyGroupSetup = [this, storage] {
- storage->doneCount = 0;
- d->setProgress(0, storage->assets.files.size(), tr("Copying assets..."));
- };
-
- const auto onAssetCopySetup = [this, storage, copyIterator](ConcurrentCall<void> &async) {
- const QString fileName = storage->assets.files.at(copyIterator.iteration()).toString();
- const QString sourcePath = storage->tempDir.absoluteFilePath(fileName);
- const QString destPath = d->m_localDownloadDir.absoluteFilePath(fileName);
- async.setConcurrentCallData(copyAndCheck, sourcePath, destPath);
- };
- const auto onAssetCopyDone = [this, storage] {
- StorageData &storageData = *storage;
- ++storageData.doneCount;
- d->updateProgress(storageData.doneCount, storageData.assets.files.size());
- };
-
- const auto onAssetsCopyGroupDone = [this, storage](DoneWith result) {
- if (result != DoneWith::Success) {
- d->setLocalDownloadDir(storage->tempDir);
- qWarning() << "Asset copy failed";
- return;
- }
- d->m_temporaryDir.reset();
- };
-
- const Group recipe {
- storage,
- onGroupSetup(onSetup),
- NetworkQueryTask(onJsonDownloadSetup, onJsonDownloadDone),
- ConcurrentCallTask<DownloadableAssets>(onReadAssetsFileSetup, onReadAssetsFileDone, CallDoneIf::Success),
- Group {
- onGroupSetup(onSkipIfAllAssetsPresent),
- NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone),
- ConcurrentCallTask<void>(onUnzipSetup, onUnzipDone),
- For (downloadIterator) >> Do {
- parallelIdealThreadCountLimit,
- onGroupSetup(onAssetsDownloadGroupSetup),
- Group {
- assetStorage,
- NetworkQueryTask(onAssetDownloadSetup, onAssetDownloadDone),
- ConcurrentCallTask<void>(onAssetWriteSetup, onAssetWriteDone)
- }
- },
- For (copyIterator) >> Do {
- parallelIdealThreadCountLimit,
- onGroupSetup(onAssetsCopyGroupSetup),
- ConcurrentCallTask<void>(onAssetCopySetup, onAssetCopyDone, CallDoneIf::Success),
- onGroupDone(onAssetsCopyGroupDone)
- }
- }
- };
- d->m_taskTreeRunner.start(recipe, [this](TaskTree *) { emit started(); },
- [this](DoneWith result) { emit finished(result == DoneWith::Success); });
-}
-
-QUrl AssetDownloader::resolvedUrl(const QUrl &url) const
-{
- return url;
-}
-
-} // namespace Assets::Downloader
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/assetdownloader.h b/src/assets/downloader/assetdownloader.h
deleted file mode 100644
index 3c9351ceafe..00000000000
--- a/src/assets/downloader/assetdownloader.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) 2024 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
-
-#ifndef ASSETDOWNLOADER_H
-#define ASSETDOWNLOADER_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtCore/QObject>
-#include <QtCore/QUrl>
-
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-
-namespace Assets::Downloader {
-
-class AssetDownloaderPrivate;
-
-class AssetDownloader : public QObject
-{
- Q_OBJECT
-
- Q_PROPERTY(
- QUrl downloadBase
- READ downloadBase
- WRITE setDownloadBase
- NOTIFY downloadBaseChanged)
-
- Q_PROPERTY(
- QUrl preferredLocalDownloadDir
- READ preferredLocalDownloadDir
- WRITE setPreferredLocalDownloadDir
- NOTIFY preferredLocalDownloadDirChanged)
-
- Q_PROPERTY(
- QUrl offlineAssetsFilePath
- READ offlineAssetsFilePath
- WRITE setOfflineAssetsFilePath
- NOTIFY offlineAssetsFilePathChanged)
-
- Q_PROPERTY(
- QString jsonFileName
- READ jsonFileName
- WRITE setJsonFileName
- NOTIFY jsonFileNameChanged)
-
- Q_PROPERTY(
- QString zipFileName
- READ zipFileName
- WRITE setZipFileName
- NOTIFY zipFileNameChanged)
-
- Q_PROPERTY(
- QUrl localDownloadDir
- READ localDownloadDir
- NOTIFY localDownloadDirChanged)
-
-public:
- AssetDownloader(QObject *parent = nullptr);
- ~AssetDownloader();
-
- QUrl downloadBase() const;
- void setDownloadBase(const QUrl &downloadBase);
-
- QUrl preferredLocalDownloadDir() const;
- void setPreferredLocalDownloadDir(const QUrl &localDir);
-
- QUrl offlineAssetsFilePath() const;
- void setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath);
-
- QString jsonFileName() const;
- void setJsonFileName(const QString &jsonFileName);
-
- QString zipFileName() const;
- void setZipFileName(const QString &zipFileName);
-
- QUrl localDownloadDir() const;
-
- Q_INVOKABLE QStringList networkErrors() const;
- Q_INVOKABLE QStringList sslErrors() const;
-
-public Q_SLOTS:
- void start();
-
-protected:
- virtual QUrl resolvedUrl(const QUrl &url) const;
-
-Q_SIGNALS:
- void started();
- void finished(bool success);
- void progressChanged(int progressValue, int progressMaximum, const QString &progressText);
- void localDownloadDirChanged(const QUrl &url);
-
- void downloadBaseChanged(const QUrl &);
- void preferredLocalDownloadDirChanged(const QUrl &url);
- void offlineAssetsFilePathChanged(const QUrl &);
- void jsonFileNameChanged(const QString &);
- void zipFileNameChanged(const QString &);
-
-private:
- std::unique_ptr<AssetDownloaderPrivate> d;
-};
-
-} // namespace Assets::Downloader
-
-QT_END_NAMESPACE
-
-#endif // ASSETDOWNLOADER_H
diff --git a/src/assets/downloader/tasking/barrier.cpp b/src/assets/downloader/tasking/barrier.cpp
deleted file mode 100644
index c9e5992bc78..00000000000
--- a/src/assets/downloader/tasking/barrier.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "barrier.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-// That's cut down qtcassert.{c,h} to avoid the dependency.
-#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__))
-#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0)
-
-void Barrier::setLimit(int value)
-{
- QT_ASSERT(!isRunning(), return);
- QT_ASSERT(value > 0, return);
-
- m_limit = value;
-}
-
-void Barrier::start()
-{
- QT_ASSERT(!isRunning(), return);
- m_current = 0;
- m_result.reset();
-}
-
-void Barrier::advance()
-{
- // Calling advance on finished is OK
- QT_ASSERT(isRunning() || m_result, return);
- if (!isRunning()) // no-op
- return;
- ++m_current;
- if (m_current == m_limit)
- stopWithResult(DoneResult::Success);
-}
-
-void Barrier::stopWithResult(DoneResult result)
-{
- // Calling stopWithResult on finished is OK when the same success is passed
- QT_ASSERT(isRunning() || (m_result && *m_result == result), return);
- if (!isRunning()) // no-op
- return;
- m_current = -1;
- m_result = result;
- emit done(result);
-}
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/barrier.h b/src/assets/downloader/tasking/barrier.h
deleted file mode 100644
index d489d2722a4..00000000000
--- a/src/assets/downloader/tasking/barrier.h
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_BARRIER_H
-#define TASKING_BARRIER_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-
-#include "tasktree.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-class TASKING_EXPORT Barrier final : public QObject
-{
- Q_OBJECT
-
-public:
- void setLimit(int value);
- int limit() const { return m_limit; }
-
- void start();
- void advance(); // If limit reached, stops with true
- void stopWithResult(DoneResult result); // Ignores limit
-
- bool isRunning() const { return m_current >= 0; }
- int current() const { return m_current; }
- std::optional<DoneResult> result() const { return m_result; }
-
-Q_SIGNALS:
- void done(DoneResult success);
-
-private:
- std::optional<DoneResult> m_result = {};
- int m_limit = 1;
- int m_current = -1;
-};
-
-using BarrierTask = SimpleCustomTask<Barrier>;
-
-template <int Limit = 1>
-class SharedBarrier
-{
-public:
- static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more.");
- SharedBarrier() : m_barrier(new Barrier) {
- m_barrier->setLimit(Limit);
- m_barrier->start();
- }
- Barrier *barrier() const { return m_barrier.get(); }
-
-private:
- std::shared_ptr<Barrier> m_barrier;
-};
-
-template <int Limit = 1>
-using MultiBarrier = Storage<SharedBarrier<Limit>>;
-
-// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work.
-// Can't have one alias with default type in C++17, getting the following error:
-// alias template deduction only available with C++20.
-using SingleBarrier = MultiBarrier<1>;
-
-template <int Limit>
-ExecutableItem waitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier)
-{
- return BarrierTask([sharedBarrier](Barrier &barrier) {
- SharedBarrier<Limit> *activeBarrier = sharedBarrier.activeStorage();
- if (!activeBarrier) {
- qWarning("The barrier referenced from WaitForBarrier element "
- "is not reachable in the running tree. "
- "It is possible that no barrier was added to the tree, "
- "or the barrier is not reachable from where it is referenced. "
- "The WaitForBarrier task finishes with an error. ");
- return SetupResult::StopWithError;
- }
- Barrier *activeSharedBarrier = activeBarrier->barrier();
- const std::optional<DoneResult> result = activeSharedBarrier->result();
- if (result.has_value()) {
- return *result == DoneResult::Success ? SetupResult::StopWithSuccess
- : SetupResult::StopWithError;
- }
- QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult);
- return SetupResult::Continue;
- });
-}
-
-template <typename Signal>
-ExecutableItem signalAwaiter(const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal)
-{
- return BarrierTask([sender, signal](Barrier &barrier) {
- QObject::connect(sender, signal, &barrier, &Barrier::advance, Qt::SingleShotConnection);
- });
-}
-
-using BarrierKickerGetter = std::function<ExecutableItem(const SingleBarrier &)>;
-
-class TASKING_EXPORT When final
-{
-public:
- explicit When(const BarrierKickerGetter &kicker) : m_barrierKicker(kicker) {}
-
-private:
- TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem);
-
- BarrierKickerGetter m_barrierKicker;
-};
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_BARRIER_H
diff --git a/src/assets/downloader/tasking/concurrentcall.h b/src/assets/downloader/tasking/concurrentcall.h
deleted file mode 100644
index cc701297d13..00000000000
--- a/src/assets/downloader/tasking/concurrentcall.h
+++ /dev/null
@@ -1,119 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_CONCURRENTCALL_H
-#define TASKING_CONCURRENTCALL_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasktree.h"
-
-#include <QtConcurrent/QtConcurrent>
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-// This class introduces the dependency to Qt::Concurrent, otherwise Tasking namespace
-// is independent on Qt::Concurrent.
-// Possibly, it could be placed inside Qt::Concurrent library, as a wrapper around
-// QtConcurrent::run() call.
-
-template <typename ResultType>
-class ConcurrentCall
-{
- Q_DISABLE_COPY_MOVE(ConcurrentCall)
-
-public:
- ConcurrentCall() = default;
- template <typename Function, typename ...Args>
- void setConcurrentCallData(Function &&function, Args &&...args)
- {
- wrapConcurrent(std::forward<Function>(function), std::forward<Args>(args)...);
- }
- void setThreadPool(QThreadPool *pool) { m_threadPool = pool; }
- ResultType result() const
- {
- return m_future.resultCount() ? m_future.result() : ResultType();
- }
- QList<ResultType> results() const
- {
- return m_future.results();
- }
- QFuture<ResultType> future() const { return m_future; }
-
-private:
- template <typename Function, typename ...Args>
- void wrapConcurrent(Function &&function, Args &&...args)
- {
- m_startHandler = [this, function = std::forward<Function>(function), args...] {
- QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance();
- return QtConcurrent::run(threadPool, function, args...);
- };
- }
-
- template <typename Function, typename ...Args>
- void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args)
- {
- m_startHandler = [this, wrapper = std::forward<std::reference_wrapper<const Function>>(wrapper), args...] {
- QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance();
- return QtConcurrent::run(threadPool, std::forward<const Function>(wrapper.get()),
- args...);
- };
- }
-
- template <typename T>
- friend class ConcurrentCallTaskAdapter;
-
- std::function<QFuture<ResultType>()> m_startHandler;
- QThreadPool *m_threadPool = nullptr;
- QFuture<ResultType> m_future;
-};
-
-template <typename ResultType>
-class ConcurrentCallTaskAdapter : public TaskAdapter<ConcurrentCall<ResultType>>
-{
-public:
- ~ConcurrentCallTaskAdapter() {
- if (m_watcher) {
- m_watcher->cancel();
- m_watcher->waitForFinished();
- }
- }
-
- void start() final {
- if (!this->task()->m_startHandler) {
- emit this->done(DoneResult::Error); // TODO: Add runtime assert
- return;
- }
- m_watcher.reset(new QFutureWatcher<ResultType>);
- this->connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] {
- emit this->done(toDoneResult(!m_watcher->isCanceled()));
- m_watcher.release()->deleteLater();
- });
- this->task()->m_future = this->task()->m_startHandler();
- m_watcher->setFuture(this->task()->m_future);
- }
-
-private:
- std::unique_ptr<QFutureWatcher<ResultType>> m_watcher;
-};
-
-template <typename T>
-using ConcurrentCallTask = CustomTask<ConcurrentCallTaskAdapter<T>>;
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_CONCURRENTCALL_H
diff --git a/src/assets/downloader/tasking/conditional.cpp b/src/assets/downloader/tasking/conditional.cpp
deleted file mode 100644
index 24a03fb703e..00000000000
--- a/src/assets/downloader/tasking/conditional.cpp
+++ /dev/null
@@ -1,91 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "conditional.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-static Group conditionRecipe(const Storage<bool> &bodyExecutedStorage, const ConditionData &condition)
-{
- const auto onSetup = [bodyExecutedStorage] {
- return *bodyExecutedStorage ? SetupResult::StopWithSuccess : SetupResult::Continue;
- };
-
- const auto onBodyDone = [bodyExecutedStorage] { *bodyExecutedStorage = true; };
-
- const Group bodyTask { condition.m_body, onGroupDone(onBodyDone) };
-
- return {
- onGroupSetup(onSetup),
- condition.m_condition ? Group{ !*condition.m_condition || bodyTask } : bodyTask
- };
-}
-
-static ExecutableItem conditionsRecipe(const QList<ConditionData> &conditions)
-{
- Storage<bool> bodyExecutedStorage;
-
- GroupItems recipes;
- for (const ConditionData &condition : conditions)
- recipes << conditionRecipe(bodyExecutedStorage, condition);
-
- return Group { bodyExecutedStorage, recipes };
-}
-
-ThenItem::operator ExecutableItem() const
-{
- return conditionsRecipe(m_conditions);
-}
-
-ThenItem::ThenItem(const If &ifItem, const Then &thenItem)
- : m_conditions{{ifItem.m_condition, thenItem.m_body}}
-{}
-
-ThenItem::ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem)
- : m_conditions(elseIfItem.m_conditions)
-{
- m_conditions.append({elseIfItem.m_nextCondition, thenItem.m_body});
-}
-
-ElseItem::operator ExecutableItem() const
-{
- return conditionsRecipe(m_conditions);
-}
-
-ElseItem::ElseItem(const ThenItem &thenItem, const Else &elseItem)
- : m_conditions(thenItem.m_conditions)
-{
- m_conditions.append({{}, elseItem.m_body});
-}
-
-ElseIfItem::ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem)
- : m_conditions(thenItem.m_conditions)
- , m_nextCondition(elseIfItem.m_condition)
-{}
-
-ThenItem operator>>(const If &ifItem, const Then &thenItem)
-{
- return {ifItem, thenItem};
-}
-
-ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem)
-{
- return {elseIfItem, thenItem};
-}
-
-ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem)
-{
- return {thenItem, elseIfItem};
-}
-
-ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem)
-{
- return {thenItem, elseItem};
-}
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/conditional.h b/src/assets/downloader/tasking/conditional.h
deleted file mode 100644
index 52fdfd64cb7..00000000000
--- a/src/assets/downloader/tasking/conditional.h
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_CONDITIONAL_H
-#define TASKING_CONDITIONAL_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-
-#include "tasktree.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-class Then;
-class ThenItem;
-class ElseItem;
-class ElseIfItem;
-
-class TASKING_EXPORT If
-{
-public:
- explicit If(const ExecutableItem &condition) : m_condition(condition) {}
-
- template <typename Handler,
- std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true>
- explicit If(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {}
-
-private:
- TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem);
-
- friend class ThenItem;
- ExecutableItem m_condition;
-};
-
-class TASKING_EXPORT ElseIf
-{
-public:
- explicit ElseIf(const ExecutableItem &condition) : m_condition(condition) {}
-
- template <typename Handler,
- std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true>
- explicit ElseIf(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {}
-
-private:
- friend class ElseIfItem;
- ExecutableItem m_condition;
-};
-
-class TASKING_EXPORT Else
-{
-public:
- explicit Else(const GroupItems &children) : m_body({children}) {}
- explicit Else(std::initializer_list<GroupItem> children) : m_body({children}) {}
-
-private:
- friend class ElseItem;
- Group m_body;
-};
-
-class TASKING_EXPORT Then
-{
-public:
- explicit Then(const GroupItems &children) : m_body({children}) {}
- explicit Then(std::initializer_list<GroupItem> children) : m_body({children}) {}
-
-private:
- friend class ThenItem;
- Group m_body;
-};
-
-class ConditionData
-{
-public:
- std::optional<ExecutableItem> m_condition;
- Group m_body;
-};
-
-class ElseIfItem;
-
-class TASKING_EXPORT ThenItem
-{
-public:
- operator ExecutableItem() const;
-
-private:
- ThenItem(const If &ifItem, const Then &thenItem);
- ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem);
-
- TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem);
- TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem);
- TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem);
- TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem);
-
- friend class ElseItem;
- friend class ElseIfItem;
- QList<ConditionData> m_conditions;
-};
-
-class TASKING_EXPORT ElseItem
-{
-public:
- operator ExecutableItem() const;
-
-private:
- ElseItem(const ThenItem &thenItem, const Else &elseItem);
-
- TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem);
-
- QList<ConditionData> m_conditions;
-};
-
-class TASKING_EXPORT ElseIfItem
-{
-private:
- ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem);
-
- TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem);
- TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem);
-
- friend class ThenItem;
- QList<ConditionData> m_conditions;
- ExecutableItem m_nextCondition;
-};
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_CONDITIONAL_H
diff --git a/src/assets/downloader/tasking/networkquery.cpp b/src/assets/downloader/tasking/networkquery.cpp
deleted file mode 100644
index 3f15fed90e0..00000000000
--- a/src/assets/downloader/tasking/networkquery.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "networkquery.h"
-
-#include <QtNetwork/QNetworkAccessManager>
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-void NetworkQuery::start()
-{
- if (m_reply) {
- qWarning("The NetworkQuery is already running. Ignoring the call to start().");
- return;
- }
- if (!m_manager) {
- qWarning("Can't start the NetworkQuery without the QNetworkAccessManager. "
- "Stopping with an error.");
- emit done(DoneResult::Error);
- return;
- }
- switch (m_operation) {
- case NetworkOperation::Get:
- m_reply.reset(m_manager->get(m_request));
- break;
- case NetworkOperation::Put:
- m_reply.reset(m_manager->put(m_request, m_writeData));
- break;
- case NetworkOperation::Post:
- m_reply.reset(m_manager->post(m_request, m_writeData));
- break;
- case NetworkOperation::Delete:
- m_reply.reset(m_manager->deleteResource(m_request));
- break;
- }
-
- connect(m_reply.get(), &QNetworkReply::downloadProgress, this, &NetworkQuery::downloadProgress);
-#if QT_CONFIG(ssl)
- connect(m_reply.get(), &QNetworkReply::sslErrors, this, &NetworkQuery::sslErrors);
-#endif
- connect(m_reply.get(), &QNetworkReply::finished, this, [this] {
- disconnect(m_reply.get(), &QNetworkReply::finished, this, nullptr);
- emit done(toDoneResult(m_reply->error() == QNetworkReply::NoError));
- m_reply.release()->deleteLater();
- });
- if (m_reply->isRunning())
- emit started();
-}
-
-NetworkQuery::~NetworkQuery()
-{
- if (m_reply) {
- disconnect(m_reply.get(), nullptr, this, nullptr);
- m_reply->abort();
- }
-}
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/networkquery.h b/src/assets/downloader/tasking/networkquery.h
deleted file mode 100644
index 2733fef8352..00000000000
--- a/src/assets/downloader/tasking/networkquery.h
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_NETWORKQUERY_H
-#define TASKING_NETWORKQUERY_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-
-#include "tasktree.h"
-
-#include <QtNetwork/QNetworkReply>
-#include <QtNetwork/QNetworkRequest>
-
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-class QNetworkAccessManager;
-
-namespace Tasking {
-
-// This class introduces the dependency to Qt::Network, otherwise Tasking namespace
-// is independent on Qt::Network.
-// Possibly, it could be placed inside Qt::Network library, as a wrapper around QNetworkReply.
-
-enum class NetworkOperation { Get, Put, Post, Delete };
-
-class TASKING_EXPORT NetworkQuery final : public QObject
-{
- Q_OBJECT
-
-public:
- ~NetworkQuery();
- void setRequest(const QNetworkRequest &request) { m_request = request; }
- void setOperation(NetworkOperation operation) { m_operation = operation; }
- void setWriteData(const QByteArray &data) { m_writeData = data; }
- void setNetworkAccessManager(QNetworkAccessManager *manager) { m_manager = manager; }
- QNetworkReply *reply() const { return m_reply.get(); }
- void start();
-
-Q_SIGNALS:
- void started();
- void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
-#if QT_CONFIG(ssl)
- void sslErrors(const QList<QSslError> &errors);
-#endif
- void done(DoneResult result);
-
-private:
- QNetworkRequest m_request;
- NetworkOperation m_operation = NetworkOperation::Get;
- QByteArray m_writeData; // Used by Put and Post
- QNetworkAccessManager *m_manager = nullptr;
- std::unique_ptr<QNetworkReply> m_reply;
-};
-
-using NetworkQueryTask = SimpleCustomTask<NetworkQuery>;
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_NETWORKQUERY_H
diff --git a/src/assets/downloader/tasking/qprocesstask.cpp b/src/assets/downloader/tasking/qprocesstask.cpp
deleted file mode 100644
index a4696ebbbfb..00000000000
--- a/src/assets/downloader/tasking/qprocesstask.cpp
+++ /dev/null
@@ -1,280 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "qprocesstask.h"
-
-#include <QtCore/QCoreApplication>
-#include <QtCore/QDebug>
-#include <QtCore/QElapsedTimer>
-#include <QtCore/QMutex>
-#include <QtCore/QThread>
-#include <QtCore/QTimer>
-#include <QtCore/QWaitCondition>
-
-QT_BEGIN_NAMESPACE
-
-#if QT_CONFIG(process)
-
-namespace Tasking {
-
-class ProcessReaperPrivate;
-
-class ProcessReaper final
-{
-public:
- static void reap(QProcess *process, int timeoutMs = 500);
- ProcessReaper();
- ~ProcessReaper();
-
-private:
- static ProcessReaper *instance();
-
- QThread m_thread;
- ProcessReaperPrivate *m_private;
-};
-
-static const int s_timeoutThreshold = 10000; // 10 seconds
-
-static QString execWithArguments(QProcess *process)
-{
- QStringList commandLine;
- commandLine.append(process->program());
- commandLine.append(process->arguments());
- return commandLine.join(QChar::Space);
-}
-
-struct ReaperSetup
-{
- QProcess *m_process = nullptr;
- int m_timeoutMs;
-};
-
-class Reaper : public QObject
-{
- Q_OBJECT
-
-public:
- Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {}
-
- void reap()
- {
- m_timer.start();
- connect(m_reaperSetup.m_process, &QProcess::finished, this, &Reaper::handleFinished);
- if (emitFinished())
- return;
- terminate();
- }
-
-Q_SIGNALS:
- void finished();
-
-private:
- void terminate()
- {
- m_reaperSetup.m_process->terminate();
- QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleTerminateTimeout);
- }
-
- void kill() { m_reaperSetup.m_process->kill(); }
-
- bool emitFinished()
- {
- if (m_reaperSetup.m_process->state() != QProcess::NotRunning)
- return false;
-
- if (!m_finished) {
- const int timeout = m_timer.elapsed();
- if (timeout > s_timeoutThreshold) {
- qWarning() << "Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process)
- << "in" << (timeout / 1000.0) << "seconds.";
- }
-
- m_finished = true;
- emit finished();
- }
- return true;
- }
-
- void handleFinished()
- {
- if (emitFinished())
- return;
- qWarning() << "Finished process still running...";
- // In case the process is still running - wait until it has finished
- QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleFinished);
- }
-
- void handleTerminateTimeout()
- {
- if (emitFinished())
- return;
- kill();
- }
-
- bool m_finished = false;
- QElapsedTimer m_timer;
- const ReaperSetup m_reaperSetup;
-};
-
-class ProcessReaperPrivate : public QObject
-{
- Q_OBJECT
-
-public:
- // Called from non-reaper's thread
- void scheduleReap(const ReaperSetup &reaperSetup)
- {
- if (QThread::currentThread() == thread())
- qWarning() << "Can't schedule reap from the reaper internal thread.";
-
- QMutexLocker locker(&m_mutex);
- m_reaperSetupList.append(reaperSetup);
- QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush);
- }
-
- // Called from non-reaper's thread
- void waitForFinished()
- {
- if (QThread::currentThread() == thread())
- qWarning() << "Can't wait for finished from the reaper internal thread.";
-
- QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush,
- Qt::BlockingQueuedConnection);
- QMutexLocker locker(&m_mutex);
- if (m_reaperList.isEmpty())
- return;
-
- m_waitCondition.wait(&m_mutex);
- }
-
-private:
- // All the private methods are called from the reaper's thread
- QList<ReaperSetup> takeReaperSetupList()
- {
- QMutexLocker locker(&m_mutex);
- return std::exchange(m_reaperSetupList, {});
- }
-
- void flush()
- {
- while (true) {
- const QList<ReaperSetup> reaperSetupList = takeReaperSetupList();
- if (reaperSetupList.isEmpty())
- return;
- for (const ReaperSetup &reaperSetup : reaperSetupList)
- reap(reaperSetup);
- }
- }
-
- void reap(const ReaperSetup &reaperSetup)
- {
- Reaper *reaper = new Reaper(reaperSetup);
- connect(reaper, &Reaper::finished, this, [this, reaper, process = reaperSetup.m_process] {
- QMutexLocker locker(&m_mutex);
- const bool isRemoved = m_reaperList.removeOne(reaper);
- if (!isRemoved)
- qWarning() << "Reaper list doesn't contain the finished process.";
-
- delete reaper;
- delete process;
- if (m_reaperList.isEmpty())
- m_waitCondition.wakeOne();
- }, Qt::QueuedConnection);
-
- {
- QMutexLocker locker(&m_mutex);
- m_reaperList.append(reaper);
- }
-
- reaper->reap();
- }
-
- QMutex m_mutex;
- QWaitCondition m_waitCondition;
- QList<ReaperSetup> m_reaperSetupList;
- QList<Reaper *> m_reaperList;
-};
-
-static ProcessReaper *s_instance = nullptr;
-static QMutex s_instanceMutex;
-
-// Call me with s_instanceMutex locked.
-ProcessReaper *ProcessReaper::instance()
-{
- if (!s_instance)
- s_instance = new ProcessReaper;
- return s_instance;
-}
-
-ProcessReaper::ProcessReaper()
- : m_private(new ProcessReaperPrivate)
-{
- m_private->moveToThread(&m_thread);
- QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater);
- m_thread.start();
- m_thread.moveToThread(qApp->thread());
-}
-
-ProcessReaper::~ProcessReaper()
-{
- if (!QThread::isMainThread())
- qWarning() << "Destructing process reaper from non-main thread.";
-
- instance()->m_private->waitForFinished();
- m_thread.quit();
- m_thread.wait();
-}
-
-void ProcessReaper::reap(QProcess *process, int timeoutMs)
-{
- if (!process)
- return;
-
- if (QThread::currentThread() != process->thread()) {
- qWarning() << "Can't reap process from non-process's thread.";
- return;
- }
-
- process->disconnect();
- if (process->state() == QProcess::NotRunning) {
- delete process;
- return;
- }
-
- // Neither can move object with a parent into a different thread
- // nor reaping the process with a parent makes any sense.
- process->setParent(nullptr);
-
- QMutexLocker locker(&s_instanceMutex);
- ProcessReaperPrivate *priv = instance()->m_private;
-
- process->moveToThread(priv->thread());
- ReaperSetup reaperSetup {process, timeoutMs};
- priv->scheduleReap(reaperSetup);
-}
-
-void QProcessDeleter::deleteAll()
-{
- QMutexLocker locker(&s_instanceMutex);
- delete s_instance;
- s_instance = nullptr;
-}
-
-void QProcessDeleter::operator()(QProcess *process)
-{
- ProcessReaper::reap(process);
-}
-
-} // namespace Tasking
-
-#endif // QT_CONFIG(process)
-
-QT_END_NAMESPACE
-
-#if QT_CONFIG(process)
-
-#include "qprocesstask.moc"
-
-#endif // QT_CONFIG(process)
-
diff --git a/src/assets/downloader/tasking/qprocesstask.h b/src/assets/downloader/tasking/qprocesstask.h
deleted file mode 100644
index 6f2ca4a18e2..00000000000
--- a/src/assets/downloader/tasking/qprocesstask.h
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_QPROCESSTASK_H
-#define TASKING_QPROCESSTASK_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-
-#include "tasktree.h"
-
-#include <QtCore/QProcess>
-
-QT_BEGIN_NAMESPACE
-
-#if QT_CONFIG(process)
-
-namespace Tasking {
-
-// Deleting a running QProcess may block the caller thread up to 30 seconds and issue warnings.
-// To avoid these issues we move the running QProcess into a separate thread
-// managed by the internal ProcessReaper, instead of deleting it immediately.
-// Inside the ProcessReaper's thread we try to finish the process in a most gentle way:
-// we call QProcess::terminate() with 500 ms timeout, and if the process is still running
-// after this timeout passed, we call QProcess::kill() and wait for the process to finish.
-// All these handlings are done is a separate thread, so the main thread doesn't block at all
-// when the QProcessTask is destructed.
-// Finally, on application quit, QProcessDeleter::deleteAll() should be called in order
-// to synchronize all the processes being still potentially reaped in a separate thread.
-// The call to QProcessDeleter::deleteAll() is blocking in case some processes
-// are still being reaped.
-// This strategy seems most sensible, since when passing the running QProcess into the
-// ProcessReaper we don't block immediately, but postpone the possible (not certain) block
-// until the end of an application.
-// In this way we terminate the running processes in the most safe way and keep the main thread
-// responsive. That's a common case when the running application wants to terminate the QProcess
-// immediately (e.g. on Cancel button pressed), without keeping and managing the handle
-// to the still running QProcess.
-
-// The implementation of the internal reaper is inspired by the Utils::ProcessReaper taken
-// from the QtCreator codebase.
-
-class TASKING_EXPORT QProcessDeleter
-{
-public:
- // Blocking, should be called after all QProcessAdapter instances are deleted.
- static void deleteAll();
- void operator()(QProcess *process);
-};
-
-class TASKING_EXPORT QProcessAdapter : public TaskAdapter<QProcess, QProcessDeleter>
-{
-private:
- void start() final {
- connect(task(), &QProcess::finished, this, [this] {
- const bool success = task()->exitStatus() == QProcess::NormalExit
- && task()->error() == QProcess::UnknownError
- && task()->exitCode() == 0;
- Q_EMIT done(toDoneResult(success));
- });
- connect(task(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
- if (error != QProcess::FailedToStart)
- return;
- Q_EMIT done(DoneResult::Error);
- });
- task()->start();
- }
-};
-
-using QProcessTask = CustomTask<QProcessAdapter>;
-
-} // namespace Tasking
-
-#endif // QT_CONFIG(process)
-
-QT_END_NAMESPACE
-
-#endif // TASKING_QPROCESSTASK_H
diff --git a/src/assets/downloader/tasking/tasking_global.h b/src/assets/downloader/tasking/tasking_global.h
deleted file mode 100644
index 57f0b7fefef..00000000000
--- a/src/assets/downloader/tasking/tasking_global.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (C) 2024 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
-
-#ifndef TASKING_GLOBAL_H
-#define TASKING_GLOBAL_H
-
-#include <QtCore/qglobal.h>
-
-QT_BEGIN_NAMESPACE
-
-// #if defined(QT_SHARED) || !defined(QT_STATIC)
-// # if defined(TASKING_LIBRARY)
-// # define TASKING_EXPORT Q_DECL_EXPORT
-// # else
-// # define TASKING_EXPORT Q_DECL_IMPORT
-// # endif
-// #else
-// # define TASKING_EXPORT
-// #endif
-
-#define TASKING_EXPORT
-
-QT_END_NAMESPACE
-
-#endif // TASKING_GLOBAL_H
diff --git a/src/assets/downloader/tasking/tasktree.cpp b/src/assets/downloader/tasking/tasktree.cpp
deleted file mode 100644
index 37064a3e714..00000000000
--- a/src/assets/downloader/tasking/tasktree.cpp
+++ /dev/null
@@ -1,3701 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "tasktree.h"
-
-#include "barrier.h"
-#include "conditional.h"
-
-#include <QtCore/QDebug>
-#include <QtCore/QEventLoop>
-#include <QtCore/QFutureWatcher>
-#include <QtCore/QHash>
-#include <QtCore/QMetaEnum>
-#include <QtCore/QMutex>
-#include <QtCore/QPointer>
-#include <QtCore/QPromise>
-#include <QtCore/QSet>
-#include <QtCore/QTime>
-#include <QtCore/QTimer>
-
-using namespace Qt::StringLiterals;
-using namespace std::chrono;
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-// That's cut down qtcassert.{c,h} to avoid the dependency.
-#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__))
-#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0)
-#define QT_CHECK(cond) if (cond) {} else { QT_STRING(#cond); } do {} while (0)
-
-class Guard
-{
- Q_DISABLE_COPY(Guard)
-public:
- Guard() = default;
- ~Guard() { QT_CHECK(m_lockCount == 0); }
- bool isLocked() const { return m_lockCount; }
-private:
- int m_lockCount = 0;
- friend class GuardLocker;
-};
-
-class GuardLocker
-{
- Q_DISABLE_COPY(GuardLocker)
-public:
- GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; }
- ~GuardLocker() { --m_guard.m_lockCount; }
-private:
- Guard &m_guard;
-};
-
-/*!
- \module TaskingSolution
- \title Tasking Solution
- \ingroup solutions-modules
- \brief Contains a general purpose Tasking solution.
-
- The Tasking solution depends on Qt only, and doesn't depend on any \QC specific code.
-*/
-
-/*!
- \namespace Tasking
- \inmodule TaskingSolution
- \brief The Tasking namespace encloses all classes and global functions of the Tasking solution.
-*/
-
-/*!
- \class Tasking::TaskInterface
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief TaskInterface is the abstract base class for implementing custom task adapters.
- \reentrant
-
- To implement a custom task adapter, derive your adapter from the
- \c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys
- the custom task instance and associates the adapter with a given \c Task type.
-*/
-
-/*!
- \fn virtual void TaskInterface::start()
-
- This method is called by the running TaskTree for starting the \c Task instance.
- Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the
- associated task.
-
- Use TaskAdapter::task() to access the associated \c Task instance.
-
- \sa done(), TaskAdapter::task()
-*/
-
-/*!
- \fn void TaskInterface::done(DoneResult result)
-
- Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished.
- Pass DoneResult::Success as a \a result argument when the task finishes with success;
- otherwise, when an error occurs, pass DoneResult::Error.
-*/
-
-/*!
- \class Tasking::TaskAdapter
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief A class template for implementing custom task adapters.
- \reentrant
-
- The TaskAdapter class template is responsible for creating a task of the \c Task type,
- starting it, and reporting success or an error when the task is finished.
- It also associates the adapter with a given \c Task type.
-
- Reimplement this class with the actual \c Task type to adapt the task's interface
- into the general TaskTree's interface for managing the \c Task instances.
-
- Each subclass needs to provide a public default constructor,
- implement the start() method, and emit the done() signal when the task is finished.
- Use task() to access the associated \c Task instance.
-
- To use your task adapter inside the task tree, create an alias to the
- Tasking::CustomTask template passing your task adapter as a template parameter:
- \code
- // Defines actual worker
- class Worker {...};
-
- // Adapts Worker's interface to work with task tree
- class WorkerTaskAdapter : public TaskAdapter<Worker> {...};
-
- // Defines WorkerTask as a new custom task type to be placed inside Group items
- using WorkerTask = CustomTask<WorkerTaskAdapter>;
- \endcode
-
- Optionally, you may pass a custom \c Deleter for the associated \c Task
- as a second template parameter of your \c TaskAdapter subclass.
- When the \c Deleter parameter is omitted, the \c std::default_delete<Task> is used by default.
- The custom \c Deleter is useful when the destructor of the running \c Task
- may potentially block the caller thread. Instead of blocking, the custom deleter may move
- the running task into a separate thread and implement the blocking destruction there.
- In this way, the fast destruction (seen from the caller thread) of the running task
- with a blocking destructor may be achieved.
-
- For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
-
- \sa start(), done(), task()
-*/
-
-/*!
- \fn template <typename Task, typename Deleter = std::default_delete<Task>> TaskAdapter<Task, Deleter>::TaskAdapter<Task, Deleter>()
-
- Creates a task adapter for the given \c Task type.
-
- Internally, it creates an instance of \c Task, which is accessible via the task() method.
- The optionally provided \c Deleter is used instead of the \c Task destructor.
- When \c Deleter is omitted, the \c std::default_delete<Task> is used by default.
-
- \sa task()
-*/
-
-/*!
- \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task()
-
- Returns the pointer to the associated \c Task instance.
-*/
-
-/*!
- \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() const
- \overload
-
- Returns the \c const pointer to the associated \c Task instance.
-*/
-
-/*!
- \class Tasking::Storage
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief A class template for custom data exchange in the running task tree.
- \reentrant
-
- The Storage class template is responsible for dynamically creating and destructing objects
- of the custom \c StorageStruct type. The creation and destruction are managed by the
- running task tree. If a Storage object is placed inside a \l {Tasking::Group} {Group} element,
- the running task tree creates the \c StorageStruct object when the group is started and before
- the group's setup handler is called. Later, whenever any handler inside this group is called,
- the task tree activates the previously created instance of the \c StorageStruct object.
- This includes all tasks' and groups' setup and done handlers inside the group where the
- Storage object was placed, also within the nested groups.
- When a copy of the Storage object is passed to the handler via the lambda capture,
- the handler may access the instance activated by the running task tree via the
- \l {Tasking::Storage::operator->()} {operator->()},
- \l {Tasking::Storage::operator*()} {operator*()}, or activeStorage() method.
- If two handlers capture the same Storage object, one of them may store a custom data there,
- and the other may read it afterwards.
- When the group is finished, the previously created instance of the \c StorageStruct
- object is destroyed after the group's done handler is called.
-
- An example of data exchange between tasks:
-
- \code
- const Storage<QString> storage;
-
- const auto onFirstDone = [storage](const Task &task) {
- // Assings QString, taken from the first task result, to the active QString instance
- // of the Storage object.
- *storage = task.getResultAsString();
- };
-
- const auto onSecondSetup = [storage](Task &task) {
- // Reads QString from the active QString instance of the Storage object and use it to
- // configure the second task before start.
- task.configureWithString(*storage);
- };
-
- const Group root {
- // The running task tree creates QString instance when root in entered
- storage,
- // The done handler of the first task stores the QString in the storage
- TaskItem(..., onFirstDone),
- // The setup handler of the second task reads the QString from the storage
- TaskItem(onSecondSetup, ...)
- };
- \endcode
-
- Since the root group executes its tasks sequentially, the \c onFirstDone handler
- is always called before the \c onSecondSetup handler. This means that the QString data,
- read from the \c storage inside the \c onSecondSetup handler's body,
- has already been set by the \c onFirstDone handler.
- You can always rely on it in \l {Tasking::sequential} {sequential} execution mode.
-
- The Storage internals are shared between all of its copies. That is why the copies of the
- Storage object inside the handlers' lambda captures still refer to the same Storage instance.
- You may place multiple Storage objects inside one \l {Tasking::Group} {Group} element,
- provided that they do not include copies of the same Storage object.
- Otherwise, an assert is triggered at runtime that includes an error message.
- However, you can place copies of the same Storage object in different
- \l {Tasking::Group} {Group} elements of the same recipe. In this case, the running task
- tree will create multiple instances of the \c StorageStruct objects (one for each copy)
- and storage shadowing will take place. Storage shadowing works in a similar way
- to C++ variable shadowing inside the nested blocks of code:
-
- \code
- Storage<QString> storage;
-
- const Group root {
- storage, // Top copy, 1st instance of StorageStruct
- onGroupSetup([storage] { ... }), // Top copy is active
- Group {
- storage, // Nested copy, 2nd instance of StorageStruct,
- // shadows Top copy
- onGroupSetup([storage] { ... }), // Nested copy is active
- },
- Group {
- onGroupSetup([storage] { ... }), // Top copy is active
- }
- };
- \endcode
-
- The Storage objects may also be used for passing the initial data to the executed task tree,
- and for reading the final data out of the task tree before it finishes.
- To do this, use \l {TaskTree::onStorageSetup()} {onStorageSetup()} or
- \l {TaskTree::onStorageDone()} {onStorageDone()}, respectively.
-
- \note If you use an unreachable Storage object inside the handler,
- because you forgot to place the storage in the recipe,
- or placed it, but not in any handler's ancestor group,
- you may expect a crash, preceded by the following message:
- \e {The referenced storage is not reachable in the running tree.
- A nullptr will be returned which might lead to a crash in the calling code.
- It is possible that no storage was added to the tree,
- or the storage is not reachable from where it is referenced.}
-*/
-
-/*!
- \fn template <typename StorageStruct> Storage<StorageStruct>::Storage<StorageStruct>()
-
- Creates a storage for the given \c StorageStruct type.
-
- \note All copies of \c this object are considered to be the same Storage instance.
-*/
-
-/*!
- \fn template <typename StorageStruct> StorageStruct &Storage<StorageStruct>::operator*() const noexcept
-
- Returns a \e reference to the active \c StorageStruct object, created by the running task tree.
- Use this function only from inside the handler body of any GroupItem element placed
- in the recipe, otherwise you may expect a crash.
- Make sure that Storage is placed in any group ancestor of the handler's group item.
-
- \note The returned reference is valid as long as the group that created this instance
- is still running.
-
- \sa activeStorage(), operator->()
-*/
-
-/*!
- \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::operator->() const noexcept
-
- Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
- Use this function only from inside the handler body of any GroupItem element placed
- in the recipe, otherwise you may expect a crash.
- Make sure that Storage is placed in any group ancestor of the handler's group item.
-
- \note The returned pointer is valid as long as the group that created this instance
- is still running.
-
- \sa activeStorage(), operator*()
-*/
-
-/*!
- \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::activeStorage() const
-
- Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
- Use this function only from inside the handler body of any GroupItem element placed
- in the recipe, otherwise you may expect a crash.
- Make sure that Storage is placed in any group ancestor of the handler's group item.
-
- \note The returned pointer is valid as long as the group that created this instance
- is still running.
-
- \sa operator->(), operator*()
-*/
-
-/*!
- \typealias Tasking::GroupItems
-
- Type alias for QList<GroupItem>.
-*/
-
-/*!
- \class Tasking::GroupItem
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief GroupItem represents the basic element that may be a part of any Group.
- \reentrant
-
- GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}.
- It encapsulates the functionality provided by any GroupItem's subclass.
- It is a value type and it is safe to copy the GroupItem instance,
- even when it is originally created via the subclass' constructor.
-
- There are four main kinds of GroupItem:
- \table
- \header
- \li GroupItem Kind
- \li Brief Description
- \row
- \li \l CustomTask
- \li Defines asynchronous task type and task's start, done, and error handlers.
- Aliased with a unique task name, such as, \c ConcurrentCallTask<ResultType>
- or \c NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree.
- \row
- \li \l {Tasking::Group} {Group}
- \li A container for other group items. Since the group is of the GroupItem type,
- it's possible to nest it inside another group. The group is seen by its parent
- as a single asynchronous task.
- \row
- \li GroupItem containing \l {Tasking::Storage} {Storage}
- \li Enables the child tasks of a group to exchange data. When GroupItem containing
- \l {Tasking::Storage} {Storage} is placed inside a group, the task tree instantiates
- the storage's data object just before the group is entered,
- and destroys it just after the group is left.
- \row
- \li Other group control items
- \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or
- \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior.
- The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()} or
- \l {Tasking::onGroupDone()} {onGroupDone()} define custom handlers called when
- the group starts or ends execution.
- \endtable
-*/
-
-/*!
- \fn template <typename StorageStruct> GroupItem::GroupItem(const Storage<StorageStruct> &storage)
-
- Constructs a \c GroupItem element holding the \a storage object.
-
- When the \l {Tasking::Group} {Group} element containing \e this GroupItem is entered
- by the running task tree, an instance of the \c StorageStruct is created dynamically.
-
- When that group is about to be left after its execution, the previously instantiated
- \c StorageStruct is deleted.
-
- The dynamically created instance of \c StorageStruct is accessible from inside any
- handler body of the parent \l {Tasking::Group} {Group} element,
- including nested groups and its tasks, via the
- \l {Tasking::Storage::operator->()} {Storage::operator->()},
- \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
-
- \sa {Tasking::Storage} {Storage}
-*/
-
-/*!
- \fn GroupItem::GroupItem(const GroupItems &items)
-
- Constructs a \c GroupItem element with a given list of \a items.
-
- When this \c GroupItem element is parsed by the TaskTree, it is simply replaced with
- its \a items.
-
- This constructor is useful when constructing a \l {Tasking::Group} {Group} element with
- lists of \c GroupItem elements:
-
- \code
- static QList<GroupItems> getItems();
-
- ...
-
- const Group root {
- parallel,
- finishAllAndSuccess,
- getItems(), // OK, getItems() list is wrapped into a single GroupItem element
- onGroupSetup(...),
- onGroupDone(...)
- };
- \endcode
-
- If you want to create a subtree, use \l {Tasking::Group} {Group} instead.
-
- \note Don't confuse this \c GroupItem with the \l {Tasking::Group} {Group} element, as
- \l {Tasking::Group} {Group} keeps its children nested
- after being parsed by the task tree, while this \c GroupItem does not.
-
- \sa {Tasking::Group} {Group}
-*/
-
-/*!
- \fn Tasking::GroupItem(std::initializer_list<GroupItem> items)
- \overload
- \sa GroupItem(const GroupItems &items)
-*/
-
-/*!
- \class Tasking::Group
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief Group represents the basic element for composing declarative recipes describing
- how to execute and handle a nested tree of asynchronous tasks.
- \reentrant
-
- Group is a container for other group items. It encloses child tasks into one unit,
- which is seen by the group's parent as a single, asynchronous task.
- Since Group is of the GroupItem type, it may also be a child of Group.
-
- Insert child tasks into the group by using aliased custom task names, such as,
- \c ConcurrentCallTask<ResultType> or \c NetworkQueryTask:
-
- \code
- const Group group {
- NetworkQueryTask(...),
- ConcurrentCallTask<int>(...)
- };
- \endcode
-
- The group's behavior may be customized by inserting the items returned by
- \l {Tasking::parallelLimit()} {parallelLimit()} or
- \l {Tasking::workflowPolicy()} {workflowPolicy()} functions:
-
- \code
- const Group group {
- parallel,
- continueOnError,
- NetworkQueryTask(...),
- NetworkQueryTask(...)
- };
- \endcode
-
- The group may contain nested groups:
-
- \code
- const Group group {
- finishAllAndSuccess,
- NetworkQueryTask(...),
- Group {
- NetworkQueryTask(...),
- Group {
- parallel,
- NetworkQueryTask(...),
- NetworkQueryTask(...),
- }
- ConcurrentCallTask<QString>(...)
- }
- };
- \endcode
-
- The group may dynamically instantiate a custom storage structure, which may be used for
- inter-task data exchange:
-
- \code
- struct MyCustomStruct { QByteArray data; };
-
- Storage<MyCustomStruct> storage;
-
- const auto onFirstSetup = [](NetworkQuery &task) { ... };
- const auto onFirstDone = [storage](const NetworkQuery &task) {
- // storage-> gives a pointer to MyCustomStruct instance,
- // created dynamically by the running task tree.
- storage->data = task.reply()->readAll();
- };
- const auto onSecondSetup = [storage](ConcurrentCall<QImage> &task) {
- // storage-> gives a pointer to MyCustomStruct. Since the group is sequential,
- // the stored MyCustomStruct was already updated inside the onFirstDone handler.
- const QByteArray storedData = storage->data;
- };
-
- const Group group {
- // When the group is entered by a running task tree, it creates MyCustomStruct
- // instance dynamically. It is later accessible from all handlers via
- // the *storage or storage-> operators.
- sequential,
- storage,
- NetworkQueryTask(onFirstSetup, onFirstDone, CallDoneIf::Success),
- ConcurrentCallTask<QImage>(onSecondSetup)
- };
- \endcode
-*/
-
-/*!
- \fn Group::Group(const GroupItems &children)
-
- Constructs a group with a given list of \a children.
-
- This constructor is useful when the child items of the group are not known at compile time,
- but later, at runtime:
-
- \code
- const QStringList sourceList = ...;
-
- GroupItems groupItems { parallel };
-
- for (const QString &source : sourceList) {
- const NetworkQueryTask task(...); // use source for setup handler
- groupItems << task;
- }
-
- const Group group(groupItems);
- \endcode
-*/
-
-/*!
- \fn Group::Group(std::initializer_list<GroupItem> children)
-
- Constructs a group from \c std::initializer_list given by \a children.
-
- This constructor is useful when all child items of the group are known at compile time:
-
- \code
- const Group group {
- finishAllAndSuccess,
- NetworkQueryTask(...),
- Group {
- NetworkQueryTask(...),
- Group {
- parallel,
- NetworkQueryTask(...),
- NetworkQueryTask(...),
- }
- ConcurrentCallTask<QString>(...)
- }
- };
- \endcode
-*/
-
-/*!
- \class Tasking::Sync
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief Synchronously executes a custom handler between other tasks.
- \reentrant
-
- \c Sync is useful when you want to execute an additional handler between other tasks.
- \c Sync is seen by its parent \l {Tasking::Group} {Group} as any other task.
- Avoid long-running execution of the \c Sync's handler body, since it is executed
- synchronously from the caller thread. If that is unavoidable, consider using
- \c ConcurrentCallTask instead.
-*/
-
-/*!
- \fn template <typename Handler> Sync::Sync(Handler &&handler)
-
- Constructs an element that executes a passed \a handler synchronously.
- The \c Handler is of the \c std::function<DoneResult()> type.
- The DoneResult value, returned by the \a handler, is considered during parent group's
- \l {workflowPolicy} {workflow policy} resolution.
- Optionally, the shortened form of \c std::function<void()> is also accepted.
- In this case, it's assumed that the return value is DoneResult::Success.
-
- The passed \a handler executes synchronously from the caller thread, so avoid a long-running
- execution of the handler body. Otherwise, consider using \c ConcurrentCallTask.
-
- \note The \c Sync element is not counted as a task when reporting task tree progress,
- and is not included in TaskTree::taskCount() or TaskTree::progressMaximum().
-*/
-
-/*!
- \class Tasking::CustomTask
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief A class template used for declaring custom task items and defining their setup
- and done handlers.
- \reentrant
-
- Describes custom task items within task tree recipes.
-
- Custom task names are aliased with unique names using the \c CustomTask template
- with a given TaskAdapter subclass as a template parameter.
- For example, \c ConcurrentCallTask<T> is an alias to the \c CustomTask that is defined
- to work with \c ConcurrentCall<T> as an associated task class.
- The following table contains example custom tasks and their associated task classes:
-
- \table
- \header
- \li Aliased Task Name (Tasking Namespace)
- \li Associated Task Class
- \li Brief Description
- \row
- \li ConcurrentCallTask<ReturnType>
- \li ConcurrentCall<ReturnType>
- \li Starts an asynchronous task. Runs in a separate thread.
- \row
- \li NetworkQueryTask
- \li NetworkQuery
- \li Sends a network query.
- \row
- \li TaskTreeTask
- \li TaskTree
- \li Starts a nested task tree.
- \row
- \li TimeoutTask
- \li \c std::chrono::milliseconds
- \li Starts a timer.
- \row
- \li WaitForBarrierTask
- \li MultiBarrier<Limit>
- \li Starts an asynchronous task waiting for the barrier to pass.
- \endtable
-*/
-
-/*!
- \typealias Tasking::CustomTask::Task
-
- Type alias for the task type associated with the custom task's \c Adapter.
-*/
-
-/*!
- \typealias Tasking::CustomTask::Deleter
-
- Type alias for the task's type deleter associated with the custom task's \c Adapter.
-*/
-
-/*!
- \typealias Tasking::CustomTask::TaskSetupHandler
-
- Type alias for \c std::function<SetupResult(Task &)>.
-
- The \c TaskSetupHandler is an optional argument of a custom task element's constructor.
- Any function with the above signature, when passed as a task setup handler,
- will be called by the running task tree after the task is created and before it is started.
-
- Inside the body of the handler, you may configure the task according to your needs.
- The additional parameters, including storages, may be passed to the handler
- via the lambda capture.
- You can decide dynamically whether the task should be started or skipped with
- success or an error.
-
- \note Do not start the task inside the start handler by yourself. Leave it for TaskTree,
- otherwise the behavior is undefined.
-
- The return value of the handler instructs the running task tree on how to proceed
- after the handler's invocation is finished. The return value of SetupResult::Continue
- instructs the task tree to continue running, that is, to execute the associated \c Task.
- The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
- instructs the task tree to skip the task's execution and finish it immediately with
- success or an error, respectively.
-
- When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
- the task's done handler (if provided) isn't called afterwards.
-
- The constructor of a custom task accepts also functions in the shortened form of
- \c std::function<void(Task &)>, that is, the return value is \c void.
- In this case, it's assumed that the return value is SetupResult::Continue.
-
- \sa CustomTask(), TaskDoneHandler, GroupSetupHandler
-*/
-
-/*!
- \typealias Tasking::CustomTask::TaskDoneHandler
-
- Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult.
-
- The \c TaskDoneHandler is an optional argument of a custom task element's constructor.
- Any function with the above signature, when passed as a task done handler,
- will be called by the running task tree after the task execution finished and before
- the final result of the execution is reported back to the parent group.
-
- Inside the body of the handler you may retrieve the final data from the finished task.
- The additional parameters, including storages, may be passed to the handler
- via the lambda capture.
- It is also possible to decide dynamically whether the task should finish with its return
- value, or the final result should be tweaked.
-
- The DoneWith argument is optional and your done handler may omit it.
- When provided, it holds the info about the final result of a task that will be
- reported to its parent.
-
- If you do not plan to read any data from the finished task,
- you may omit the \c {const Task &} argument.
-
- The returned DoneResult value is optional and your handler may return \c void instead.
- In this case, the final result of the task will be equal to the value indicated by
- the DoneWith argument. When the handler returns the DoneResult value,
- the task's final result may be tweaked inside the done handler's body by the returned value.
-
- For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed,
- and the task finishes unconditionally with the passed value of DoneResult.
-
- \sa CustomTask(), TaskSetupHandler, GroupDoneHandler
-*/
-
-/*!
- \fn template <typename Adapter> template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> CustomTask<Adapter>::CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
-
- Constructs a \c CustomTask instance and attaches the \a setup and \a done handlers to the task.
- When the running task tree is about to start the task,
- it instantiates the associated \l Task object, invokes \a setup handler with a \e reference
- to the created task, and starts it. When the running task finishes,
- the task tree invokes a \a done handler, with a \c const \e reference to the created task.
-
- The passed \a setup handler is of the \l TaskSetupHandler type. For example:
-
- \code
- static void parseAndLog(const QString &input);
-
- ...
-
- const QString input = ...;
-
- const auto onFirstSetup = [input](ConcurrentCall<void> &task) {
- if (input == "Skip")
- return SetupResult::StopWithSuccess; // This task won't start, the next one will
- if (input == "Error")
- return SetupResult::StopWithError; // This task and the next one won't start
- task.setConcurrentCallData(parseAndLog, input);
- // This task will start, and the next one will start after this one finished with success
- return SetupResult::Continue;
- };
-
- const auto onSecondSetup = [input](ConcurrentCall<void> &task) {
- task.setConcurrentCallData(parseAndLog, input);
- };
-
- const Group group {
- ConcurrentCallTask<void>(onFirstSetup),
- ConcurrentCallTask<void>(onSecondSetup)
- };
- \endcode
-
- The \a done handler is of the \l TaskDoneHandler type.
- By default, the \a done handler is invoked whenever the task finishes.
- Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
- only on a successful or failed execution.
-
- \sa TaskSetupHandler, TaskDoneHandler
-*/
-
-/*!
- \enum Tasking::WorkflowPolicy
-
- This enum describes the possible behavior of the Group element when any group's child task
- finishes its execution. It's also used when the running Group is canceled.
-
- \value StopOnError
- Default. Corresponds to the stopOnError global element.
- If any child task finishes with an error, the group stops and finishes with an error.
- If all child tasks finished with success, the group finishes with success.
- If a group is empty, it finishes with success.
- \value ContinueOnError
- Corresponds to the continueOnError global element.
- Similar to stopOnError, but in case any child finishes with an error,
- the execution continues until all tasks finish, and the group reports an error
- afterwards, even when some other tasks in the group finished with success.
- If all child tasks finish successfully, the group finishes with success.
- If a group is empty, it finishes with success.
- \value StopOnSuccess
- Corresponds to the stopOnSuccess global element.
- If any child task finishes with success, the group stops and finishes with success.
- If all child tasks finished with an error, the group finishes with an error.
- If a group is empty, it finishes with an error.
- \value ContinueOnSuccess
- Corresponds to the continueOnSuccess global element.
- Similar to stopOnSuccess, but in case any child finishes successfully,
- the execution continues until all tasks finish, and the group reports success
- afterwards, even when some other tasks in the group finished with an error.
- If all child tasks finish with an error, the group finishes with an error.
- If a group is empty, it finishes with an error.
- \value StopOnSuccessOrError
- Corresponds to the stopOnSuccessOrError global element.
- The group starts as many tasks as it can. When any task finishes,
- the group stops and reports the task's result.
- Useful only in parallel mode.
- In sequential mode, only the first task is started, and when finished,
- the group finishes too, so the other tasks are always skipped.
- If a group is empty, it finishes with an error.
- \value FinishAllAndSuccess
- Corresponds to the finishAllAndSuccess global element.
- The group executes all tasks and ignores their return results. When all
- tasks finished, the group finishes with success.
- If a group is empty, it finishes with success.
- \value FinishAllAndError
- Corresponds to the finishAllAndError global element.
- The group executes all tasks and ignores their return results. When all
- tasks finished, the group finishes with an error.
- If a group is empty, it finishes with an error.
-
- Whenever a child task's result causes the Group to stop, that is,
- in case of StopOnError, StopOnSuccess, or StopOnSuccessOrError policies,
- the Group cancels the other running child tasks (if any - for example in parallel mode),
- and skips executing tasks it has not started yet (for example, in the sequential mode -
- those, that are placed after the failed task). Both canceling and skipping child tasks
- may happen when parallelLimit() is used.
-
- The table below summarizes the differences between various workflow policies:
-
- \table
- \header
- \li \l WorkflowPolicy
- \li Executes all child tasks
- \li Result
- \li Result when the group is empty
- \row
- \li StopOnError
- \li Stops when any child task finished with an error and reports an error
- \li An error when at least one child task failed, success otherwise
- \li Success
- \row
- \li ContinueOnError
- \li Yes
- \li An error when at least one child task failed, success otherwise
- \li Success
- \row
- \li StopOnSuccess
- \li Stops when any child task finished with success and reports success
- \li Success when at least one child task succeeded, an error otherwise
- \li An error
- \row
- \li ContinueOnSuccess
- \li Yes
- \li Success when at least one child task succeeded, an error otherwise
- \li An error
- \row
- \li StopOnSuccessOrError
- \li Stops when any child task finished and reports child task's result
- \li Success or an error, depending on the finished child task's result
- \li An error
- \row
- \li FinishAllAndSuccess
- \li Yes
- \li Success
- \li Success
- \row
- \li FinishAllAndError
- \li Yes
- \li An error
- \li An error
- \endtable
-
- If a child of a group is also a group, the child group runs its tasks according to its own
- workflow policy. When a parent group stops the running child group because
- of parent group's workflow policy, that is, when the StopOnError, StopOnSuccess,
- or StopOnSuccessOrError policy was used for the parent,
- the child group's result is reported according to the
- \b Result column and to the \b {child group's workflow policy} row in the table above.
-*/
-
-/*!
- \variable Tasking::nullItem
-
- A convenient global group's element indicating a no-op item.
-
- This is useful in conditional expressions to indicate the absence of an optional element:
-
- \code
- const ExecutableItem task = ...;
- const std::optional<ExecutableItem> optionalTask = ...;
-
- Group group {
- task,
- optionalTask ? *optionalTask : nullItem
- };
- \endcode
-*/
-
-/*!
- \variable Tasking::successItem
-
- A convenient global executable element containing an empty, successful, synchronous task.
-
- This is useful in if-statements to indicate that a branch ends with success:
-
- \code
- const ExecutableItem conditionalTask = ...;
-
- Group group {
- stopOnDone,
- If (conditionalTask) >> Then {
- ...
- } >> Else {
- successItem
- },
- nextTask
- };
- \endcode
-
- In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
- is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped
- (because of the stopOnDone workflow policy of the \c group)
- and the \c group finishes with success.
-
- \sa errorItem
-*/
-
-/*!
- \variable Tasking::errorItem
-
- A convenient global executable element containing an empty, erroneous, synchronous task.
-
- This is useful in if-statements to indicate that a branch ends with an error:
-
- \code
- const ExecutableItem conditionalTask = ...;
-
- Group group {
- stopOnError,
- If (conditionalTask) >> Then {
- ...
- } >> Else {
- errorItem
- },
- nextTask
- };
- \endcode
-
- In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
- is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped
- (because of the stopOnError workflow policy of the \c group)
- and the \c group finishes with an error.
-
- \sa successItem
-*/
-
-/*!
- \variable Tasking::sequential
- A convenient global group's element describing the sequential execution mode.
-
- This is the default execution mode of the Group element.
-
- When a Group has no execution mode, it runs in the sequential mode.
- All the direct child tasks of a group are started in a chain, so that when one task finishes,
- the next one starts. This enables you to pass the results from the previous task
- as input to the next task before it starts. This mode guarantees that the next task
- is started only after the previous task finishes.
-
- \sa parallel, parallelLimit()
-*/
-
-/*!
- \variable Tasking::parallel
- A convenient global group's element describing the parallel execution mode.
-
- All the direct child tasks of a group are started after the group is started,
- without waiting for the previous child tasks to finish.
- In this mode, all child tasks run simultaneously.
-
- \sa sequential, parallelLimit()
-*/
-
-/*!
- \variable Tasking::parallelIdealThreadCountLimit
- A convenient global group's element describing the parallel execution mode with a limited
- number of tasks running simultanously. The limit is equal to the ideal number of threads
- excluding the calling thread.
-
- This is a shortcut to:
- \code
- parallelLimit(qMax(QThread::idealThreadCount() - 1, 1))
- \endcode
-
- \sa parallel, parallelLimit()
-*/
-
-/*!
- \variable Tasking::stopOnError
- A convenient global group's element describing the StopOnError workflow policy.
-
- This is the default workflow policy of the Group element.
-*/
-
-/*!
- \variable Tasking::continueOnError
- A convenient global group's element describing the ContinueOnError workflow policy.
-*/
-
-/*!
- \variable Tasking::stopOnSuccess
- A convenient global group's element describing the StopOnSuccess workflow policy.
-*/
-
-/*!
- \variable Tasking::continueOnSuccess
- A convenient global group's element describing the ContinueOnSuccess workflow policy.
-*/
-
-/*!
- \variable Tasking::stopOnSuccessOrError
- A convenient global group's element describing the StopOnSuccessOrError workflow policy.
-*/
-
-/*!
- \variable Tasking::finishAllAndSuccess
- A convenient global group's element describing the FinishAllAndSuccess workflow policy.
-*/
-
-/*!
- \variable Tasking::finishAllAndError
- A convenient global group's element describing the FinishAllAndError workflow policy.
-*/
-
-/*!
- \enum Tasking::SetupResult
-
- This enum is optionally returned from the group's or task's setup handler function.
- It instructs the running task tree on how to proceed after the setup handler's execution
- finished.
- \value Continue
- Default. The group's or task's execution continues normally.
- When a group's or task's setup handler returns void, it's assumed that
- it returned Continue.
- \value StopWithSuccess
- The group's or task's execution stops immediately with success.
- When returned from the group's setup handler, all child tasks are skipped,
- and the group's onGroupDone() handler is invoked with DoneWith::Success.
- The group reports success to its parent. The group's workflow policy is ignored.
- When returned from the task's setup handler, the task isn't started,
- its done handler isn't invoked, and the task reports success to its parent.
- \value StopWithError
- The group's or task's execution stops immediately with an error.
- When returned from the group's setup handler, all child tasks are skipped,
- and the group's onGroupDone() handler is invoked with DoneWith::Error.
- The group reports an error to its parent. The group's workflow policy is ignored.
- When returned from the task's setup handler, the task isn't started,
- its error handler isn't invoked, and the task reports an error to its parent.
-*/
-
-/*!
- \enum Tasking::DoneResult
-
- This enum is optionally returned from the group's or task's done handler function.
- When the done handler doesn't return any value, that is, its return type is \c void,
- its final return value is automatically deduced by the running task tree and reported
- to its parent group.
-
- When the done handler returns the DoneResult, you can tweak the final return value
- inside the handler.
-
- When the DoneResult is returned by the group's done handler, the group's workflow policy
- is ignored.
-
- This enum is also used inside the TaskInterface::done() signal and it indicates whether
- the task finished with success or an error.
-
- \value Success
- The group's or task's execution ends with success.
- \value Error
- The group's or task's execution ends with an error.
-*/
-
-/*!
- \enum Tasking::DoneWith
-
- This enum is an optional argument for the group's or task's done handler.
- It indicates whether the group or task finished with success or an error, or it was canceled.
-
- It is also used as an argument inside the TaskTree::done() signal,
- indicating the final result of the TaskTree execution.
-
- \value Success
- The group's or task's execution ended with success.
- \value Error
- The group's or task's execution ended with an error.
- \value Cancel
- The group's or task's execution was canceled. This happens when the user calls
- TaskTree::cancel() for the running task tree or when the group's workflow policy
- results in canceling some of its running children.
- Tweaking the done handler's final result by returning Tasking::DoneResult from
- the handler is no-op when the group's or task's execution was canceled.
-*/
-
-/*!
- \enum Tasking::CallDoneIf
-
- This enum is an optional argument for the \l onGroupDone() element or custom task's constructor.
- It instructs the task tree on when the group's or task's done handler should be invoked.
-
- \value SuccessOrError
- The done handler is always invoked.
- \value Success
- The done handler is invoked only after successful execution,
- that is, when DoneWith::Success.
- \value Error
- The done handler is invoked only after failed execution,
- that is, when DoneWith::Error or when DoneWith::Cancel.
-*/
-
-/*!
- \typealias Tasking::GroupItem::GroupSetupHandler
-
- Type alias for \c std::function<SetupResult()>.
-
- The \c GroupSetupHandler is an argument of the onGroupSetup() element.
- Any function with the above signature, when passed as a group setup handler,
- will be called by the running task tree when the group execution starts.
-
- The return value of the handler instructs the running group on how to proceed
- after the handler's invocation is finished. The default return value of SetupResult::Continue
- instructs the group to continue running, that is, to start executing its child tasks.
- The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
- instructs the group to skip the child tasks' execution and finish immediately with
- success or an error, respectively.
-
- When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
- the group's done handler (if provided) is called synchronously immediately afterwards.
-
- \note Even if the group setup handler returns StopWithSuccess or StopWithError,
- the group's done handler is invoked. This behavior differs from that of task done handler
- and might change in the future.
-
- The onGroupSetup() element accepts also functions in the shortened form of
- \c std::function<void()>, that is, the return value is \c void.
- In this case, it's assumed that the return value is SetupResult::Continue.
-
- \sa onGroupSetup(), GroupDoneHandler, CustomTask::TaskSetupHandler
-*/
-
-/*!
- \typealias Tasking::GroupItem::GroupDoneHandler
-
- Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult.
-
- The \c GroupDoneHandler is an argument of the onGroupDone() element.
- Any function with the above signature, when passed as a group done handler,
- will be called by the running task tree when the group execution ends.
-
- The DoneWith argument is optional and your done handler may omit it.
- When provided, it holds the info about the final result of a group that will be
- reported to its parent.
-
- The returned DoneResult value is optional and your handler may return \c void instead.
- In this case, the final result of the group will be equal to the value indicated by
- the DoneWith argument. When the handler returns the DoneResult value,
- the group's final result may be tweaked inside the done handler's body by the returned value.
-
- For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed,
- and the group finishes unconditionally with the passed value of DoneResult,
- ignoring the group's workflow policy.
-
- \sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler
-*/
-
-/*!
- \fn template <typename Handler> GroupItem onGroupSetup(Handler &&handler)
-
- Constructs a group's element holding the group setup handler.
- The \a handler is invoked whenever the group starts.
-
- The passed \a handler is either of the \c std::function<SetupResult()> or the
- \c std::function<void()> type. For more information on a possible handler type, refer to
- \l {GroupItem::GroupSetupHandler}.
-
- When the \a handler is invoked, none of the group's child tasks are running yet.
-
- If a group contains the Storage elements, the \a handler is invoked
- after the storages are constructed, so that the \a handler may already
- perform some initial modifications to the active storages.
-
- \sa GroupItem::GroupSetupHandler, onGroupDone()
-*/
-
-/*!
- \fn template <typename Handler> GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
-
- Constructs a group's element holding the group done handler.
- By default, the \a handler is invoked whenever the group finishes.
- Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
- only on a successful or failed execution.
- Depending on the group's workflow policy, this handler may also be called
- when the running group is canceled (e.g. when stopOnError element was used).
-
- The passed \a handler is of the \c std::function<DoneResult(DoneWith)> type.
- Optionally, each of the return DoneResult type or the argument DoneWith type may be omitted
- (that is, its return type may be \c void). For more information on a possible handler type,
- refer to \l {GroupItem::GroupDoneHandler}.
-
- When the \a handler is invoked, all of the group's child tasks are already finished.
-
- If a group contains the Storage elements, the \a handler is invoked
- before the storages are destructed, so that the \a handler may still
- perform a last read of the active storages' data.
-
- \sa GroupItem::GroupDoneHandler, onGroupSetup()
-*/
-
-/*!
- Constructs a group's element describing the \l{Execution Mode}{execution mode}.
-
- The execution mode element in a Group specifies how the direct child tasks of
- the Group are started.
-
- For convenience, when appropriate, the \l sequential or \l parallel global elements
- may be used instead.
-
- The \a limit defines the maximum number of direct child tasks running in parallel:
-
- \list
- \li When \a limit equals to 0, there is no limit, and all direct child tasks are started
- together, in the oder in which they appear in a group. This means the fully parallel
- execution, and the \l parallel element may be used instead.
-
- \li When \a limit equals to 1, it means that only one child task may run at the time.
- This means the sequential execution, and the \l sequential element may be used instead.
- In this case, child tasks run in chain, so the next child task starts after
- the previous child task has finished.
-
- \li When other positive number is passed as \a limit, the group's child tasks run
- in parallel, but with a limited number of tasks running simultanously.
- The \e limit defines the maximum number of tasks running in parallel in a group.
- When the group is started, the first batch of tasks is started
- (the number of tasks in a batch equals to the passed \a limit, at most),
- while the others are kept waiting. When any running task finishes,
- the group starts the next remaining one, so that the \e limit of simultaneously
- running tasks inside a group isn't exceeded. This repeats on every child task's
- finish until all child tasks are started. This enables you to limit the maximum
- number of tasks that run simultaneously, for example if running too many processes might
- block the machine for a long time.
- \endlist
-
- In all execution modes, a group starts tasks in the oder in which they appear.
-
- If a child of a group is also a group, the child group runs its tasks according
- to its own execution mode.
-
- \sa sequential, parallel
-*/
-
-GroupItem parallelLimit(int limit)
-{
- struct ParallelLimit : GroupItem {
- ParallelLimit(int limit) : GroupItem({{}, limit}) {}
- };
- return ParallelLimit(limit);
-}
-
-/*!
- Constructs a group's \l {Workflow Policy} {workflow policy} element for a given \a policy.
-
- For convenience, global elements may be used instead.
-
- \sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError,
- finishAllAndSuccess, finishAllAndError, WorkflowPolicy
-*/
-GroupItem workflowPolicy(WorkflowPolicy policy)
-{
- struct WorkflowPolicyItem : GroupItem {
- WorkflowPolicyItem(WorkflowPolicy policy) : GroupItem({{}, {}, policy}) {}
- };
- return WorkflowPolicyItem(policy);
-}
-
-const GroupItem sequential = parallelLimit(1);
-const GroupItem parallel = parallelLimit(0);
-const GroupItem parallelIdealThreadCountLimit = parallelLimit(qMax(QThread::idealThreadCount() - 1, 1));
-
-const GroupItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError);
-const GroupItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError);
-const GroupItem stopOnSuccess = workflowPolicy(WorkflowPolicy::StopOnSuccess);
-const GroupItem continueOnSuccess = workflowPolicy(WorkflowPolicy::ContinueOnSuccess);
-const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSuccessOrError);
-const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess);
-const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError);
-
-// Keep below the above in order to avoid static initialization fiasco.
-const GroupItem nullItem = Group {};
-const ExecutableItem successItem = Group { finishAllAndSuccess };
-const ExecutableItem errorItem = Group { finishAllAndError };
-
-Group operator>>(const For &forItem, const Do &doItem)
-{
- return {forItem.m_loop, doItem.m_children};
-}
-
-Group operator>>(const When &whenItem, const Do &doItem)
-{
- const SingleBarrier barrier;
-
- return {
- barrier,
- parallel,
- whenItem.m_barrierKicker(barrier),
- Group {
- waitForBarrierTask(barrier),
- doItem.m_children
- }
- };
-}
-
-// Please note the thread_local keyword below guarantees a separate instance per thread.
-// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
-// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
-static thread_local QList<TaskTree *> s_activeTaskTrees = {};
-
-static TaskTree *activeTaskTree()
-{
- QT_ASSERT(s_activeTaskTrees.size(), return nullptr);
- return s_activeTaskTrees.back();
-}
-
-DoneResult toDoneResult(bool success)
-{
- return success ? DoneResult::Success : DoneResult::Error;
-}
-
-static SetupResult toSetupResult(bool success)
-{
- return success ? SetupResult::StopWithSuccess : SetupResult::StopWithError;
-}
-
-static DoneResult toDoneResult(DoneWith doneWith)
-{
- return doneWith == DoneWith::Success ? DoneResult::Success : DoneResult::Error;
-}
-
-static DoneWith toDoneWith(DoneResult result)
-{
- return result == DoneResult::Success ? DoneWith::Success : DoneWith::Error;
-}
-
-class LoopThreadData
-{
- Q_DISABLE_COPY_MOVE(LoopThreadData)
-
-public:
- LoopThreadData() = default;
- void pushIteration(int index)
- {
- m_activeLoopStack.push_back(index);
- }
- void popIteration()
- {
- QT_ASSERT(m_activeLoopStack.size(), return);
- m_activeLoopStack.pop_back();
- }
- int iteration() const
- {
- QT_ASSERT(m_activeLoopStack.size(), qWarning(
- "The referenced loop is not reachable in the running tree. "
- "A -1 will be returned which might lead to a crash in the calling code. "
- "It is possible that no loop was added to the tree, "
- "or the loop is not reachable from where it is referenced."); return -1);
- return m_activeLoopStack.last();
- }
-
-private:
- QList<int> m_activeLoopStack;
-};
-
-class LoopData
-{
-public:
- LoopThreadData &threadData() {
- QMutexLocker lock(&m_threadDataMutex);
- return m_threadDataMap.try_emplace(QThread::currentThread()).first->second;
- }
-
- const std::optional<int> m_loopCount = {};
- const Loop::ValueGetter m_valueGetter = {};
- const Loop::Condition m_condition = {};
- QMutex m_threadDataMutex = {};
- // Use std::map on purpose, so that it doesn't invalidate references on modifications.
- // Don't optimize it by using std::unordered_map.
- std::map<QThread *, LoopThreadData> m_threadDataMap = {};
-};
-
-Loop::Loop()
- : m_loopData(new LoopData)
-{}
-
-Loop::Loop(int count, const ValueGetter &valueGetter)
- : m_loopData(new LoopData{count, valueGetter})
-{}
-
-Loop::Loop(const Condition &condition)
- : m_loopData(new LoopData{{}, {}, condition})
-{}
-
-int Loop::iteration() const
-{
- return m_loopData->threadData().iteration();
-}
-
-const void *Loop::valuePtr() const
-{
- return m_loopData->m_valueGetter(iteration());
-}
-
-using StoragePtr = void *;
-
-static constexpr QLatin1StringView s_activeStorageWarning =
- "The referenced storage is not reachable in the running tree. "
- "A nullptr will be returned which might lead to a crash in the calling code. "
- "It is possible that no storage was added to the tree, "
- "or the storage is not reachable from where it is referenced."_L1;
-
-class StorageThreadData
-{
- Q_DISABLE_COPY_MOVE(StorageThreadData)
-
-public:
- StorageThreadData() = default;
- void pushStorage(StoragePtr storagePtr)
- {
- m_activeStorageStack.push_back({storagePtr, activeTaskTree()});
- }
- void popStorage()
- {
- QT_ASSERT(m_activeStorageStack.size(), return);
- m_activeStorageStack.pop_back();
- }
- StoragePtr activeStorage() const
- {
- QT_ASSERT(m_activeStorageStack.size(),
- qWarning().noquote() << s_activeStorageWarning; return nullptr);
- const QPair<StoragePtr, TaskTree *> &top = m_activeStorageStack.last();
- QT_ASSERT(top.second == activeTaskTree(),
- qWarning().noquote() << s_activeStorageWarning; return nullptr);
- return top.first;
- }
-
-private:
- QList<QPair<StoragePtr, TaskTree *>> m_activeStorageStack;
-};
-
-class StorageData
-{
-public:
- StorageThreadData &threadData() {
- QMutexLocker lock(&m_threadDataMutex);
- return m_threadDataMap.try_emplace(QThread::currentThread()).first->second;
- }
-
- const StorageBase::StorageConstructor m_constructor = {};
- const StorageBase::StorageDestructor m_destructor = {};
- QMutex m_threadDataMutex = {};
- // Use std::map on purpose, so that it doesn't invalidate references on modifications.
- // Don't optimize it by using std::unordered_map.
- std::map<QThread *, StorageThreadData> m_threadDataMap = {};
-};
-
-StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor)
- : m_storageData(new StorageData{ctor, dtor})
-{}
-
-void *StorageBase::activeStorageVoid() const
-{
- return m_storageData->threadData().activeStorage();
-}
-
-void GroupItem::addChildren(const GroupItems &children)
-{
- QT_ASSERT(m_type == Type::Group || m_type == Type::List,
- qWarning("Only Group or List may have children, skipping..."); return);
- if (m_type == Type::List) {
- m_children.append(children);
- return;
- }
- for (const GroupItem &child : children) {
- switch (child.m_type) {
- case Type::List:
- addChildren(child.m_children);
- break;
- case Type::Group:
- m_children.append(child);
- break;
- case Type::GroupData:
- if (child.m_groupData.m_groupHandler.m_setupHandler) {
- QT_ASSERT(!m_groupData.m_groupHandler.m_setupHandler,
- qWarning("Group setup handler redefinition, overriding..."));
- m_groupData.m_groupHandler.m_setupHandler
- = child.m_groupData.m_groupHandler.m_setupHandler;
- }
- if (child.m_groupData.m_groupHandler.m_doneHandler) {
- QT_ASSERT(!m_groupData.m_groupHandler.m_doneHandler,
- qWarning("Group done handler redefinition, overriding..."));
- m_groupData.m_groupHandler.m_doneHandler
- = child.m_groupData.m_groupHandler.m_doneHandler;
- m_groupData.m_groupHandler.m_callDoneIf
- = child.m_groupData.m_groupHandler.m_callDoneIf;
- }
- if (child.m_groupData.m_parallelLimit) {
- QT_ASSERT(!m_groupData.m_parallelLimit,
- qWarning("Group execution mode redefinition, overriding..."));
- m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit;
- }
- if (child.m_groupData.m_workflowPolicy) {
- QT_ASSERT(!m_groupData.m_workflowPolicy,
- qWarning("Group workflow policy redefinition, overriding..."));
- m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy;
- }
- if (child.m_groupData.m_loop) {
- QT_ASSERT(!m_groupData.m_loop,
- qWarning("Group loop redefinition, overriding..."));
- m_groupData.m_loop = child.m_groupData.m_loop;
- }
- break;
- case Type::TaskHandler:
- QT_ASSERT(child.m_taskHandler.m_createHandler,
- qWarning("Task create handler can't be null, skipping..."); return);
- m_children.append(child);
- break;
- case Type::Storage:
- // Check for duplicates, as can't have the same storage twice on the same level.
- for (const StorageBase &storage : child.m_storageList) {
- if (m_storageList.contains(storage)) {
- QT_ASSERT(false, qWarning("Can't add the same storage into one Group twice, "
- "skipping..."));
- continue;
- }
- m_storageList.append(storage);
- }
- break;
- }
- }
-}
-
-/*!
- \class Tasking::ExecutableItem
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief Base class for executable task items.
- \reentrant
-
- \c ExecutableItem provides an additional interface for items containing executable tasks.
- Use withTimeout() to attach a timeout to a task.
- Use withLog() to include debugging information about the task startup and the execution result.
-*/
-
-/*!
- Attaches \c TimeoutTask to a copy of \c this ExecutableItem, elapsing after \a timeout
- in milliseconds, with an optionally provided timeout \a handler, and returns the coupled item.
-
- When the ExecutableItem finishes before \a timeout passes, the returned item finishes
- immediately with the task's result. Otherwise, \a handler is invoked (if provided),
- the task is canceled, and the returned item finishes with an error.
-*/
-Group ExecutableItem::withTimeout(milliseconds timeout,
- const std::function<void()> &handler) const
-{
- const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
- return Group {
- parallel,
- stopOnSuccessOrError,
- Group {
- finishAllAndError,
- handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
- : TimeoutTask(onSetup)
- },
- *this
- };
-}
-
-static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); }
-
-static QString logHeader(const QString &logName)
-{
- return QString::fromLatin1("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName);
-};
-
-/*!
- Attaches a custom debug printout to a copy of \c this ExecutableItem,
- issued on task startup and after the task is finished, and returns the coupled item.
-
- The debug printout includes a timestamp of the event (start or finish)
- and \a logName to identify the specific task in the debug log.
-
- The finish printout contains the additional information whether the execution was
- synchronous or asynchronous, its result (the value described by the DoneWith enum),
- and the total execution time in milliseconds.
-*/
-Group ExecutableItem::withLog(const QString &logName) const
-{
- struct LogStorage
- {
- time_point<system_clock, nanoseconds> start;
- int asyncCount = 0;
- };
- const Storage<LogStorage> storage;
- return Group {
- storage,
- onGroupSetup([storage, logName] {
- storage->start = system_clock::now();
- storage->asyncCount = activeTaskTree()->asyncCount();
- qDebug().noquote().nospace() << logHeader(logName) << " started.";
- }),
- *this,
- onGroupDone([storage, logName](DoneWith result) {
- const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start);
- const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount;
- QT_CHECK(asyncCountDiff >= 0);
- const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>();
- const QString syncType = asyncCountDiff ? QString::fromLatin1("asynchronously")
- : QString::fromLatin1("synchronously");
- qDebug().noquote().nospace() << logHeader(logName) << " finished " << syncType
- << " with " << doneWithEnum.valueToKey(int(result))
- << " within " << elapsed.count() << "ms.";
- })
- };
-}
-
-/*!
- \fn Group ExecutableItem::operator!(const ExecutableItem &item)
-
- Returns a Group with the DoneResult of \a item negated.
-
- If \a item reports DoneResult::Success, the returned item reports DoneResult::Error.
- If \a item reports DoneResult::Error, the returned item reports DoneResult::Success.
-
- The returned item is equivalent to:
- \code
- Group {
- item,
- onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
- }
- \endcode
-
- \sa operator&&(), operator||()
-*/
-Group operator!(const ExecutableItem &item)
-{
- return {
- item,
- onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
- };
-}
-
-/*!
- \fn Group ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second)
-
- Returns a Group with \a first and \a second tasks merged with conjunction.
-
- Both \a first and \a second tasks execute in sequence.
- If both tasks report DoneResult::Success, the returned item reports DoneResult::Success.
- Otherwise, the returned item reports DoneResult::Error.
-
- The returned item is
- \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
- if the \a first task reports DoneResult::Error, the \a second task is skipped,
- and the returned item reports DoneResult::Error immediately.
-
- The returned item is equivalent to:
- \code
- Group { stopOnError, first, second }
- \endcode
-
- \note Parallel execution of conjunction in a short-circuit manner can be achieved with the
- following code: \c {Group { parallel, stopOnError, first, second }}. In this case:
- if the \e {first finished} task reports DoneResult::Error,
- the \e other task is canceled, and the group reports DoneResult::Error immediately.
-
- \sa operator||(), operator!()
-*/
-Group operator&&(const ExecutableItem &first, const ExecutableItem &second)
-{
- return { stopOnError, first, second };
-}
-
-/*!
- \fn Group ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second)
-
- Returns a Group with \a first and \a second tasks merged with disjunction.
-
- Both \a first and \a second tasks execute in sequence.
- If both tasks report DoneResult::Error, the returned item reports DoneResult::Error.
- Otherwise, the returned item reports DoneResult::Success.
-
- The returned item is
- \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
- if the \a first task reports DoneResult::Success, the \a second task is skipped,
- and the returned item reports DoneResult::Success immediately.
-
- The returned item is equivalent to:
- \code
- Group { stopOnSuccess, first, second }
- \endcode
-
- \note Parallel execution of disjunction in a short-circuit manner can be achieved with the
- following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case:
- if the \e {first finished} task reports DoneResult::Success,
- the \e other task is canceled, and the group reports DoneResult::Success immediately.
-
- \sa operator&&(), operator!()
-*/
-Group operator||(const ExecutableItem &first, const ExecutableItem &second)
-{
- return { stopOnSuccess, first, second };
-}
-
-/*!
- \fn Group ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result)
- \overload ExecutableItem::operator&&()
-
- Returns the \a item task if the \a result is DoneResult::Success; otherwise returns
- the \a item task with its done result tweaked to DoneResult::Error.
-
- The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result
- into DoneResult::Error unconditionally.
-*/
-Group operator&&(const ExecutableItem &item, DoneResult result)
-{
- return { result == DoneResult::Success ? stopOnError : finishAllAndError, item };
-}
-
-/*!
- \fn Group ExecutableItem::operator||(const ExecutableItem &item, DoneResult result)
- \overload ExecutableItem::operator||()
-
- Returns the \a item task if the \a result is DoneResult::Error; otherwise returns
- the \a item task with its done result tweaked to DoneResult::Success.
-
- The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result
- into DoneResult::Success unconditionally.
-*/
-Group operator||(const ExecutableItem &item, DoneResult result)
-{
- return { result == DoneResult::Error ? stopOnError : finishAllAndSuccess, item };
-}
-
-Group ExecutableItem::withCancelImpl(
- const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper,
- const GroupItems &postCancelRecipe) const
-{
- const Storage<bool> canceledStorage(false);
-
- const auto onSetup = [connectWrapper, canceledStorage](Barrier &barrier) {
- connectWrapper(&barrier, [barrierPtr = &barrier, canceled = canceledStorage.activeStorage()] {
- *canceled = true;
- barrierPtr->advance();
- });
- };
-
- const auto wasCanceled = [canceledStorage] { return *canceledStorage; };
-
- return {
- continueOnError,
- canceledStorage,
- Group {
- parallel,
- stopOnSuccessOrError,
- BarrierTask(onSetup) && errorItem,
- *this
- },
- If (wasCanceled) >> Then {
- postCancelRecipe
- }
- };
-}
-
-Group ExecutableItem::withAcceptImpl(
- const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const
-{
- const auto onSetup = [connectWrapper](Barrier &barrier) {
- connectWrapper(&barrier, [barrierPtr = &barrier] { barrierPtr->advance(); });
- };
- return Group {
- parallel,
- BarrierTask(onSetup),
- *this
- };
-}
-
-class TaskTreePrivate;
-class TaskNode;
-class RuntimeContainer;
-class RuntimeIteration;
-class RuntimeTask;
-
-class ExecutionContextActivator
-{
-public:
- ExecutionContextActivator(RuntimeIteration *iteration) {
- activateTaskTree(iteration);
- activateContext(iteration);
- }
- ExecutionContextActivator(RuntimeContainer *container) {
- activateTaskTree(container);
- activateContext(container);
- }
- ~ExecutionContextActivator() {
- for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
- m_activeStorages[i].m_storageData->threadData().popStorage();
- for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
- m_activeLoops[i].m_loopData->threadData().popIteration();
- QT_ASSERT(s_activeTaskTrees.size(), return);
- s_activeTaskTrees.pop_back();
- }
-
-private:
- void activateTaskTree(RuntimeIteration *iteration);
- void activateTaskTree(RuntimeContainer *container);
- void activateContext(RuntimeIteration *iteration);
- void activateContext(RuntimeContainer *container);
- QList<Loop> m_activeLoops;
- QList<StorageBase> m_activeStorages;
-};
-
-class ContainerNode
-{
- Q_DISABLE_COPY(ContainerNode)
-
-public:
- ContainerNode(ContainerNode &&other) = default;
- ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task);
-
- TaskTreePrivate *const m_taskTreePrivate = nullptr;
-
- const GroupItem::GroupHandler m_groupHandler;
- const int m_parallelLimit = 1;
- const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
- const std::optional<Loop> m_loop;
- const QList<StorageBase> m_storageList;
- std::vector<TaskNode> m_children;
- const int m_taskCount = 0;
-};
-
-class TaskNode
-{
- Q_DISABLE_COPY(TaskNode)
-
-public:
- TaskNode(TaskNode &&other) = default;
- TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
- : m_taskHandler(task.m_taskHandler)
- , m_container(taskTreePrivate, task)
- {}
-
- bool isTask() const { return bool(m_taskHandler.m_createHandler); }
- int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; }
-
- const GroupItem::TaskHandler m_taskHandler;
- ContainerNode m_container;
-};
-
-class TaskTreePrivate
-{
- Q_DISABLE_COPY_MOVE(TaskTreePrivate)
-
-public:
- explicit TaskTreePrivate(TaskTree *taskTree);
- ~TaskTreePrivate();
-
- void start();
- void stop();
- void bumpAsyncCount();
- void advanceProgress(int byValue);
- void emitDone(DoneWith result);
- void callSetupHandler(const StorageBase &storage, StoragePtr storagePtr) {
- callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
- }
- void callDoneHandler(const StorageBase &storage, StoragePtr storagePtr) {
- callStorageHandler(storage, storagePtr, &StorageHandler::m_doneHandler);
- }
- struct StorageHandler {
- StorageBase::StorageHandler m_setupHandler = {};
- StorageBase::StorageHandler m_doneHandler = {};
- };
- typedef StorageBase::StorageHandler StorageHandler::*HandlerPtr; // ptr to class member
- void callStorageHandler(const StorageBase &storage, StoragePtr storagePtr, HandlerPtr ptr)
- {
- const auto it = m_storageHandlers.constFind(storage);
- if (it == m_storageHandlers.constEnd())
- return;
- const StorageHandler storageHandler = *it;
- if (storageHandler.*ptr) {
- GuardLocker locker(m_guard);
- (storageHandler.*ptr)(storagePtr);
- }
- }
-
- // Node related methods
-
- // If returned value != Continue, childDone() needs to be called in parent container (in caller)
- // in order to unwind properly.
- void startTask(const std::shared_ptr<RuntimeTask> &node);
- void stopTask(RuntimeTask *node);
- bool invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith);
-
- // Container related methods
-
- void continueContainer(RuntimeContainer *container);
- void startChildren(RuntimeContainer *container);
- void childDone(RuntimeIteration *iteration, bool success);
- void stopContainer(RuntimeContainer *container);
- bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith);
- bool invokeLoopHandler(RuntimeContainer *container);
-
- template <typename Container, typename Handler, typename ...Args,
- typename ReturnType = std::invoke_result_t<Handler, Args...>>
- ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args)
- {
- QT_ASSERT(!m_guard.isLocked(), qWarning("Nested execution of handlers detected."
- "This may happen when one task's handler has entered a nested event loop,"
- "and other task finished during nested event loop's processing, "
- "causing stopping (canceling) the task executing the nested event loop. "
- "This includes the case when QCoreApplication::processEvents() was called from "
- "the handler. It may also happen when the Barrier task is advanced directly "
- "from some other task handler. This will lead to a crash. "
- "Avoid event processing during handlers' execution. "
- "If it can't be avoided, make sure no other tasks are run in parallel when "
- "processing events from the handler."));
- ExecutionContextActivator activator(container);
- GuardLocker locker(m_guard);
- return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
- }
-
- static int effectiveLoopCount(const std::optional<Loop> &loop)
- {
- return loop && loop->m_loopData->m_loopCount ? *loop->m_loopData->m_loopCount : 1;
- }
-
- TaskTree *q = nullptr;
- Guard m_guard;
- int m_progressValue = 0;
- int m_asyncCount = 0;
- QSet<StorageBase> m_storages;
- QHash<StorageBase, StorageHandler> m_storageHandlers;
- std::optional<TaskNode> m_root;
- std::shared_ptr<RuntimeTask> m_runtimeRoot; // Keep me last in order to destruct first
-};
-
-static bool initialSuccessBit(WorkflowPolicy workflowPolicy)
-{
- switch (workflowPolicy) {
- case WorkflowPolicy::StopOnError:
- case WorkflowPolicy::ContinueOnError:
- case WorkflowPolicy::FinishAllAndSuccess:
- return true;
- case WorkflowPolicy::StopOnSuccess:
- case WorkflowPolicy::ContinueOnSuccess:
- case WorkflowPolicy::StopOnSuccessOrError:
- case WorkflowPolicy::FinishAllAndError:
- return false;
- }
- QT_CHECK(false);
- return false;
-}
-
-static bool isProgressive(RuntimeContainer *container);
-
-class RuntimeIteration
-{
- Q_DISABLE_COPY(RuntimeIteration)
-
-public:
- RuntimeIteration(int index, RuntimeContainer *container);
- ~RuntimeIteration();
- std::optional<Loop> loop() const;
- void removeChild(RuntimeTask *node);
-
- const int m_iterationIndex = 0;
- const bool m_isProgressive = true;
- RuntimeContainer *m_container = nullptr;
- int m_doneCount = 0;
- std::vector<std::shared_ptr<RuntimeTask>> m_children = {}; // Owning.
-};
-
-class RuntimeContainer
-{
- Q_DISABLE_COPY(RuntimeContainer)
-
-public:
- RuntimeContainer(const ContainerNode &taskContainer, RuntimeTask *parentTask)
- : m_containerNode(taskContainer)
- , m_parentTask(parentTask)
- , m_storages(createStorages(taskContainer))
- , m_successBit(initialSuccessBit(taskContainer.m_workflowPolicy))
- , m_shouldIterate(taskContainer.m_loop)
- {}
-
- ~RuntimeContainer()
- {
- for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order
- const StorageBase storage = m_containerNode.m_storageList[i];
- StoragePtr storagePtr = m_storages.value(i);
- if (m_callStorageDoneHandlersOnDestruction)
- m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr);
- storage.m_storageData->m_destructor(storagePtr);
- }
- }
-
- static QList<StoragePtr> createStorages(const ContainerNode &container);
- bool isStarting() const { return m_startGuard.isLocked(); }
- RuntimeIteration *parentIteration() const;
- bool updateSuccessBit(bool success);
- void deleteFinishedIterations();
- int progressiveLoopCount() const
- {
- return m_containerNode.m_taskTreePrivate->effectiveLoopCount(m_containerNode.m_loop);
- }
-
- const ContainerNode &m_containerNode; // Not owning.
- RuntimeTask *m_parentTask = nullptr; // Not owning.
- const QList<StoragePtr> m_storages; // Owning.
-
- bool m_successBit = true;
- bool m_callStorageDoneHandlersOnDestruction = false;
- Guard m_startGuard;
-
- int m_iterationCount = 0;
- int m_nextToStart = 0;
- int m_runningChildren = 0;
- bool m_shouldIterate = true;
- std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning.
-};
-
-class RuntimeTask
-{
-public:
- ~RuntimeTask()
- {
- if (m_task) {
- // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204.
- QObject::disconnect(m_task.get(), &TaskInterface::done, nullptr, nullptr);
- }
- }
-
- const TaskNode &m_taskNode; // Not owning.
- RuntimeIteration *m_parentIteration = nullptr; // Not owning.
- std::optional<RuntimeContainer> m_container = {}; // Owning.
- std::unique_ptr<TaskInterface> m_task = {}; // Owning.
- SetupResult m_setupResult = SetupResult::Continue;
-};
-
-RuntimeIteration::~RuntimeIteration() = default;
-
-TaskTreePrivate::TaskTreePrivate(TaskTree *taskTree)
- : q(taskTree) {}
-
-TaskTreePrivate::~TaskTreePrivate() = default;
-
-static bool isProgressive(RuntimeContainer *container)
-{
- RuntimeIteration *iteration = container->m_parentTask->m_parentIteration;
- return iteration ? iteration->m_isProgressive : true;
-}
-
-void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
-{
- activateTaskTree(iteration->m_container);
-}
-
-void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
-{
- s_activeTaskTrees.push_back(container->m_containerNode.m_taskTreePrivate->q);
-}
-
-void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
-{
- std::optional<Loop> loop = iteration->loop();
- if (loop) {
- loop->m_loopData->threadData().pushIteration(iteration->m_iterationIndex);
- m_activeLoops.append(*loop);
- }
- activateContext(iteration->m_container);
-}
-
-void ExecutionContextActivator::activateContext(RuntimeContainer *container)
-{
- const ContainerNode &containerNode = container->m_containerNode;
- for (int i = 0; i < containerNode.m_storageList.size(); ++i) {
- const StorageBase &storage = containerNode.m_storageList[i];
- if (m_activeStorages.contains(storage))
- continue; // Storage shadowing: The storage is already active, skipping it...
- m_activeStorages.append(storage);
- storage.m_storageData->threadData().pushStorage(container->m_storages.value(i));
- }
- // Go to the parent after activating this storages so that storage shadowing works
- // in the direction from child to parent root.
- if (container->parentIteration())
- activateContext(container->parentIteration());
-}
-
-void TaskTreePrivate::start()
-{
- QT_ASSERT(m_root, return);
- QT_ASSERT(!m_runtimeRoot, return);
- m_asyncCount = 0;
- m_progressValue = 0;
- {
- GuardLocker locker(m_guard);
- emit q->started();
- emit q->asyncCountChanged(m_asyncCount);
- emit q->progressValueChanged(m_progressValue);
- }
- // TODO: check storage handlers for not existing storages in tree
- for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
- QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
- "exist in task tree. Its handlers will never be called."));
- }
- m_runtimeRoot.reset(new RuntimeTask{*m_root});
- startTask(m_runtimeRoot);
- bumpAsyncCount();
-}
-
-void TaskTreePrivate::stop()
-{
- QT_ASSERT(m_root, return);
- if (!m_runtimeRoot)
- return;
- stopTask(m_runtimeRoot.get());
- m_runtimeRoot.reset();
- emitDone(DoneWith::Cancel);
-}
-
-void TaskTreePrivate::bumpAsyncCount()
-{
- if (!m_runtimeRoot)
- return;
- ++m_asyncCount;
- GuardLocker locker(m_guard);
- emit q->asyncCountChanged(m_asyncCount);
-}
-
-void TaskTreePrivate::advanceProgress(int byValue)
-{
- if (byValue == 0)
- return;
- QT_CHECK(byValue > 0);
- QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
- m_progressValue += byValue;
- GuardLocker locker(m_guard);
- emit q->progressValueChanged(m_progressValue);
-}
-
-void TaskTreePrivate::emitDone(DoneWith result)
-{
- QT_CHECK(m_progressValue == m_root->taskCount());
- GuardLocker locker(m_guard);
- emit q->done(result);
-}
-
-RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container)
- : m_iterationIndex(index)
- , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container))
- , m_container(container)
-{}
-
-std::optional<Loop> RuntimeIteration::loop() const
-{
- return m_container->m_containerNode.m_loop;
-}
-
-void RuntimeIteration::removeChild(RuntimeTask *task)
-{
- const auto it = std::find_if(m_children.cbegin(), m_children.cend(), [task](const auto &ptr) {
- return ptr.get() == task;
- });
- if (it != m_children.cend())
- m_children.erase(it);
-}
-
-static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate,
- const GroupItems &children)
-{
- std::vector<TaskNode> result;
- result.reserve(children.size());
- for (const GroupItem &child : children)
- result.emplace_back(taskTreePrivate, child);
- return result;
-}
-
-ContainerNode::ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
- : m_taskTreePrivate(taskTreePrivate)
- , m_groupHandler(task.m_groupData.m_groupHandler)
- , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(1))
- , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError))
- , m_loop(task.m_groupData.m_loop)
- , m_storageList(task.m_storageList)
- , m_children(createChildren(taskTreePrivate, task.m_children))
- , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0,
- [](int r, const TaskNode &n) { return r + n.taskCount(); })
- * taskTreePrivate->effectiveLoopCount(m_loop))
-{
- for (const StorageBase &storage : m_storageList)
- m_taskTreePrivate->m_storages << storage;
-}
-
-QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container)
-{
- QList<StoragePtr> storages;
- for (const StorageBase &storage : container.m_storageList) {
- StoragePtr storagePtr = storage.m_storageData->m_constructor();
- storages.append(storagePtr);
- container.m_taskTreePrivate->callSetupHandler(storage, storagePtr);
- }
- return storages;
-}
-
-RuntimeIteration *RuntimeContainer::parentIteration() const
-{
- return m_parentTask->m_parentIteration;
-}
-
-bool RuntimeContainer::updateSuccessBit(bool success)
-{
- if (m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess
- || m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndError
- || m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) {
- if (m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError)
- m_successBit = success;
- return m_successBit;
- }
-
- const bool donePolicy = m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccess
- || m_containerNode.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess;
- m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success);
- return m_successBit;
-}
-
-void RuntimeContainer::deleteFinishedIterations()
-{
- for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) {
- if (it->get()->m_doneCount == int(m_containerNode.m_children.size()))
- it = m_iterations.erase(it);
- else
- ++it;
- }
-}
-
-void TaskTreePrivate::continueContainer(RuntimeContainer *container)
-{
- RuntimeTask *parentTask = container->m_parentTask;
- if (parentTask->m_setupResult == SetupResult::Continue)
- startChildren(container);
- if (parentTask->m_setupResult == SetupResult::Continue)
- return;
-
- const bool bit = container->updateSuccessBit(parentTask->m_setupResult == SetupResult::StopWithSuccess);
- RuntimeIteration *parentIteration = container->parentIteration();
- QT_CHECK(parentTask);
- const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error);
- parentTask->m_setupResult = toSetupResult(result);
- if (parentIteration) {
- parentIteration->removeChild(parentTask);
- if (!parentIteration->m_container->isStarting())
- childDone(parentIteration, result);
- } else {
- QT_CHECK(m_runtimeRoot.get() == parentTask);
- m_runtimeRoot.reset();
- emitDone(result ? DoneWith::Success : DoneWith::Error);
- }
-}
-
-void TaskTreePrivate::startChildren(RuntimeContainer *container)
-{
- const ContainerNode &containerNode = container->m_containerNode;
- const int childCount = int(containerNode.m_children.size());
-
- if (container->m_iterationCount == 0) {
- if (container->m_shouldIterate && !invokeLoopHandler(container)) {
- if (isProgressive(container))
- advanceProgress(containerNode.m_taskCount);
- container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit);
- return;
- }
- container->m_iterations.emplace_back(
- std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
- ++container->m_iterationCount;
- }
-
- GuardLocker locker(container->m_startGuard);
-
- while (containerNode.m_parallelLimit == 0
- || container->m_runningChildren < containerNode.m_parallelLimit) {
- container->deleteFinishedIterations();
- if (container->m_nextToStart == childCount) {
- if (invokeLoopHandler(container)) {
- container->m_nextToStart = 0;
- container->m_iterations.emplace_back(
- std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
- ++container->m_iterationCount;
- } else if (container->m_iterations.empty()) {
- container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit);
- return;
- } else {
- return;
- }
- }
- if (containerNode.m_children.size() == 0) // Empty loop body.
- continue;
-
- RuntimeIteration *iteration = container->m_iterations.back().get();
- const std::shared_ptr<RuntimeTask> task(
- new RuntimeTask{containerNode.m_children.at(container->m_nextToStart), iteration});
- iteration->m_children.emplace_back(task);
- ++container->m_runningChildren;
- ++container->m_nextToStart;
-
- startTask(task);
- if (task->m_setupResult == SetupResult::Continue)
- continue;
-
- task->m_parentIteration->removeChild(task.get());
- childDone(iteration, task->m_setupResult == SetupResult::StopWithSuccess);
- if (container->m_parentTask->m_setupResult != SetupResult::Continue)
- return;
- }
-}
-
-void TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success)
-{
- RuntimeContainer *container = iteration->m_container;
- const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy;
- const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError
- || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success)
- || (workflowPolicy == WorkflowPolicy::StopOnError && !success);
- ++iteration->m_doneCount;
- --container->m_runningChildren;
- const bool updatedSuccess = container->updateSuccessBit(success);
- container->m_parentTask->m_setupResult = shouldStop ? toSetupResult(updatedSuccess) : SetupResult::Continue;
- if (shouldStop)
- stopContainer(container);
-
- if (container->isStarting())
- return;
- continueContainer(container);
-}
-
-void TaskTreePrivate::stopContainer(RuntimeContainer *container)
-{
- const ContainerNode &containerNode = container->m_containerNode;
- for (auto &iteration : container->m_iterations) {
- for (auto &child : iteration->m_children) {
- ++iteration->m_doneCount;
- stopTask(child.get());
- }
-
- if (iteration->m_isProgressive) {
- int skippedTaskCount = 0;
- for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i)
- skippedTaskCount += containerNode.m_children.at(i).taskCount();
- advanceProgress(skippedTaskCount);
- }
- }
- const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount;
- if (skippedIterations > 0) {
- advanceProgress(container->m_containerNode.m_taskCount / container->progressiveLoopCount()
- * skippedIterations);
- }
-}
-
-static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
-{
- if (result == DoneWith::Success)
- return callDoneIf != CallDoneIf::Error;
- return callDoneIf != CallDoneIf::Success;
-}
-
-bool TaskTreePrivate::invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith)
-{
- DoneResult result = toDoneResult(doneWith);
- const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler;
- if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith))
- result = invokeHandler(container, groupHandler.m_doneHandler, doneWith);
- container->m_callStorageDoneHandlersOnDestruction = true;
- return result == DoneResult::Success;
-}
-
-bool TaskTreePrivate::invokeLoopHandler(RuntimeContainer *container)
-{
- if (container->m_shouldIterate) {
- const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get();
- if (loopData->m_loopCount) {
- container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount;
- } else if (loopData->m_condition) {
- container->m_shouldIterate = invokeHandler(container, loopData->m_condition,
- container->m_iterationCount);
- }
- }
- return container->m_shouldIterate;
-}
-
-void TaskTreePrivate::startTask(const std::shared_ptr<RuntimeTask> &node)
-{
- if (!node->m_taskNode.isTask()) {
- const ContainerNode &containerNode = node->m_taskNode.m_container;
- node->m_container.emplace(containerNode, node.get());
- RuntimeContainer *container = &*node->m_container;
- if (containerNode.m_groupHandler.m_setupHandler) {
- container->m_parentTask->m_setupResult = invokeHandler(container, containerNode.m_groupHandler.m_setupHandler);
- if (container->m_parentTask->m_setupResult != SetupResult::Continue) {
- if (isProgressive(container))
- advanceProgress(containerNode.m_taskCount);
- // Non-Continue SetupResult takes precedence over the workflow policy.
- container->m_successBit = container->m_parentTask->m_setupResult == SetupResult::StopWithSuccess;
- }
- }
- continueContainer(container);
- return;
- }
-
- const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
- node->m_task.reset(handler.m_createHandler());
- node->m_setupResult = handler.m_setupHandler
- ? invokeHandler(node->m_parentIteration, handler.m_setupHandler, *node->m_task.get())
- : SetupResult::Continue;
- if (node->m_setupResult != SetupResult::Continue) {
- if (node->m_parentIteration->m_isProgressive)
- advanceProgress(1);
- node->m_parentIteration->removeChild(node.get());
- return;
- }
- QObject::connect(node->m_task.get(), &TaskInterface::done,
- q, [this, node](DoneResult doneResult) {
- const bool result = invokeTaskDoneHandler(node.get(), toDoneWith(doneResult));
- node->m_setupResult = toSetupResult(result);
- QObject::disconnect(node->m_task.get(), &TaskInterface::done, q, nullptr);
- node->m_task.release()->deleteLater();
- RuntimeIteration *parentIteration = node->m_parentIteration;
- if (parentIteration->m_container->isStarting())
- return;
-
- parentIteration->removeChild(node.get());
- childDone(parentIteration, result);
- bumpAsyncCount();
- });
- node->m_task->start();
-}
-
-void TaskTreePrivate::stopTask(RuntimeTask *node)
-{
- if (!node->m_task) {
- if (!node->m_container)
- return;
- stopContainer(&*node->m_container);
- node->m_container->updateSuccessBit(false);
- invokeDoneHandler(&*node->m_container, DoneWith::Cancel);
- return;
- }
-
- invokeTaskDoneHandler(node, DoneWith::Cancel);
- node->m_task.reset();
-}
-
-bool TaskTreePrivate::invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith)
-{
- DoneResult result = toDoneResult(doneWith);
- const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
- if (handler.m_doneHandler && shouldCall(handler.m_callDoneIf, doneWith)) {
- result = invokeHandler(node->m_parentIteration,
- handler.m_doneHandler, *node->m_task.get(), doneWith);
- }
- if (node->m_parentIteration->m_isProgressive)
- advanceProgress(1);
- return result == DoneResult::Success;
-}
-
-/*!
- \class Tasking::TaskTree
- \inheaderfile solutions/tasking/tasktree.h
- \inmodule TaskingSolution
- \brief The TaskTree class runs an async task tree structure defined in a declarative way.
- \reentrant
-
- Use the Tasking namespace to build extensible, declarative task tree
- structures that contain possibly asynchronous tasks, such as QProcess,
- NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you
- to create a sophisticated mixture of a parallel or sequential flow of tasks
- in the form of a tree and to run it any time later.
-
- \section1 Root Element and Tasks
-
- The TaskTree has a mandatory Group root element, which may contain
- any number of tasks of various types, such as QProcessTask, NetworkQueryTask,
- or ConcurrentCallTask<ReturnType>:
-
- \code
- using namespace Tasking;
-
- const Group root {
- QProcessTask(...),
- NetworkQueryTask(...),
- ConcurrentCallTask<int>(...)
- };
-
- TaskTree *taskTree = new TaskTree(root);
- connect(taskTree, &TaskTree::done, ...); // finish handler
- taskTree->start();
- \endcode
-
- The task tree above has a top level element of the Group type that contains
- tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type.
- After taskTree->start() is called, the tasks are run in a chain, starting
- with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask
- task is started. Finally, when the network task finishes successfully, the
- ConcurrentCallTask<int> task is started.
-
- When the last running task finishes with success, the task tree is considered
- to have run successfully and the done() signal is emitted with DoneWith::Success.
- When a task finishes with an error, the execution of the task tree is stopped
- and the remaining tasks are skipped. The task tree finishes with an error and
- sends the TaskTree::done() signal with DoneWith::Error.
-
- \section1 Groups
-
- The parent of the Group sees it as a single task. Like other tasks,
- the group can be started and it can finish with success or an error.
- The Group elements can be nested to create a tree structure:
-
- \code
- const Group root {
- Group {
- parallel,
- QProcessTask(...),
- ConcurrentCallTask<int>(...)
- },
- NetworkQueryTask(...)
- };
- \endcode
-
- The example above differs from the first example in that the root element has
- a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a
- sibling element of the NetworkQueryTask in the root. The subgroup contains an
- additional \e parallel element that instructs its Group to execute its tasks
- in parallel.
-
- So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start
- immediately and run in parallel. Since the root group doesn't contain a
- \e parallel element, its direct child tasks are run in sequence. Thus, the
- NetworkQueryTask starts when the whole subgroup finishes. The group is
- considered as finished when all its tasks have finished. The order in which
- the tasks finish is not relevant.
-
- So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the
- following scenarios can take place:
-
- \table
- \header
- \li Scenario 1
- \li Scenario 2
- \row
- \li Root Group starts
- \li Root Group starts
- \row
- \li Sub Group starts
- \li Sub Group starts
- \row
- \li QProcessTask starts
- \li QProcessTask starts
- \row
- \li ConcurrentCallTask<int> starts
- \li ConcurrentCallTask<int> starts
- \row
- \li ...
- \li ...
- \row
- \li \b {QProcessTask finishes}
- \li \b {ConcurrentCallTask<int> finishes}
- \row
- \li ...
- \li ...
- \row
- \li \b {ConcurrentCallTask<int> finishes}
- \li \b {QProcessTask finishes}
- \row
- \li Sub Group finishes
- \li Sub Group finishes
- \row
- \li NetworkQueryTask starts
- \li NetworkQueryTask starts
- \row
- \li ...
- \li ...
- \row
- \li NetworkQueryTask finishes
- \li NetworkQueryTask finishes
- \row
- \li Root Group finishes
- \li Root Group finishes
- \endtable
-
- The differences between the scenarios are marked with bold. Three dots mean
- that an unspecified amount of time passes between previous and next events
- (a task or tasks continue to run). No dots between events
- means that they occur synchronously.
-
- The presented scenarios assume that all tasks run successfully. If a task
- fails during execution, the task tree finishes with an error. In particular,
- when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed,
- the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error,
- the NetworkQueryTask is skipped, and the tree finishes with an error.
-
- \section1 Task Types
-
- Each task type is associated with its corresponding task class that executes
- the task. For example, a QProcessTask inside a task tree is associated with
- the QProcess class that executes the process. The associated objects are
- automatically created, started, and destructed exclusively by the task tree
- at the appropriate time.
-
- If a root group consists of five sequential QProcessTask tasks, and the task tree
- executes the group, it creates an instance of QProcess for the first
- QProcessTask and starts it. If the QProcess instance finishes successfully,
- the task tree destructs it and creates a new QProcess instance for the
- second QProcessTask, and so on. If the first task finishes with an error, the task
- tree stops creating QProcess instances, and the root group finishes with an
- error.
-
- The following table shows examples of task types and their corresponding task
- classes:
-
- \table
- \header
- \li Task Type (Tasking Namespace)
- \li Associated Task Class
- \li Brief Description
- \row
- \li QProcessTask
- \li QProcess
- \li Starts process.
- \row
- \li ConcurrentCallTask<ReturnType>
- \li Tasking::ConcurrentCall<ReturnType>
- \li Starts asynchronous task, runs in separate thread.
- \row
- \li TaskTreeTask
- \li Tasking::TaskTree
- \li Starts nested task tree.
- \row
- \li NetworkQueryTask
- \li NetworkQuery
- \li Starts network download.
- \endtable
-
- \section1 Task Handlers
-
- Use Task handlers to set up a task for execution and to enable reading
- the output data from the task when it finishes with success or an error.
-
- \section2 Task's Start Handler
-
- When a corresponding task class object is created and before it's started,
- the task tree invokes an optionally user-provided setup handler. The setup
- handler should always take a \e reference to the associated task class object:
-
- \code
- const auto onSetup = [](QProcess &process) {
- process.setProgram("sleep");
- process.setArguments({"3"});
- };
- const Group root {
- QProcessTask(onSetup)
- };
- \endcode
-
- You can modify the passed QProcess in the setup handler, so that the task
- tree can start the process according to your configuration.
- You should not call \c {process.start();} in the setup handler,
- as the task tree calls it when needed. The setup handler is optional. When used,
- it must be the first argument of the task's constructor.
-
- Optionally, the setup handler may return a SetupResult. The returned
- SetupResult influences the further start behavior of a given task. The
- possible values are:
-
- \table
- \header
- \li SetupResult Value
- \li Brief Description
- \row
- \li Continue
- \li The task will be started normally. This is the default behavior when the
- setup handler doesn't return SetupResult (that is, its return type is
- void).
- \row
- \li StopWithSuccess
- \li The task won't be started and it will report success to its parent.
- \row
- \li StopWithError
- \li The task won't be started and it will report an error to its parent.
- \endtable
-
- This is useful for running a task only when a condition is met and the data
- needed to evaluate this condition is not known until previously started tasks
- finish. In this way, the setup handler dynamically decides whether to start the
- corresponding task normally or skip it and report success or an error.
- For more information about inter-task data exchange, see \l Storage.
-
- \section2 Task's Done Handler
-
- When a running task finishes, the task tree invokes an optionally provided done handler.
- The handler should take a \c const \e reference to the associated task class object:
-
- \code
- const auto onSetup = [](QProcess &process) {
- process.setProgram("sleep");
- process.setArguments({"3"});
- };
- const auto onDone = [](const QProcess &process, DoneWith result) {
- if (result == DoneWith::Success)
- qDebug() << "Success" << process.cleanedStdOut();
- else
- qDebug() << "Failure" << process.cleanedStdErr();
- };
- const Group root {
- QProcessTask(onSetup, onDone)
- };
- \endcode
-
- The done handler may collect output data from QProcess, and store it
- for further processing or perform additional actions.
-
- \note If the task setup handler returns StopWithSuccess or StopWithError,
- the done handler is not invoked.
-
- \section1 Group Handlers
-
- Similarly to task handlers, group handlers enable you to set up a group to
- execute and to apply more actions when the whole group finishes with
- success or an error.
-
- \section2 Group's Start Handler
-
- The task tree invokes the group start handler before it starts the child
- tasks. The group handler doesn't take any arguments:
-
- \code
- const auto onSetup = [] {
- qDebug() << "Entering the group";
- };
- const Group root {
- onGroupSetup(onSetup),
- QProcessTask(...)
- };
- \endcode
-
- The group setup handler is optional. To define a group setup handler, add an
- onGroupSetup() element to a group. The argument of onGroupSetup() is a user
- handler. If you add more than one onGroupSetup() element to a group, an assert
- is triggered at runtime that includes an error message.
-
- Like the task's start handler, the group start handler may return SetupResult.
- The returned SetupResult value affects the start behavior of the
- whole group. If you do not specify a group start handler or its return type
- is void, the default group's action is SetupResult::Continue, so that all
- tasks are started normally. Otherwise, when the start handler returns
- SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not
- started (they are skipped) and the group itself reports success or failure,
- depending on the returned value, respectively.
-
- \code
- const Group root {
- onGroupSetup([] { qDebug() << "Root setup"; }),
- Group {
- onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
- QProcessTask(...) // Process 1
- },
- Group {
- onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }),
- QProcessTask(...) // Process 2
- },
- Group {
- onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
- QProcessTask(...) // Process 3
- },
- QProcessTask(...) // Process 4
- };
- \endcode
-
- In the above example, all subgroups of a root group define their setup handlers.
- The following scenario assumes that all started processes finish with success:
-
- \table
- \header
- \li Scenario
- \li Comment
- \row
- \li Root Group starts
- \li Doesn't return SetupResult, so its tasks are executed.
- \row
- \li Group 1 starts
- \li Returns Continue, so its tasks are executed.
- \row
- \li Process 1 starts
- \li
- \row
- \li ...
- \li ...
- \row
- \li Process 1 finishes (success)
- \li
- \row
- \li Group 1 finishes (success)
- \li
- \row
- \li Group 2 starts
- \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports
- success.
- \row
- \li Group 2 finishes (success)
- \li
- \row
- \li Group 3 starts
- \li Returns StopWithError, so Process 3 is skipped and Group 3 reports
- an error.
- \row
- \li Group 3 finishes (error)
- \li
- \row
- \li Root Group finishes (error)
- \li Group 3, which is a direct child of the root group, finished with an
- error, so the root group stops executing, skips Process 4, which has
- not started yet, and reports an error.
- \endtable
-
- \section2 Groups's Done Handler
-
- A Group's done handler is executed after the successful or failed execution of its tasks.
- The final value reported by the group depends on its \l {Workflow Policy}.
- The handler can apply other necessary actions.
- The done handler is defined inside the onGroupDone() element of a group.
- It may take the optional DoneWith argument, indicating the successful or failed execution:
-
- \code
- const Group root {
- onGroupSetup([] { qDebug() << "Root setup"; }),
- QProcessTask(...),
- onGroupDone([](DoneWith result) {
- if (result == DoneWith::Success)
- qDebug() << "Root finished with success";
- else
- qDebug() << "Root finished with an error";
- })
- };
- \endcode
-
- The group done handler is optional. If you add more than one onGroupDone() to a group,
- an assert is triggered at runtime that includes an error message.
-
- \note Even if the group setup handler returns StopWithSuccess or StopWithError,
- the group's done handler is invoked. This behavior differs from that of task done handler
- and might change in the future.
-
- \section1 Other Group Elements
-
- A group can contain other elements that describe the processing flow, such as
- the execution mode or workflow policy. It can also contain storage elements
- that are responsible for collecting and sharing custom common data gathered
- during group execution.
-
- \section2 Execution Mode
-
- The execution mode element in a Group specifies how the direct child tasks of
- the Group are started. The most common execution modes are \l sequential and
- \l parallel. It's also possible to specify the limit of tasks running
- in parallel by using the parallelLimit() function.
-
- In all execution modes, a group starts tasks in the oder in which they appear.
-
- If a child of a group is also a group, the child group runs its tasks
- according to its own execution mode.
-
- \section2 Workflow Policy
-
- The workflow policy element in a Group specifies how the group should behave
- when any of its \e direct child's tasks finish. For a detailed description of possible
- policies, refer to WorkflowPolicy.
-
- If a child of a group is also a group, the child group runs its tasks
- according to its own workflow policy.
-
- \section2 Storage
-
- Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks.
- Especially, in the sequential execution mode, when a task needs data from another,
- already finished task, before it can start. For example, a task tree that copies data by reading
- it from a source and writing it to a destination might look as follows:
-
- \code
- static QByteArray load(const QString &fileName) { ... }
- static void save(const QString &fileName, const QByteArray &array) { ... }
-
- static Group copyRecipe(const QString &source, const QString &destination)
- {
- struct CopyStorage { // [1] custom inter-task struct
- QByteArray content; // [2] custom inter-task data
- };
-
- // [3] instance of custom inter-task struct manageable by task tree
- const Storage<CopyStorage> storage;
-
- const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) {
- async.setConcurrentCallData(&load, source);
- };
- // [4] runtime: task tree activates the instance from [7] before invoking handler
- const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) {
- storage->content = async.result(); // [5] loader stores the result in storage
- };
-
- // [4] runtime: task tree activates the instance from [7] before invoking handler
- const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) {
- const QByteArray content = storage->content; // [6] saver takes data from storage
- async.setConcurrentCallData(&save, destination, content);
- };
- const auto onSaverDone = [](const ConcurrentCall<void> &async) {
- qDebug() << "Save done successfully";
- };
-
- const Group root {
- // [7] runtime: task tree creates an instance of CopyStorage when root is entered
- storage,
- ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success),
- ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success)
- };
- return root;
- }
-
- const QString source = ...;
- const QString destination = ...;
- TaskTree taskTree(copyRecipe(source, destination));
- connect(&taskTree, &TaskTree::done,
- &taskTree, [](DoneWith result) {
- if (result == DoneWith::Success)
- qDebug() << "The copying finished successfully.";
- });
- tasktree.start();
- \endcode
-
- In the example above, the inter-task data consists of a QByteArray content
- variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader
- finishes successfully, it stores the data in a \c CopyStorage::content
- variable [5]. The saver then uses the variable to configure the saving task [6].
-
- To enable a task tree to manage the \c CopyStorage struct, an instance of
- \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is
- inserted as the group's child item [7], an instance of the \c CopyStorage struct is
- created dynamically when the task tree enters this group. When the task
- tree leaves this group, the existing instance of the \c CopyStorage struct is
- destructed as it's no longer needed.
-
- If several task trees holding a copy of the common
- \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously
- (including the case when the task trees are run in different threads),
- each task tree contains its own copy of the \c CopyStorage struct.
-
- You can access \c CopyStorage from any handler in the group with a storage object.
- This includes all handlers of all descendant tasks of the group with
- a storage object. To access the custom struct in a handler, pass the
- copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler
- (for example, in a lambda capture) [4].
-
- When the task tree invokes a handler in a subtree containing the storage [7],
- the task tree activates its own \c CopyStorage instance inside the
- \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct
- may be accessed only from within the handler body. To access the currently active
- \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the
- \l {Tasking::Storage::operator->()} {Storage::operator->()},
- \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
-
- The following list summarizes how to employ a Storage object into the task
- tree:
- \list 1
- \li Define the custom structure \c MyStorage with custom data [1], [2]
- \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3]
- \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4]
- \li Access the \c MyStorage instance in handlers [5], [6]
- \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7]
- \endlist
-
- \section1 TaskTree class
-
- TaskTree executes the tree structure of asynchronous tasks according to the
- recipe described by the Group root element.
-
- As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
- To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask
- element into another Group element.
-
- TaskTree reports progress of completed tasks when running. The progress value
- is increased when a task finishes or is skipped or canceled.
- When TaskTree is finished and the TaskTree::done() signal is emitted,
- the current value of the progress equals the maximum progress value.
- Maximum progress equals the total number of asynchronous tasks in a tree.
- A nested TaskTree is counted as a single task, and its child tasks are not
- counted in the top level tree. Groups themselves are not counted as tasks,
- but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous,
- so they are not counted as tasks.
-
- To set additional initial data for the running tree, modify the storage
- instances in a tree when it creates them by installing a storage setup
- handler:
-
- \code
- Storage<CopyStorage> storage;
- const Group root = ...; // storage placed inside root's group and inside handlers
- TaskTree taskTree(root);
- auto initStorage = [](CopyStorage &storage) {
- storage.content = "initial content";
- };
- taskTree.onStorageSetup(storage, initStorage);
- taskTree.start();
- \endcode
-
- When the running task tree creates a \c CopyStorage instance, and before any
- handler inside a tree is called, the task tree calls the initStorage handler,
- to enable setting up initial data of the storage, unique to this particular
- run of taskTree.
-
- Similarly, to collect some additional result data from the running tree,
- read it from storage instances in the tree when they are about to be
- destroyed. To do this, install a storage done handler:
-
- \code
- Storage<CopyStorage> storage;
- const Group root = ...; // storage placed inside root's group and inside handlers
- TaskTree taskTree(root);
- auto collectStorage = [](const CopyStorage &storage) {
- qDebug() << "final content" << storage.content;
- };
- taskTree.onStorageDone(storage, collectStorage);
- taskTree.start();
- \endcode
-
- When the running task tree is about to destroy a \c CopyStorage instance, the
- task tree calls the collectStorage handler, to enable reading the final data
- from the storage, unique to this particular run of taskTree.
-
- \section1 Task Adapters
-
- To extend a TaskTree with a new task type, implement a simple adapter class
- derived from the TaskAdapter class template. The following class is an
- adapter for a single shot timer, which may be considered as a new asynchronous task:
-
- \code
- class TimerTaskAdapter : public TaskAdapter<QTimer>
- {
- public:
- TimerTaskAdapter() {
- task()->setSingleShot(true);
- task()->setInterval(1000);
- connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); });
- }
- private:
- void start() final { task()->start(); }
- };
-
- using TimerTask = CustomTask<TimerTaskAdapter>;
- \endcode
-
- You must derive the custom adapter from the TaskAdapter class template
- instantiated with a template parameter of the class implementing a running
- task. The code above uses QTimer to run the task. This class appears
- later as an argument to the task's handlers. The instance of this class
- parameter automatically becomes a member of the TaskAdapter template, and is
- accessible through the TaskAdapter::task() method. The constructor
- of \c TimerTaskAdapter initially configures the QTimer object and connects
- to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter
- emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that
- the task finished successfully. If it emits TaskInterface::done(DoneResult::Error),
- the task finished with an error.
- The TaskAdapter::start() method starts the timer.
-
- To make QTimer accessible inside TaskTree under the \c TimerTask name,
- define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>.
- \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter.
-
- The new task type is now registered, and you can use it in TaskTree:
-
- \code
- const auto onSetup = [](QTimer &task) { task.setInterval(2000); };
- const auto onDone = [] { qDebug() << "timer triggered"; };
- const Group root {
- TimerTask(onSetup, onDone)
- };
- \endcode
-
- When a task tree containing the root from the above example is started, it
- prints a debug message within two seconds and then finishes successfully.
-
- \note The class implementing the running task should have a default constructor,
- and objects of this class should be freely destructible. It should be allowed
- to destroy a running object, preferably without waiting for the running task
- to finish (that is, safe non-blocking destructor of a running task).
- To achieve a non-blocking destruction of a task that has a blocking destructor,
- consider using the optional \c Deleter template parameter of the TaskAdapter.
-*/
-
-/*!
- Constructs an empty task tree. Use setRecipe() to pass a declarative description
- on how the task tree should execute the tasks and how it should handle the finished tasks.
-
- Starting an empty task tree is no-op and the relevant warning message is issued.
-
- \sa setRecipe(), start()
-*/
-TaskTree::TaskTree()
- : d(new TaskTreePrivate(this))
-{}
-
-/*!
- \overload
-
- Constructs a task tree with a given \a recipe. After the task tree is started,
- it executes the tasks contained inside the \a recipe and
- handles finished tasks according to the passed description.
-
- \sa setRecipe(), start()
-*/
-TaskTree::TaskTree(const Group &recipe) : TaskTree()
-{
- setRecipe(recipe);
-}
-
-/*!
- Destroys the task tree.
-
- When the task tree is running while being destructed, it cancels all the running tasks
- immediately. In this case, no handlers are called, not even the groups' and
- tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any
- signals from the destructor, not even done() or progressValueChanged() signals.
- This behavior may always be relied on.
- It is completely safe to destruct the running task tree.
-
- It's a usual pattern to destruct the running task tree.
- It's guaranteed that the destruction will run quickly, without having to wait for
- the currently running tasks to finish, provided that the used tasks implement
- their destructors in a non-blocking way.
-
- \note Do not call the destructor directly from any of the running task's handlers
- or task tree's signals. In these cases, use \l deleteLater() instead.
-
- \sa cancel()
-*/
-TaskTree::~TaskTree()
-{
- QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
- "one of its handlers will lead to a crash!"));
- // TODO: delete storages explicitly here?
- delete d;
-}
-
-/*!
- Sets a given \a recipe for the task tree. After the task tree is started,
- it executes the tasks contained inside the \a recipe and
- handles finished tasks according to the passed description.
-
- \note When called for a running task tree, the call is ignored.
-
- \sa TaskTree(const Tasking::Group &recipe), start()
-*/
-void TaskTree::setRecipe(const Group &recipe)
-{
- QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
- QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the"
- "TaskTree handlers, ignoring..."); return);
- // TODO: Should we clear the m_storageHandlers, too?
- d->m_storages.clear();
- d->m_root.emplace(d, recipe);
-}
-
-/*!
- Starts the task tree.
-
- Use setRecipe() or the constructor to set the declarative description according to which
- the task tree will execute the contained tasks and handle finished tasks.
-
- When the task tree is empty, that is, constructed with a default constructor,
- a call to \c start() is no-op and the relevant warning message is issued.
-
- Otherwise, when the task tree is already running, a call to \e start() is ignored and the
- relevant warning message is issued.
-
- Otherwise, the task tree is started.
-
- The started task tree may finish synchronously,
- for example when the main group's start handler returns SetupResult::StopWithError.
- For this reason, the connection to the done signal should be established before calling
- \c start(). Use isRunning() in order to detect whether the task tree is still running
- after a call to \c start().
-
- The task tree implementation relies on the running event loop.
- Make sure you have a QEventLoop or QCoreApplication or one of its
- subclasses running (or about to be run) when calling this method.
-
- \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel()
-*/
-void TaskTree::start()
-{
- QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
- QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
- "TaskTree handlers, ignoring..."); return);
- d->start();
-}
-
-/*!
- \fn void TaskTree::started()
-
- This signal is emitted when the task tree is started. The emission of this signal is
- followed synchronously by the progressValueChanged() signal with an initial \c 0 value.
-
- \sa start(), done()
-*/
-
-/*!
- \fn void TaskTree::done(DoneWith result)
-
- This signal is emitted when the task tree finished, passing the final \a result
- of the execution. The task tree neither calls any handler,
- nor emits any signal anymore after this signal was emitted.
-
- \note Do not delete the task tree directly from this signal's handler.
- Use deleteLater() instead.
-
- \sa started()
-*/
-
-/*!
- Cancels the execution of the running task tree.
-
- Cancels all the running tasks immediately.
- All running tasks finish with an error, invoking their error handlers.
- All running groups dispatch their handlers according to their workflow policies,
- invoking their done handlers. The storages' onStorageDone() handlers are invoked, too.
- The progressValueChanged() signals are also being sent.
- This behavior may always be relied on.
-
- The \c cancel() function is executed synchronously, so that after a call to \c cancel()
- all running tasks are finished and the tree is already canceled.
- It's guaranteed that \c cancel() will run quickly, without any blocking wait for
- the currently running tasks to finish, provided the used tasks implement their destructors
- in a non-blocking way.
-
- When the task tree is empty, that is, constructed with a default constructor,
- a call to \c cancel() is no-op and the relevant warning message is issued.
-
- Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored.
-
- \note Do not call this function directly from any of the running task's handlers
- or task tree's signals.
-
- \sa ~TaskTree()
-*/
-void TaskTree::cancel()
-{
- QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the"
- "TaskTree handlers, ignoring..."); return);
- d->stop();
-}
-
-/*!
- Returns \c true if the task tree is currently running; otherwise returns \c false.
-
- \sa start(), cancel()
-*/
-bool TaskTree::isRunning() const
-{
- return bool(d->m_runtimeRoot);
-}
-
-/*!
- Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
-
- Returns DoneWith::Success if the task tree finished successfully;
- otherwise returns DoneWith::Error.
-
- \note Avoid using this method from the main thread. Use asynchronous start() instead.
- This method is to be used in non-main threads or in auto tests.
-
- \sa start()
-*/
-DoneWith TaskTree::runBlocking()
-{
- QPromise<void> dummy;
- dummy.start();
- return runBlocking(dummy.future());
-}
-
-/*!
- \overload runBlocking()
-
- The passed \a future is used for listening to the cancel event.
- When the task tree is canceled, this method cancels the passed \a future.
-*/
-DoneWith TaskTree::runBlocking(const QFuture<void> &future)
-{
- if (future.isCanceled())
- return DoneWith::Cancel;
-
- DoneWith doneWith = DoneWith::Cancel;
- QEventLoop loop;
- connect(this, &TaskTree::done, &loop, [&loop, &doneWith](DoneWith result) {
- doneWith = result;
- // Otherwise, the tasks from inside the running tree that were deleteLater()
- // will be leaked. Refer to the QObject::deleteLater() docs.
- QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection);
- });
- QFutureWatcher<void> watcher;
- connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::cancel);
- watcher.setFuture(future);
-
- QTimer::singleShot(0, this, &TaskTree::start);
-
- loop.exec(QEventLoop::ExcludeUserInputEvents);
- if (doneWith == DoneWith::Cancel) {
- auto nonConstFuture = future;
- nonConstFuture.cancel();
- }
- return doneWith;
-}
-
-/*!
- Constructs a temporary task tree using the passed \a recipe and runs it blocking.
-
- Returns DoneWith::Success if the task tree finished successfully;
- otherwise returns DoneWith::Error.
-
- \note Avoid using this method from the main thread. Use asynchronous start() instead.
- This method is to be used in non-main threads or in auto tests.
-
- \sa start()
-*/
-DoneWith TaskTree::runBlocking(const Group &recipe)
-{
- QPromise<void> dummy;
- dummy.start();
- return TaskTree::runBlocking(recipe, dummy.future());
-}
-
-/*!
- \overload runBlocking(const Group &recipe)
-
- The passed \a future is used for listening to the cancel event.
- When the task tree is canceled, this method cancels the passed \a future.
-*/
-DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future)
-{
- TaskTree taskTree(recipe);
- return taskTree.runBlocking(future);
-}
-
-/*!
- Returns the current real count of asynchronous chains of invocations.
-
- The returned value indicates how many times the control returns to the caller's
- event loop while the task tree is running. Initially, this value is 0.
- If the execution of the task tree finishes fully synchronously, this value remains 0.
- If the task tree contains any asynchronous tasks that are successfully started during
- a call to start(), this value is bumped to 1 just before the call to start() finishes.
- Later, when any asynchronous task finishes and any possible continuations are started,
- this value is bumped again. The bumping continues until the task tree finishes.
- When the task tree emits the done() signal, the bumping stops.
- The asyncCountChanged() signal is emitted on every bump of this value.
-
- \sa asyncCountChanged()
-*/
-int TaskTree::asyncCount() const
-{
- return d->m_asyncCount;
-}
-
-/*!
- \fn void TaskTree::asyncCountChanged(int count)
-
- This signal is emitted when the running task tree is about to return control to the caller's
- event loop. When the task tree is started, this signal is emitted with \a count value of 0,
- and emitted later on every asyncCount() value bump with an updated \a count value.
- Every signal sent (except the initial one with the value of 0) guarantees that the task tree
- is still running asynchronously after the emission.
-
- \sa asyncCount()
-*/
-
-/*!
- Returns the number of asynchronous tasks contained in the stored recipe.
-
- \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks.
- \note Any task or group that was set up using withTimeout() increases the total number of
- tasks by \c 1.
-
- \sa setRecipe(), progressMaximum()
-*/
-int TaskTree::taskCount() const
-{
- return d->m_root ? d->m_root->taskCount() : 0;
-}
-
-/*!
- \fn void TaskTree::progressValueChanged(int value)
-
- This signal is emitted when the running task tree finished, canceled, or skipped some tasks.
- The \a value gives the current total number of finished, canceled or skipped tasks.
- When the task tree is started, and after the started() signal was emitted,
- this signal is emitted with an initial \a value of \c 0.
- When the task tree is about to finish, and before the done() signal is emitted,
- this signal is emitted with the final \a value of progressMaximum().
-
- \sa progressValue(), progressMaximum()
-*/
-
-/*!
- \fn int TaskTree::progressMaximum() const
-
- Returns the maximum progressValue().
-
- \note Currently, it's the same as taskCount(). This might change in the future.
-
- \sa progressValue()
-*/
-
-/*!
- Returns the current progress value, which is between the \c 0 and progressMaximum().
-
- The returned number indicates how many tasks have been already finished, canceled, or skipped
- while the task tree is running.
- When the task tree is started, this number is set to \c 0.
- When the task tree is finished, this number always equals progressMaximum().
-
- \sa progressMaximum(), progressValueChanged()
-*/
-int TaskTree::progressValue() const
-{
- return d->m_progressValue;
-}
-
-/*!
- \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler)
-
- Installs a storage setup \a handler for the \a storage to pass the initial data
- dynamically to the running task tree.
-
- The \c StorageHandler takes a \e reference to the \c StorageStruct instance:
-
- \code
- static void save(const QString &fileName, const QByteArray &array) { ... }
-
- Storage<QByteArray> storage;
-
- const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) {
- concurrent.setConcurrentCallData(&save, "foo.txt", *storage);
- };
-
- const Group root {
- storage,
- ConcurrentCallTask(onSaverSetup)
- };
-
- TaskTree taskTree(root);
- auto initStorage = [](QByteArray &storage){
- storage = "initial content";
- };
- taskTree.onStorageSetup(storage, initStorage);
- taskTree.start();
- \endcode
-
- When the running task tree enters a Group where the \a storage is placed in,
- it creates a \c StorageStruct instance, ready to be used inside this group.
- Just after the \c StorageStruct instance is created, and before any handler of this group
- is called, the task tree invokes the passed \a handler. This enables setting up
- initial content for the given storage dynamically. Later, when any group's handler is invoked,
- the task tree activates the created and initialized storage, so that it's available inside
- any group's handler.
-
- \sa onStorageDone()
-*/
-
-/*!
- \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler)
-
- Installs a storage done \a handler for the \a storage to retrieve the final data
- dynamically from the running task tree.
-
- The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance:
-
- \code
- static QByteArray load(const QString &fileName) { ... }
-
- Storage<QByteArray> storage;
-
- const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) {
- concurrent.setConcurrentCallData(&load, "foo.txt");
- };
- const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) {
- *storage = concurrent.result();
- };
-
- const Group root {
- storage,
- ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success)
- };
-
- TaskTree taskTree(root);
- auto collectStorage = [](const QByteArray &storage){
- qDebug() << "final content" << storage;
- };
- taskTree.onStorageDone(storage, collectStorage);
- taskTree.start();
- \endcode
-
- When the running task tree is about to leave a Group where the \a storage is placed in,
- it destructs a \c StorageStruct instance.
- Just before the \c StorageStruct instance is destructed, and after all possible handlers from
- this group were called, the task tree invokes the passed \a handler. This enables reading
- the final content of the given storage dynamically and processing it further outside of
- the task tree.
-
- This handler is called also when the running tree is canceled. However, it's not called
- when the running tree is destructed.
-
- \sa onStorageSetup()
-*/
-
-void TaskTree::setupStorageHandler(const StorageBase &storage,
- const StorageBase::StorageHandler &setupHandler,
- const StorageBase::StorageHandler &doneHandler)
-{
- auto it = d->m_storageHandlers.find(storage);
- if (it == d->m_storageHandlers.end()) {
- d->m_storageHandlers.insert(storage, {setupHandler, doneHandler});
- return;
- }
- if (setupHandler) {
- QT_ASSERT(!it->m_setupHandler,
- qWarning("The storage has its setup handler defined, overriding..."));
- it->m_setupHandler = setupHandler;
- }
- if (doneHandler) {
- QT_ASSERT(!it->m_doneHandler,
- qWarning("The storage has its done handler defined, overriding..."));
- it->m_doneHandler = doneHandler;
- }
-}
-
-TaskTreeTaskAdapter::TaskTreeTaskAdapter()
-{
- connect(task(), &TaskTree::done, this,
- [this](DoneWith result) { emit done(toDoneResult(result)); });
-}
-
-void TaskTreeTaskAdapter::start()
-{
- task()->start();
-}
-
-using TimeoutCallback = std::function<void()>;
-
-struct TimerData
-{
- system_clock::time_point m_deadline;
- QPointer<QObject> m_context;
- TimeoutCallback m_callback;
-};
-
-struct TimerThreadData
-{
- Q_DISABLE_COPY_MOVE(TimerThreadData)
-
- TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20
- QHash<int, TimerData> m_timerIdToTimerData = {};
- QMap<system_clock::time_point, QList<int>> m_deadlineToTimerId = {};
- int m_timerIdCounter = 0;
-};
-
-// Please note the thread_local keyword below guarantees a separate instance per thread.
-static thread_local TimerThreadData s_threadTimerData = {};
-
-static void removeTimerId(int timerId)
-{
- const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(timerId);
- QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(),
- qWarning("Removing active timerId failed."); return);
-
- const system_clock::time_point deadline = it->m_deadline;
- s_threadTimerData.m_timerIdToTimerData.erase(it);
-
- QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline];
- const int removedCount = ids.removeAll(timerId);
- QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return);
- if (ids.isEmpty())
- s_threadTimerData.m_deadlineToTimerId.remove(deadline);
-}
-
-static void handleTimeout(int timerId)
-{
- const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(timerId);
- if (itData == s_threadTimerData.m_timerIdToTimerData.cend())
- return; // The timer was already activated.
-
- const auto deadline = itData->m_deadline;
- while (true) {
- auto itMap = s_threadTimerData.m_deadlineToTimerId.begin();
- if (itMap == s_threadTimerData.m_deadlineToTimerId.end())
- return;
-
- if (itMap.key() > deadline)
- return;
-
- std::optional<TimerData> timerData;
- QList<int> &idList = *itMap;
- if (!idList.isEmpty()) {
- const int first = idList.first();
- idList.removeFirst();
-
- const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(first);
- if (it != s_threadTimerData.m_timerIdToTimerData.cend()) {
- timerData = it.value();
- s_threadTimerData.m_timerIdToTimerData.erase(it);
- } else {
- QT_CHECK(false);
- }
- } else {
- QT_CHECK(false);
- }
-
- if (idList.isEmpty())
- s_threadTimerData.m_deadlineToTimerId.erase(itMap);
- if (timerData && timerData->m_context)
- timerData->m_callback();
- }
-}
-
-static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
-{
- const int timerId = ++s_threadTimerData.m_timerIdCounter;
- const system_clock::time_point deadline = system_clock::now() + timeout;
- QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); });
- s_threadTimerData.m_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback});
- s_threadTimerData.m_deadlineToTimerId[deadline].append(timerId);
- return timerId;
-}
-
-TimeoutTaskAdapter::TimeoutTaskAdapter()
-{
- *task() = milliseconds::zero();
-}
-
-TimeoutTaskAdapter::~TimeoutTaskAdapter()
-{
- if (m_timerId)
- removeTimerId(*m_timerId);
-}
-
-void TimeoutTaskAdapter::start()
-{
- m_timerId = scheduleTimeout(*task(), this, [this] {
- m_timerId.reset();
- emit done(DoneResult::Success);
- });
-}
-
-ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, DoneResult result)
-{
- return TimeoutTask([timeout](std::chrono::milliseconds &t) { t = timeout; }, result);
-}
-
-/*!
- \typealias Tasking::TaskTreeTask
-
- Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task.
-*/
-
-/*!
- \typealias Tasking::TimeoutTask
-
- Type alias for the CustomTask, to be used inside recipes, associated with the
- \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the
- timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is,
- the TimeoutTask finishes as soon as the control returns to the running event loop.
-
- Example usage:
-
- \code
- using namespace std::chrono;
- using namespace std::chrono_literals;
-
- const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; }
- const auto onDone = [] { qDebug() << "Timed out."; }
-
- const Group root {
- Timeout(onSetup, onDone)
- };
- \endcode
-*/
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/tasktree.h b/src/assets/downloader/tasking/tasktree.h
deleted file mode 100644
index d46bd8eee3e..00000000000
--- a/src/assets/downloader/tasking/tasktree.h
+++ /dev/null
@@ -1,757 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_TASKTREE_H
-#define TASKING_TASKTREE_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-
-#include <QtCore/QList>
-#include <QtCore/QObject>
-
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-template <class T>
-class QFuture;
-
-namespace Tasking {
-
-class Do;
-class For;
-class Group;
-class GroupItem;
-using GroupItems = QList<GroupItem>;
-
-Q_NAMESPACE_EXPORT(TASKING_EXPORT)
-
-// WorkflowPolicy:
-// 1. When all children finished with success -> report success, otherwise:
-// a) Report error on first error and stop executing other children (including their subtree).
-// b) On first error - continue executing all children and report error afterwards.
-// 2. When all children finished with error -> report error, otherwise:
-// a) Report success on first success and stop executing other children (including their subtree).
-// b) On first success - continue executing all children and report success afterwards.
-// 3. Stops on first finished child. In sequential mode it will never run other children then the first one.
-// Useful only in parallel mode.
-// 4. Always run all children, let them finish, ignore their results and report success afterwards.
-// 5. Always run all children, let them finish, ignore their results and report error afterwards.
-
-enum class WorkflowPolicy
-{
- StopOnError, // 1a - Reports error on first child error, otherwise success (if all children were success).
- ContinueOnError, // 1b - The same, but children execution continues. Reports success when no children.
- StopOnSuccess, // 2a - Reports success on first child success, otherwise error (if all children were error).
- ContinueOnSuccess, // 2b - The same, but children execution continues. Reports error when no children.
- StopOnSuccessOrError, // 3 - Stops on first finished child and report its result.
- FinishAllAndSuccess, // 4 - Reports success after all children finished.
- FinishAllAndError // 5 - Reports error after all children finished.
-};
-Q_ENUM_NS(WorkflowPolicy)
-
-enum class SetupResult
-{
- Continue,
- StopWithSuccess,
- StopWithError
-};
-Q_ENUM_NS(SetupResult)
-
-enum class DoneResult
-{
- Success,
- Error
-};
-Q_ENUM_NS(DoneResult)
-
-enum class DoneWith
-{
- Success,
- Error,
- Cancel
-};
-Q_ENUM_NS(DoneWith)
-
-enum class CallDoneIf
-{
- SuccessOrError,
- Success,
- Error
-};
-Q_ENUM_NS(CallDoneIf)
-
-TASKING_EXPORT DoneResult toDoneResult(bool success);
-
-class LoopData;
-class StorageData;
-class TaskTreePrivate;
-
-class TASKING_EXPORT TaskInterface : public QObject
-{
- Q_OBJECT
-
-Q_SIGNALS:
- void done(DoneResult result);
-
-private:
- template <typename Task, typename Deleter> friend class TaskAdapter;
- friend class TaskTreePrivate;
- TaskInterface() = default;
-#ifdef Q_QDOC
-protected:
-#endif
- virtual void start() = 0;
-};
-
-class TASKING_EXPORT Loop
-{
-public:
- using Condition = std::function<bool(int)>; // Takes iteration, called prior to each iteration.
- using ValueGetter = std::function<const void *(int)>; // Takes iteration, returns ptr to ref.
-
- int iteration() const;
-
-protected:
- Loop(); // LoopForever
- Loop(int count, const ValueGetter &valueGetter = {}); // LoopRepeat, LoopList
- Loop(const Condition &condition); // LoopUntil
-
- const void *valuePtr() const;
-
-private:
- friend class ExecutionContextActivator;
- friend class TaskTreePrivate;
- std::shared_ptr<LoopData> m_loopData;
-};
-
-class TASKING_EXPORT LoopForever final : public Loop
-{
-public:
- LoopForever() : Loop() {}
-};
-
-class TASKING_EXPORT LoopRepeat final : public Loop
-{
-public:
- LoopRepeat(int count) : Loop(count) {}
-};
-
-class TASKING_EXPORT LoopUntil final : public Loop
-{
-public:
- LoopUntil(const Condition &condition) : Loop(condition) {}
-};
-
-template <typename T>
-class LoopList final : public Loop
-{
-public:
- LoopList(const QList<T> &list) : Loop(list.size(), [list](int i) { return &list.at(i); }) {}
- const T *operator->() const { return static_cast<const T *>(valuePtr()); }
- const T &operator*() const { return *static_cast<const T *>(valuePtr()); }
-};
-
-class TASKING_EXPORT StorageBase
-{
-private:
- using StorageConstructor = std::function<void *(void)>;
- using StorageDestructor = std::function<void(void *)>;
- using StorageHandler = std::function<void(void *)>;
-
- StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor);
-
- void *activeStorageVoid() const;
-
- friend bool operator==(const StorageBase &first, const StorageBase &second)
- { return first.m_storageData == second.m_storageData; }
-
- friend bool operator!=(const StorageBase &first, const StorageBase &second)
- { return first.m_storageData != second.m_storageData; }
-
- friend size_t qHash(const StorageBase &storage, uint seed = 0)
- { return size_t(storage.m_storageData.get()) ^ seed; }
-
- std::shared_ptr<StorageData> m_storageData;
-
- template <typename StorageStruct> friend class Storage;
- friend class ExecutionContextActivator;
- friend class StorageData;
- friend class RuntimeContainer;
- friend class TaskTree;
- friend class TaskTreePrivate;
-};
-
-template <typename StorageStruct>
-class Storage final : public StorageBase
-{
-public:
- Storage() : StorageBase(Storage::ctor(), Storage::dtor()) {}
-#if __cplusplus >= 201803L // C++20: Allow pack expansion in lambda init-capture.
- template <typename ...Args>
- Storage(const Args &...args)
- : StorageBase([...args = args] { return new StorageStruct(args...); }, Storage::dtor()) {}
-#else // C++17
- template <typename ...Args>
- Storage(const Args &...args)
- : StorageBase([argsTuple = std::tuple(args...)] {
- return std::apply([](const Args &...arguments) { return new StorageStruct(arguments...); }, argsTuple);
- }, Storage::dtor()) {}
-#endif
- StorageStruct &operator*() const noexcept { return *activeStorage(); }
- StorageStruct *operator->() const noexcept { return activeStorage(); }
- StorageStruct *activeStorage() const {
- return static_cast<StorageStruct *>(activeStorageVoid());
- }
-
-private:
- static StorageConstructor ctor() { return [] { return new StorageStruct(); }; }
- static StorageDestructor dtor() {
- return [](void *storage) { delete static_cast<StorageStruct *>(storage); };
- }
-};
-
-class TASKING_EXPORT GroupItem
-{
-public:
- // Called when group entered, after group's storages are created
- using GroupSetupHandler = std::function<SetupResult()>;
- // Called when group done, before group's storages are deleted
- using GroupDoneHandler = std::function<DoneResult(DoneWith)>;
-
- template <typename StorageStruct>
- GroupItem(const Storage<StorageStruct> &storage)
- : m_type(Type::Storage)
- , m_storageList{storage} {}
-
- // TODO: Add tests.
- GroupItem(const GroupItems &children) : m_type(Type::List) { addChildren(children); }
- GroupItem(std::initializer_list<GroupItem> children) : m_type(Type::List) { addChildren(children); }
-
-protected:
- GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {}
- // Internal, provided by CustomTask
- using InterfaceCreateHandler = std::function<TaskInterface *(void)>;
- // Called prior to task start, just after createHandler
- using InterfaceSetupHandler = std::function<SetupResult(TaskInterface &)>;
- // Called on task done, just before deleteLater
- using InterfaceDoneHandler = std::function<DoneResult(const TaskInterface &, DoneWith)>;
-
- struct TaskHandler {
- InterfaceCreateHandler m_createHandler;
- InterfaceSetupHandler m_setupHandler = {};
- InterfaceDoneHandler m_doneHandler = {};
- CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError;
- };
-
- struct GroupHandler {
- GroupSetupHandler m_setupHandler;
- GroupDoneHandler m_doneHandler = {};
- CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError;
- };
-
- struct GroupData {
- GroupHandler m_groupHandler = {};
- std::optional<int> m_parallelLimit = {};
- std::optional<WorkflowPolicy> m_workflowPolicy = {};
- std::optional<Loop> m_loop = {};
- };
-
- enum class Type {
- List,
- Group,
- GroupData,
- Storage,
- TaskHandler
- };
-
- GroupItem() = default;
- GroupItem(Type type) : m_type(type) { }
- GroupItem(const GroupData &data)
- : m_type(Type::GroupData)
- , m_groupData(data) {}
- GroupItem(const TaskHandler &handler)
- : m_type(Type::TaskHandler)
- , m_taskHandler(handler) {}
- void addChildren(const GroupItems &children);
-
- static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); }
-
- // Checks if Function may be invoked with Args and if Function's return type is Result.
- template <typename Result, typename Function, typename ...Args,
- typename DecayedFunction = std::decay_t<Function>>
- static constexpr bool isInvocable()
- {
- // Note, that std::is_invocable_r_v doesn't check Result type properly.
- if constexpr (std::is_invocable_r_v<Result, DecayedFunction, Args...>)
- return std::is_same_v<Result, std::invoke_result_t<DecayedFunction, Args...>>;
- return false;
- }
-
-private:
- TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem);
- friend class ContainerNode;
- friend class TaskNode;
- friend class TaskTreePrivate;
- friend class ParallelLimitFunctor;
- friend class WorkflowPolicyFunctor;
- Type m_type = Type::Group;
- GroupItems m_children;
- GroupData m_groupData;
- QList<StorageBase> m_storageList;
- TaskHandler m_taskHandler;
-};
-
-class TASKING_EXPORT ExecutableItem : public GroupItem
-{
-public:
- Group withTimeout(std::chrono::milliseconds timeout,
- const std::function<void()> &handler = {}) const;
- Group withLog(const QString &logName) const;
- template <typename SenderSignalPairGetter>
- Group withCancel(SenderSignalPairGetter &&getter, std::initializer_list<GroupItem> postCancelRecipe = {}) const;
- template <typename SenderSignalPairGetter>
- Group withAccept(SenderSignalPairGetter &&getter) const;
-
-protected:
- ExecutableItem() = default;
- ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {}
-
-private:
- TASKING_EXPORT friend Group operator!(const ExecutableItem &item);
- TASKING_EXPORT friend Group operator&&(const ExecutableItem &first,
- const ExecutableItem &second);
- TASKING_EXPORT friend Group operator||(const ExecutableItem &first,
- const ExecutableItem &second);
- TASKING_EXPORT friend Group operator&&(const ExecutableItem &item, DoneResult result);
- TASKING_EXPORT friend Group operator||(const ExecutableItem &item, DoneResult result);
-
- Group withCancelImpl(
- const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper,
- const GroupItems &postCancelRecipe) const;
- Group withAcceptImpl(
- const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const;
-};
-
-class TASKING_EXPORT Group : public ExecutableItem
-{
-public:
- Group(const GroupItems &children) { addChildren(children); }
- Group(std::initializer_list<GroupItem> children) { addChildren(children); }
-
- // GroupData related:
- template <typename Handler>
- static GroupItem onGroupSetup(Handler &&handler) {
- return groupHandler({wrapGroupSetup(std::forward<Handler>(handler))});
- }
- template <typename Handler>
- static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) {
- return groupHandler({{}, wrapGroupDone(std::forward<Handler>(handler)), callDoneIf});
- }
-
-private:
- template <typename Handler>
- static GroupSetupHandler wrapGroupSetup(Handler &&handler)
- {
- // R, V stands for: Setup[R]esult, [V]oid
- static constexpr bool isR = isInvocable<SetupResult, Handler>();
- static constexpr bool isV = isInvocable<void, Handler>();
- static_assert(isR || isV,
- "Group setup handler needs to take no arguments and has to return void or SetupResult. "
- "The passed handler doesn't fulfill these requirements.");
- return [handler = std::move(handler)] {
- if constexpr (isR)
- return std::invoke(handler);
- std::invoke(handler);
- return SetupResult::Continue;
- };
- }
- template <typename Handler>
- static GroupDoneHandler wrapGroupDone(Handler &&handler)
- {
- static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>;
- // R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith
- static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
- static constexpr bool isR = isInvocable<DoneResult, Handler>();
- static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
- static constexpr bool isB = isInvocable<bool, Handler>();
- static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
- static constexpr bool isV = isInvocable<void, Handler>();
- static_assert(isDoneResultType || isRD || isR || isBD || isB || isVD || isV,
- "Group done handler needs to take (DoneWith) or (void) as an argument and has to "
- "return void, bool or DoneResult. Alternatively, it may be of DoneResult type. "
- "The passed handler doesn't fulfill these requirements.");
- return [handler = std::move(handler)](DoneWith result) {
- if constexpr (isDoneResultType)
- return handler;
- if constexpr (isRD)
- return std::invoke(handler, result);
- if constexpr (isR)
- return std::invoke(handler);
- if constexpr (isBD)
- return toDoneResult(std::invoke(handler, result));
- if constexpr (isB)
- return toDoneResult(std::invoke(handler));
- if constexpr (isVD)
- std::invoke(handler, result);
- else if constexpr (isV)
- std::invoke(handler);
- return toDoneResult(result == DoneWith::Success);
- };
- }
-};
-
-template <typename SenderSignalPairGetter>
-Group ExecutableItem::withCancel(SenderSignalPairGetter &&getter,
- std::initializer_list<GroupItem> postCancelRecipe) const
-{
- const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) {
- const auto senderSignalPair = getter();
- QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] {
- trigger();
- }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
- };
- return withCancelImpl(connectWrapper, postCancelRecipe);
-}
-
-template <typename SenderSignalPairGetter>
-Group ExecutableItem::withAccept(SenderSignalPairGetter &&getter) const
-{
- const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) {
- const auto senderSignalPair = getter();
- QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] {
- trigger();
- }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
- };
- return withAcceptImpl(connectWrapper);
-}
-
-template <typename Handler>
-static GroupItem onGroupSetup(Handler &&handler)
-{
- return Group::onGroupSetup(std::forward<Handler>(handler));
-}
-
-template <typename Handler>
-static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
-{
- return Group::onGroupDone(std::forward<Handler>(handler), callDoneIf);
-}
-
-// Default: 1 (sequential). 0 means unlimited (parallel).
-TASKING_EXPORT GroupItem parallelLimit(int limit);
-
-// Default: WorkflowPolicy::StopOnError.
-TASKING_EXPORT GroupItem workflowPolicy(WorkflowPolicy policy);
-
-TASKING_EXPORT extern const GroupItem sequential;
-TASKING_EXPORT extern const GroupItem parallel;
-TASKING_EXPORT extern const GroupItem parallelIdealThreadCountLimit;
-
-TASKING_EXPORT extern const GroupItem stopOnError;
-TASKING_EXPORT extern const GroupItem continueOnError;
-TASKING_EXPORT extern const GroupItem stopOnSuccess;
-TASKING_EXPORT extern const GroupItem continueOnSuccess;
-TASKING_EXPORT extern const GroupItem stopOnSuccessOrError;
-TASKING_EXPORT extern const GroupItem finishAllAndSuccess;
-TASKING_EXPORT extern const GroupItem finishAllAndError;
-
-TASKING_EXPORT extern const GroupItem nullItem;
-TASKING_EXPORT extern const ExecutableItem successItem;
-TASKING_EXPORT extern const ExecutableItem errorItem;
-
-class TASKING_EXPORT For final
-{
-public:
- explicit For(const Loop &loop) : m_loop(loop) {}
-
-private:
- TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem);
-
- Loop m_loop;
-};
-
-class When;
-
-class TASKING_EXPORT Do final
-{
-public:
- explicit Do(const GroupItems &children) : m_children(children) {}
- explicit Do(std::initializer_list<GroupItem> children) : m_children(children) {}
-
-private:
- TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem);
- TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem);
-
- GroupItem m_children;
-};
-
-class TASKING_EXPORT Forever final : public ExecutableItem
-{
-public:
- explicit Forever(const GroupItems &children)
- { addChildren({ For (LoopForever()) >> Do { children } } ); }
- explicit Forever(std::initializer_list<GroupItem> children)
- { addChildren({ For (LoopForever()) >> Do { children } } ); }
-};
-
-// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
-class TASKING_EXPORT Sync final : public ExecutableItem
-{
-public:
- template <typename Handler>
- Sync(Handler &&handler) {
- addChildren({ onGroupDone(wrapHandler(std::forward<Handler>(handler))) });
- }
-
-private:
- template <typename Handler>
- static auto wrapHandler(Handler &&handler) {
- // R, B, V stands for: Done[R]esult, [B]ool, [V]oid
- static constexpr bool isR = isInvocable<DoneResult, Handler>();
- static constexpr bool isB = isInvocable<bool, Handler>();
- static constexpr bool isV = isInvocable<void, Handler>();
- static_assert(isR || isB || isV,
- "Sync handler needs to take no arguments and has to return void, bool or DoneResult. "
- "The passed handler doesn't fulfill these requirements.");
- return handler;
- }
-};
-
-template <typename Task, typename Deleter = std::default_delete<Task>>
-class TaskAdapter : public TaskInterface
-{
-protected:
- TaskAdapter() : m_task(new Task) {}
- Task *task() { return m_task.get(); }
- const Task *task() const { return m_task.get(); }
-
-private:
- using TaskType = Task;
- using DeleterType = Deleter;
- template <typename Adapter> friend class CustomTask;
- std::unique_ptr<Task, Deleter> m_task;
-};
-
-template <typename Adapter>
-class CustomTask final : public ExecutableItem
-{
-public:
- using Task = typename Adapter::TaskType;
- using Deleter = typename Adapter::DeleterType;
- static_assert(std::is_base_of_v<TaskAdapter<Task, Deleter>, Adapter>,
- "The Adapter type for the CustomTask<Adapter> needs to be derived from "
- "TaskAdapter<Task>.");
- using TaskSetupHandler = std::function<SetupResult(Task &)>;
- using TaskDoneHandler = std::function<DoneResult(const Task &, DoneWith)>;
-
- template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler>
- CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(),
- CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
- : ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
- wrapDone(std::forward<DoneHandler>(done)), callDoneIf})
- {}
-
-private:
- static Adapter *createAdapter() { return new Adapter; }
-
- template <typename Handler>
- static InterfaceSetupHandler wrapSetup(Handler &&handler) {
- if constexpr (std::is_same_v<Handler, TaskSetupHandler>)
- return {}; // When user passed {} for the setup handler.
- // R, V stands for: Setup[R]esult, [V]oid
- static constexpr bool isR = isInvocable<SetupResult, Handler, Task &>();
- static constexpr bool isV = isInvocable<void, Handler, Task &>();
- static_assert(isR || isV,
- "Task setup handler needs to take (Task &) as an argument and has to return void or "
- "SetupResult. The passed handler doesn't fulfill these requirements.");
- return [handler = std::move(handler)](TaskInterface &taskInterface) {
- Adapter &adapter = static_cast<Adapter &>(taskInterface);
- if constexpr (isR)
- return std::invoke(handler, *adapter.task());
- std::invoke(handler, *adapter.task());
- return SetupResult::Continue;
- };
- }
-
- template <typename Handler>
- static InterfaceDoneHandler wrapDone(Handler &&handler) {
- if constexpr (std::is_same_v<Handler, TaskDoneHandler>)
- return {}; // User passed {} for the done handler.
- static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>;
- // R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith
- static constexpr bool isRTD = isInvocable<DoneResult, Handler, const Task &, DoneWith>();
- static constexpr bool isRT = isInvocable<DoneResult, Handler, const Task &>();
- static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>();
- static constexpr bool isR = isInvocable<DoneResult, Handler>();
- static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>();
- static constexpr bool isBT = isInvocable<bool, Handler, const Task &>();
- static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>();
- static constexpr bool isB = isInvocable<bool, Handler>();
- static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>();
- static constexpr bool isVT = isInvocable<void, Handler, const Task &>();
- static constexpr bool isVD = isInvocable<void, Handler, DoneWith>();
- static constexpr bool isV = isInvocable<void, Handler>();
- static_assert(isDoneResultType || isRTD || isRT || isRD || isR
- || isBTD || isBT || isBD || isB
- || isVTD || isVT || isVD || isV,
- "Task done handler needs to take (const Task &, DoneWith), (const Task &), "
- "(DoneWith) or (void) as arguments and has to return void, bool or DoneResult. "
- "Alternatively, it may be of DoneResult type. "
- "The passed handler doesn't fulfill these requirements.");
- return [handler = std::move(handler)](const TaskInterface &taskInterface, DoneWith result) {
- if constexpr (isDoneResultType)
- return handler;
- const Adapter &adapter = static_cast<const Adapter &>(taskInterface);
- if constexpr (isRTD)
- return std::invoke(handler, *adapter.task(), result);
- if constexpr (isRT)
- return std::invoke(handler, *adapter.task());
- if constexpr (isRD)
- return std::invoke(handler, result);
- if constexpr (isR)
- return std::invoke(handler);
- if constexpr (isBTD)
- return toDoneResult(std::invoke(handler, *adapter.task(), result));
- if constexpr (isBT)
- return toDoneResult(std::invoke(handler, *adapter.task()));
- if constexpr (isBD)
- return toDoneResult(std::invoke(handler, result));
- if constexpr (isB)
- return toDoneResult(std::invoke(handler));
- if constexpr (isVTD)
- std::invoke(handler, *adapter.task(), result);
- else if constexpr (isVT)
- std::invoke(handler, *adapter.task());
- else if constexpr (isVD)
- std::invoke(handler, result);
- else if constexpr (isV)
- std::invoke(handler);
- return toDoneResult(result == DoneWith::Success);
- };
- }
-};
-
-template <typename Task>
-class SimpleTaskAdapter final : public TaskAdapter<Task>
-{
-public:
- SimpleTaskAdapter() { this->connect(this->task(), &Task::done, this, &TaskInterface::done); }
- void start() final { this->task()->start(); }
-};
-
-// A convenient helper, when:
-// 1. Task is derived from QObject.
-// 2. Task::start() method starts the task.
-// 3. Task::done(DoneResult) signal is emitted when the task is finished.
-template <typename Task>
-using SimpleCustomTask = CustomTask<SimpleTaskAdapter<Task>>;
-
-class TASKING_EXPORT TaskTree final : public QObject
-{
- Q_OBJECT
-
-public:
- TaskTree();
- TaskTree(const Group &recipe);
- ~TaskTree();
-
- void setRecipe(const Group &recipe);
-
- void start();
- void cancel();
- bool isRunning() const;
-
- // Helper methods. They execute a local event loop with ExcludeUserInputEvents.
- // The passed future is used for listening to the cancel event.
- // Don't use it in main thread. To be used in non-main threads or in auto tests.
- DoneWith runBlocking();
- DoneWith runBlocking(const QFuture<void> &future);
- static DoneWith runBlocking(const Group &recipe);
- static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future);
-
- int asyncCount() const;
- int taskCount() const;
- int progressMaximum() const { return taskCount(); }
- int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded
-
- template <typename StorageStruct, typename Handler>
- void onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) {
- static_assert(std::is_invocable_v<std::decay_t<Handler>, StorageStruct &>,
- "Storage setup handler needs to take (Storage &) as an argument. "
- "The passed handler doesn't fulfill this requirement.");
- setupStorageHandler(storage,
- wrapHandler<StorageStruct>(std::forward<Handler>(handler)), {});
- }
- template <typename StorageStruct, typename Handler>
- void onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) {
- static_assert(std::is_invocable_v<std::decay_t<Handler>, const StorageStruct &>,
- "Storage done handler needs to take (const Storage &) as an argument. "
- "The passed handler doesn't fulfill this requirement.");
- setupStorageHandler(storage, {},
- wrapHandler<const StorageStruct>(std::forward<Handler>(handler)));
- }
-
-Q_SIGNALS:
- void started();
- void done(DoneWith result);
- void asyncCountChanged(int count);
- void progressValueChanged(int value); // updated whenever task finished / skipped / stopped
-
-private:
- void setupStorageHandler(const StorageBase &storage,
- const StorageBase::StorageHandler &setupHandler,
- const StorageBase::StorageHandler &doneHandler);
- template <typename StorageStruct, typename Handler>
- StorageBase::StorageHandler wrapHandler(Handler &&handler) {
- return [handler](void *voidStruct) {
- auto *storageStruct = static_cast<StorageStruct *>(voidStruct);
- std::invoke(handler, *storageStruct);
- };
- }
-
- TaskTreePrivate *d;
-};
-
-class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree>
-{
-public:
- TaskTreeTaskAdapter();
-
-private:
- void start() final;
-};
-
-class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds>
-{
-public:
- TimeoutTaskAdapter();
- ~TimeoutTaskAdapter();
-
-private:
- void start() final;
- std::optional<int> m_timerId;
-};
-
-using TaskTreeTask = CustomTask<TaskTreeTaskAdapter>;
-using TimeoutTask = CustomTask<TimeoutTaskAdapter>;
-
-TASKING_EXPORT ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout,
- DoneResult result = DoneResult::Error);
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_TASKTREE_H
diff --git a/src/assets/downloader/tasking/tasktreerunner.cpp b/src/assets/downloader/tasking/tasktreerunner.cpp
deleted file mode 100644
index 6ed642b1bfd..00000000000
--- a/src/assets/downloader/tasking/tasktreerunner.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "tasktreerunner.h"
-
-#include "tasktree.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-TaskTreeRunner::~TaskTreeRunner() = default;
-
-void TaskTreeRunner::start(const Group &recipe,
- const SetupHandler &setupHandler,
- const DoneHandler &doneHandler)
-{
- m_taskTree.reset(new TaskTree(recipe));
- connect(m_taskTree.get(), &TaskTree::done, this, [this, doneHandler](DoneWith result) {
- m_taskTree.release()->deleteLater();
- if (doneHandler)
- doneHandler(result);
- emit done(result);
- });
- if (setupHandler)
- setupHandler(m_taskTree.get());
- emit aboutToStart(m_taskTree.get());
- m_taskTree->start();
-}
-
-void TaskTreeRunner::cancel()
-{
- if (m_taskTree)
- m_taskTree->cancel();
-}
-
-void TaskTreeRunner::reset()
-{
- m_taskTree.reset();
-}
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/tasktreerunner.h b/src/assets/downloader/tasking/tasktreerunner.h
deleted file mode 100644
index f91e7608113..00000000000
--- a/src/assets/downloader/tasking/tasktreerunner.h
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_TASKTREERUNNER_H
-#define TASKING_TASKTREERUNNER_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasking_global.h"
-#include "tasktree.h"
-
-#include <QtCore/QObject>
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-class TASKING_EXPORT TaskTreeRunner : public QObject
-{
- Q_OBJECT
-
-public:
- using SetupHandler = std::function<void(TaskTree *)>;
- using DoneHandler = std::function<void(DoneWith)>;
-
- ~TaskTreeRunner();
-
- bool isRunning() const { return bool(m_taskTree); }
-
- // When task tree is running it resets the old task tree.
- void start(const Group &recipe,
- const SetupHandler &setupHandler = {},
- const DoneHandler &doneHandler = {});
-
- // When task tree is running it emits done(DoneWith::Cancel) synchronously.
- void cancel();
-
- // No done() signal is emitted.
- void reset();
-
-Q_SIGNALS:
- void aboutToStart(TaskTree *taskTree);
- void done(DoneWith result);
-
-private:
- std::unique_ptr<TaskTree> m_taskTree;
-};
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_TASKTREERUNNER_H
diff --git a/src/assets/downloader/tasking/tcpsocket.cpp b/src/assets/downloader/tasking/tcpsocket.cpp
deleted file mode 100644
index 19aab8fc714..00000000000
--- a/src/assets/downloader/tasking/tcpsocket.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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 "tcpsocket.h"
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-void TcpSocket::start()
-{
- if (m_socket) {
- qWarning("The TcpSocket is already running. Ignoring the call to start().");
- return;
- }
- if (m_address.isNull()) {
- qWarning("Can't start the TcpSocket with invalid address. "
- "Stopping with an error.");
- m_error = QAbstractSocket::HostNotFoundError;
- emit done(DoneResult::Error);
- return;
- }
-
- m_socket.reset(new QTcpSocket);
- connect(m_socket.get(), &QAbstractSocket::errorOccurred, this,
- [this](QAbstractSocket::SocketError error) {
- m_error = error;
- m_socket->disconnect();
- emit done(DoneResult::Error);
- m_socket.release()->deleteLater();
- });
- connect(m_socket.get(), &QAbstractSocket::connected, this, [this] {
- if (!m_writeData.isEmpty())
- m_socket->write(m_writeData);
- emit started();
- });
- connect(m_socket.get(), &QAbstractSocket::disconnected, this, [this] {
- m_socket->disconnect();
- emit done(DoneResult::Success);
- m_socket.release()->deleteLater();
- });
-
- m_socket->connectToHost(m_address, m_port);
-}
-
-TcpSocket::~TcpSocket()
-{
- if (m_socket) {
- m_socket->disconnect();
- m_socket->abort();
- }
-}
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
diff --git a/src/assets/downloader/tasking/tcpsocket.h b/src/assets/downloader/tasking/tcpsocket.h
deleted file mode 100644
index ec0069bf130..00000000000
--- a/src/assets/downloader/tasking/tcpsocket.h
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2024 Jarek Kobus
-// Copyright (C) 2024 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
-
-#ifndef TASKING_TCPSOCKET_H
-#define TASKING_TCPSOCKET_H
-
-#include "tasking_global.h"
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include "tasktree.h"
-
-#include <QtNetwork/QTcpSocket>
-
-#include <memory>
-
-QT_BEGIN_NAMESPACE
-
-namespace Tasking {
-
-// This class introduces the dependency to Qt::Network, otherwise Tasking namespace
-// is independent on Qt::Network.
-// Possibly, it could be placed inside Qt::Network library, as a wrapper around QTcpSocket.
-
-class TASKING_EXPORT TcpSocket final : public QObject
-{
- Q_OBJECT
-
-public:
- ~TcpSocket();
- void setAddress(const QHostAddress &address) { m_address = address; }
- void setPort(quint16 port) { m_port = port; }
- void setWriteData(const QByteArray &data) { m_writeData = data; }
- QTcpSocket *socket() const { return m_socket.get(); }
- void start();
-
-Q_SIGNALS:
- void started();
- void done(DoneResult result);
-
-private:
- QHostAddress m_address;
- quint16 m_port = 0;
- QByteArray m_writeData;
- std::unique_ptr<QTcpSocket> m_socket;
- QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError;
-};
-
-using TcpSocketTask = SimpleCustomTask<TcpSocket>;
-
-} // namespace Tasking
-
-QT_END_NAMESPACE
-
-#endif // TASKING_TCPSOCKET_H
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index 1147205b79f..c4a3480b2f9 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -1028,7 +1028,7 @@ qt_internal_extend_target(Core
qt_internal_extend_target(Core
CONDITION
- QT_FEATURE_timezone AND UNIX
+ QT_FEATURE_timezone AND UNIX AND NOT VXWORKS
AND NOT ANDROID AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb
SOURCES
time/qtimezoneprivate_tz.cpp
@@ -1037,7 +1037,7 @@ qt_internal_extend_target(Core
qt_internal_extend_target(Core
CONDITION
QT_FEATURE_icu AND QT_FEATURE_timezone
- AND NOT UNIX AND NOT QT_FEATURE_timezone_tzdb
+ AND (VXWORKS OR NOT UNIX) AND NOT QT_FEATURE_timezone_tzdb
SOURCES
time/qtimezoneprivate_icu.cpp
)
diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake
index d951b85c147..edcfba0f6ce 100644
--- a/src/corelib/configure.cmake
+++ b/src/corelib/configure.cmake
@@ -1150,7 +1150,7 @@ qt_feature("timezone" PUBLIC
SECTION "Utilities"
LABEL "QTimeZone"
PURPOSE "Provides support for time-zone handling."
- CONDITION NOT WASM AND NOT VXWORKS
+ CONDITION NOT WASM
)
qt_feature("timezone_locale" PRIVATE
SECTION "Utilities"
diff --git a/src/corelib/doc/images/javaiterators1.svg b/src/corelib/doc/images/javaiterators1.svg
new file mode 100644
index 00000000000..468dbe5371c
--- /dev/null
+++ b/src/corelib/doc/images/javaiterators1.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ width="340"
+ height="110"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+
+<style>
+ svg .box-style { stroke: black; fill: white }
+ svg .line-style { stroke: red; fill: none }
+ svg .text-style { font: 20px arial; fill: black }
+ svg .fill-style { stroke: none; fill: red }
+
+ [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black }
+ [data-theme="dark"] svg .line-style { stroke: red; fill: none }
+ [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 }
+ [data-theme="dark"] svg .fill-style { stroke: none; fill: red }
+
+ [data-theme="light"] svg .box-style { stroke: black; fill: white }
+ [data-theme="light"] svg .line-style { stroke: red; fill: none }
+ [data-theme="light"] svg .text-style { font: 20px arial; fill: black }
+ [data-theme="light"] svg .fill-style { stroke: none; fill: red }
+</style>
+
+<g transform="translate(10.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">A</text>
+<path d="M 0,60 v 30" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+</g>
+
+<g transform="translate(90.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">B</text>
+<path d="M 0,60 v 30" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+</g>
+
+<g transform="translate(170.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">C</text>
+<path d="M 0,60 v 30" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+</g>
+
+<g transform="translate(250.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">D</text>
+<path d="M 0,60 v 30" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+</g>
+
+<g transform="translate(330.5, 10.5)">
+<path d="M 0,60 v 30" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+</g>
+
+</svg>
diff --git a/src/corelib/doc/images/javaiterators2.svg b/src/corelib/doc/images/javaiterators2.svg
new file mode 100644
index 00000000000..df4c6b352a6
--- /dev/null
+++ b/src/corelib/doc/images/javaiterators2.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ version="1.1"
+ width="340"
+ height="130"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg">
+
+<style>
+ svg .box-style { stroke: black; fill: white }
+ svg .line-style { stroke: red; fill: none }
+ svg .fill-style { stroke: none; fill: red }
+ svg .np-line-style { stroke: blue; fill: none }
+ svg .np-fill-style { stroke: none; fill: blue }
+ svg .text-style { font: 20px arial; fill: black }
+ svg .small-text-style { font: 12px monospace; fill: black }
+
+ [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black }
+ [data-theme="dark"] svg .line-style { stroke: red; fill: none }
+ [data-theme="dark"] svg .fill-style { stroke: none; fill: red }
+ [data-theme="dark"] svg .np-line-style { stroke: #4080ff; fill: none; stroke-width: 1.5 }
+ [data-theme="dark"] svg .np-fill-style { stroke: none; fill: #4080ff }
+ [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 }
+ [data-theme="dark"] svg .small-text-style { font: 12px monospace; fill: #f2f2f2 }
+
+ [data-theme="light"] svg .box-style { stroke: black; fill: white }
+ [data-theme="light"] svg .line-style { stroke: red; fill: none }
+ [data-theme="light"] svg .fill-style { stroke: none; fill: red }
+ [data-theme="light"] svg .np-line-style { stroke: blue; fill: none }
+ [data-theme="light"] svg .np-fill-style { stroke: none; fill: blue }
+ [data-theme="light"] svg .text-style { font: 20px arial; fill: black }
+ [data-theme="light"] svg .small-text-style { font: 12px monospace; fill: black }
+</style>
+
+<g transform="translate(10.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">A</text>
+</g>
+
+<g transform="translate(90.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">B</text>
+<path d="M 0,60 c 0,30 50,40 80,30" class="np-line-style" />
+<path d="M 0,60 l -2,14 l 11,-4 z" class="np-fill-style" />
+<text x="-15" y="110" class="small-text-style">previous()</text>
+</g>
+
+<g transform="translate(170.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">C</text>
+<path d="M 0,60 v 50" class="line-style" />
+<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" />
+<text x="30" y="110" class="small-text-style">next()</text>
+</g>
+
+<g transform="translate(250.5, 10.5)">
+<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" />
+<text x="35" y="36" class="text-style">D</text>
+<path d="M 0,60 c 0,30 -50,40 -80,30" class="np-line-style" />
+<path d="M 0,60 l 2,14 l -11,-4 z" class="np-fill-style" />
+</g>
+
+</svg>
diff --git a/src/corelib/doc/src/java-style-iterators.qdoc b/src/corelib/doc/src/java-style-iterators.qdoc
index 856005cb66c..a0e5c53d633 100644
--- a/src/corelib/doc/src/java-style-iterators.qdoc
+++ b/src/corelib/doc/src/java-style-iterators.qdoc
@@ -42,7 +42,7 @@
diagram below shows the valid iterator positions as red arrows for a list
containing four items:
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's a typical loop for iterating through all the elements of a
QList<QString> in order:
@@ -70,7 +70,7 @@
\l{QListIterator::next()}{next()} and
\l{QListIterator::previous()}{previous()} on an iterator:
- \image javaiterators2.png
+ \image javaiterators2.png Iterating to the next and previous items
The following table summarizes the QListIterator API:
diff --git a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc
index 9b3ea6ae660..5dc2e6dad12 100644
--- a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc
+++ b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc
@@ -172,7 +172,7 @@
The following illustrates this approach.
- \badcode
+ \code
void DerivedClass::setValue(int val)
{
// do something
@@ -247,7 +247,7 @@
be called for the current value of the property, register your callback using
subscribe() instead.
- \section1 Interaction with Q_PROPERTYs
+ \section1 Interaction with Q_PROPERTY
A \l {The Property System}{Q_PROPERTY} that defines \c BINDABLE can be bound and
used in binding expressions. You can implement such properties using \l {QProperty},
diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp
index 075eb144e51..4d431b46213 100644
--- a/src/corelib/io/qlockfile.cpp
+++ b/src/corelib/io/qlockfile.cpp
@@ -379,8 +379,9 @@ QLockFilePrivate::~QLockFilePrivate()
QByteArray QLockFilePrivate::lockFileContents() const
{
// Use operator% from the fast builder to avoid multiple memory allocations.
- return QByteArray::number(QCoreApplication::applicationPid()) % '\n'
- % processNameByPid(QCoreApplication::applicationPid()).toUtf8() % '\n'
+ qint64 pid = QCoreApplication::applicationPid();
+ return QByteArray::number(pid) % '\n'
+ % processNameByPid(pid).toUtf8() % '\n'
% machineName().toUtf8() % '\n'
% QSysInfo::machineUniqueId() % '\n'
% QSysInfo::bootUniqueId() % '\n';
diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp
index 05e3a39e589..b0c6c46b125 100644
--- a/src/corelib/itemmodels/qrangemodel.cpp
+++ b/src/corelib/itemmodels/qrangemodel.cpp
@@ -20,6 +20,8 @@ public:
private:
std::unique_ptr<QRangeModelImplBase, QRangeModelImplBase::Deleter> impl;
+ friend class QRangeModelImplBase;
+
mutable QHash<int, QByteArray> m_roleNames;
};
@@ -28,6 +30,16 @@ QRangeModel::QRangeModel(QRangeModelImplBase *impl, QObject *parent)
{
}
+QRangeModelImplBase *QRangeModelImplBase::getImplementation(QRangeModel *model)
+{
+ return model->d_func()->impl.get();
+}
+
+const QRangeModelImplBase *QRangeModelImplBase::getImplementation(const QRangeModel *model)
+{
+ return model->d_func()->impl.get();
+}
+
/*!
\class QRangeModel
\inmodule QtCore
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index f38dc88c0d9..c2f473542d7 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -842,6 +842,11 @@ namespace QRangeModelDetails
{
using protocol = wrapped_t<Protocol>;
using row = typename range_traits<wrapped_t<Range>>::value_type;
+ static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>,
+ protocol_childRows<protocol, row>>;
+ static constexpr bool is_list = static_size_v<row> == 0
+ && (!has_metaobject_v<row> || row_category<row>::isMultiRole);
+ static constexpr bool is_table = !is_list && !is_tree;
static constexpr bool has_newRow = protocol_newRow<protocol>();
static constexpr bool has_deleteRow = protocol_deleteRow<protocol, row>();
@@ -1046,6 +1051,9 @@ public:
typename C::MultiData
>;
+ static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model);
+ static Q_CORE_EXPORT const QRangeModelImplBase *getImplementation(const QRangeModel *model);
+
private:
friend class QRangeModelPrivate;
QRangeModel *m_rangeModel;
@@ -1147,13 +1155,6 @@ protected:
}
}
- static constexpr bool isMutable()
- {
- return range_features::is_mutable && row_features::is_mutable
- && std::is_reference_v<row_reference>
- && Structure::is_mutable_impl;
- }
-
static constexpr int static_row_count = QRangeModelDetails::static_size_v<range_type>;
static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>;
static constexpr bool rows_are_owning_or_raw_pointers =
@@ -1161,9 +1162,6 @@ protected:
static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>;
static constexpr bool one_dimensional_range = static_column_count == 0;
- static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; }
- static constexpr bool dynamicColumns() { return static_column_count < 0; }
-
// A row might be a value (or range of values), or a pointer.
// row_ptr is always a pointer, and const_row_ptr is a pointer to const.
using row_ptr = wrapped_row_type *;
@@ -1205,6 +1203,15 @@ protected:
"The range holding a move-only row-type must support insert(pos, start, end)");
public:
+ static constexpr bool isMutable()
+ {
+ return range_features::is_mutable && row_features::is_mutable
+ && std::is_reference_v<row_reference>
+ && Structure::is_mutable_impl;
+ }
+ static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; }
+ static constexpr bool dynamicColumns() { return static_column_count < 0; }
+
explicit QRangeModelImpl(Range &&model, Protocol&& protocol, QRangeModel *itemModel)
: Ancestor(itemModel)
, ProtocolStorage{std::forward<Protocol>(protocol)}
@@ -1236,7 +1243,7 @@ public:
if (row == index.row() && column == index.column())
return index;
- if (column < 0 || column >= this->itemModel().columnCount())
+ if (column < 0 || column >= this->columnCount({}))
return {};
if (row == index.row())
@@ -1974,8 +1981,8 @@ public:
}
if (sourceRow == destRow || sourceRow == destRow - 1 || count <= 0
- || sourceRow < 0 || sourceRow + count - 1 >= this->itemModel().rowCount(sourceParent)
- || destRow < 0 || destRow > this->itemModel().rowCount(destParent)) {
+ || sourceRow < 0 || sourceRow + count - 1 >= this->rowCount(sourceParent)
+ || destRow < 0 || destRow > this->rowCount(destParent)) {
return false;
}
@@ -1995,11 +2002,14 @@ public:
}
}
- QModelIndex parent(const QModelIndex &child) const { return that().parent(child); }
+ const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
+ protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
+
+ QModelIndex parent(const QModelIndex &child) const { return that().parentImpl(child); }
- int rowCount(const QModelIndex &parent) const { return that().rowCount(parent); }
+ int rowCount(const QModelIndex &parent) const { return that().rowCountImpl(parent); }
- int columnCount(const QModelIndex &parent) const { return that().columnCount(parent); }
+ int columnCount(const QModelIndex &parent) const { return that().columnCountImpl(parent); }
void destroy() { delete std::addressof(that()); }
@@ -2035,6 +2045,11 @@ public:
protected:
~QRangeModelImpl()
{
+ deleteOwnedRows();
+ }
+
+ void deleteOwnedRows()
+ {
// We delete row objects if we are not operating on a reference or pointer
// to a range, as in that case, the owner of the referenced/pointed to
// range also owns the row entries.
@@ -2335,9 +2350,6 @@ protected:
}
- const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
- protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); }
-
ModelData m_data;
};
@@ -2385,7 +2397,7 @@ protected:
return this->createIndex(row, column, QRangeModelDetails::pointerTo(*it));
}
- QModelIndex parent(const QModelIndex &child) const
+ QModelIndex parentImpl(const QModelIndex &child) const
{
if (!child.isValid())
return {};
@@ -2410,12 +2422,12 @@ protected:
return {};
}
- int rowCount(const QModelIndex &parent) const
+ int rowCountImpl(const QModelIndex &parent) const
{
return Base::size(this->childRange(parent));
}
- int columnCount(const QModelIndex &) const
+ int columnCountImpl(const QModelIndex &) const
{
// all levels of a tree have to have the same, static, column count
if constexpr (Base::one_dimensional_range)
@@ -2662,6 +2674,9 @@ class QGenericTableItemModelImpl
using Base = QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
friend class QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>;
+ static constexpr bool is_mutable_impl = true;
+
+public:
using range_type = typename Base::range_type;
using range_features = typename Base::range_features;
using row_type = typename Base::row_type;
@@ -2669,9 +2684,6 @@ class QGenericTableItemModelImpl
using row_traits = typename Base::row_traits;
using row_features = typename Base::row_features;
- static constexpr bool is_mutable_impl = true;
-
-public:
explicit QGenericTableItemModelImpl(Range &&model, QRangeModel *itemModel)
: Base(std::forward<Range>(model), {}, itemModel)
{}
@@ -2692,19 +2704,19 @@ protected:
}
}
- QModelIndex parent(const QModelIndex &) const
+ QModelIndex parentImpl(const QModelIndex &) const
{
return {};
}
- int rowCount(const QModelIndex &parent) const
+ int rowCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return int(Base::size(*this->m_data.model()));
}
- int columnCount(const QModelIndex &parent) const
+ int columnCountImpl(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
@@ -2760,7 +2772,7 @@ protected:
// dynamically sized rows all have to have the same column count
if constexpr (Base::dynamicColumns() && row_features::has_resize) {
if (QRangeModelDetails::isValid(empty_row))
- QRangeModelDetails::refTo(empty_row).resize(this->itemModel().columnCount());
+ QRangeModelDetails::refTo(empty_row).resize(this->columnCount({}));
}
return empty_row;
diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp
index 0c7e7db3188..de7c4df6d1d 100644
--- a/src/corelib/kernel/qcoreapplication.cpp
+++ b/src/corelib/kernel/qcoreapplication.cpp
@@ -2512,7 +2512,7 @@ QString QCoreApplication::applicationFilePath()
Returns the current process ID for the application.
*/
-qint64 QCoreApplication::applicationPid()
+qint64 QCoreApplication::applicationPid() noexcept
{
#if defined(Q_OS_WIN)
return GetCurrentProcessId();
diff --git a/src/corelib/kernel/qcoreapplication.h b/src/corelib/kernel/qcoreapplication.h
index 55c8097adc5..054e53a4a41 100644
--- a/src/corelib/kernel/qcoreapplication.h
+++ b/src/corelib/kernel/qcoreapplication.h
@@ -119,7 +119,7 @@ public:
static QString applicationDirPath();
static QString applicationFilePath();
- Q_DECL_CONST_FUNCTION static qint64 applicationPid();
+ Q_DECL_CONST_FUNCTION static qint64 applicationPid() noexcept;
#if QT_CONFIG(permissions) || defined(Q_QDOC)
Qt::PermissionStatus checkPermission(const QPermission &permission);
diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp
index 8c95431d01f..bbcea8338ca 100644
--- a/src/corelib/kernel/qpermissions.cpp
+++ b/src/corelib/kernel/qpermissions.cpp
@@ -132,6 +132,10 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg);
\annotatedlist permissions
+ \note The available permission types cover core functionality of Qt modules
+ like Qt Multimedia and Qt Positioning, but do not encompass all platform-specific
+ permissions. Custom permission types are not currently supported.
+
\section1 Best Practices
To ensure the best possible user experience for the end user we recommend
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp
index 8366787ea66..57089f164b2 100644
--- a/src/corelib/kernel/qvariant.cpp
+++ b/src/corelib/kernel/qvariant.cpp
@@ -2974,4 +2974,156 @@ const QVariant *QVariantConstPointer::operator->() const
implement operator->().
*/
+/*!
+ \class QVariant::ConstReference
+ \since 6.11
+ \inmodule QtCore
+ \brief The QVariant::ConstReference acts as a const reference to a QVariant.
+
+ As the generic iterators don't actually instantiate a QVariant on each
+ step, they cannot return a reference to one from operator*().
+ QVariant::ConstReference provides the same functionality as an actual
+ reference to a QVariant would, but is backed a referred-to value given as
+ template parameter. The template is implemented for
+ QMetaSequence::ConstIterator, QMetaSequence::Iterator,
+ QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(const Referred &referred)
+
+ Creates a QVariant::ConstReference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(Referred &&referred)
+
+ Creates a QVariant::ConstReference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::ConstReference<Referred>::operator QVariant() const
+
+ Dereferences the reference to a QVariant.
+ This method needs to be specialized for each Referred type. It is
+ pre-defined for QMetaSequence::ConstIterator, QMetaSequence::Iterator,
+ QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator.
+ */
+
+
+/*!
+ \class QVariant::Reference
+ \since 6.11
+ \inmodule QtCore
+ \brief The QVariant::Reference acts as a non-const reference to a QVariant.
+
+ As the generic iterators don't actually instantiate a QVariant on each
+ step, they cannot return a reference to one from operator*().
+ QVariant::Reference provides the same functionality as an actual reference
+ to a QVariant would, but is backed a referred-to value given as template
+ parameter. The template is implemented for QMetaSequence::Iterator and
+ QMetaAssociation::Iterator.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::Reference(const Referred &referred)
+
+ Creates a QVariant::Reference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::Reference(Referred &&referred)
+
+ Creates a QVariant::Reference from a \a referred.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const Reference<Referred> &value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(Reference<Referred> &&value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+*/
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const QVariant &value)
+
+ Assigns a new \a value to the value referred to by this QVariant::Reference.
+ This method needs to be specialized for each Referred type. It is
+ pre-defined for QMetaSequence::Iterator and QMetaAssociation::Iterator.
+ */
+
+/*!
+ \fn template<typename Referred> QVariant::Reference<Referred>::operator QVariant() const
+
+ Dereferences the reference to a QVariant. By default this instantiates a
+ temporary QVariant::ConstReference and calls dereferences that. In cases
+ where instantiating a temporary ConstReference is expensive, this method
+ should be specialized.
+ */
+
+/*!
+ \class QVariant::ConstPointer
+ \since 6.11
+ \inmodule QtCore
+ \brief QVariant::ConstPointer is a template class that emulates a const pointer to QVariant.
+
+ QVariant::ConstPointer wraps pointed-to value and returns a
+ QVariant::ConstReference to it from its operator*(). This makes it suitable
+ as replacement for an actual pointer. We cannot return an actual pointer
+ from generic iterators as the iterators don't hold an actual QVariant.
+*/
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(const Pointed &pointed)
+
+ Constructs a QVariant::ConstPointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(Pointed &&pointed)
+
+ Constructs a QVariant::ConstPointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::ConstReference<Pointer> QVariant::ConstPointer<Pointed>::operator*() const
+
+ Dereferences the QVariant::ConstPointer to a QVariant::ConstReference.
+ */
+
+/*!
+ \class QVariant::Pointer
+ \since 6.11
+ \inmodule QtCore
+ \brief QVariant::Pointer is a template class that emulates a non-const pointer to QVariant.
+
+ QVariant::Pointer wraps pointed-to value and returns a QVariant::Reference
+ to it from its operator*(). This makes it suitable as replacement for an
+ actual pointer. We cannot return an actual pointer from generic iterators as
+ the iterators don't hold an actual QVariant.
+*/
+
+/*!
+ \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(const Pointed &pointed)
+
+ Constructs a QVariant::Pointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(Pointed &&pointed)
+
+ Constructs a QVariant::Pointer from the value \a pointed to.
+ */
+
+/*!
+ \fn template<typename Pointed> QVariant::Reference<Pointer> QVariant::Pointer<Pointed>::operator*() const
+
+ Dereferences the QVariant::Pointer to a QVariant::Reference.
+ */
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h
index 542b1d9b709..9b219d089b5 100644
--- a/src/corelib/kernel/qvariant.h
+++ b/src/corelib/kernel/qvariant.h
@@ -61,7 +61,20 @@ inline T qvariant_cast(const QVariant &);
namespace QtPrivate {
template<> constexpr inline bool qIsRelocatable<QVariant> = true;
-}
+
+template<typename Referred>
+class ConstReference;
+
+template<typename Referred>
+class Reference;
+
+template<typename Pointed>
+class ConstPointer;
+
+template<typename Pointed>
+class Pointer;
+} // namespace QtPrivate
+
class Q_CORE_EXPORT QVariant
{
template <typename T, typename... Args>
@@ -228,6 +241,123 @@ private:
>;
public:
+ template<typename Referred>
+ class ConstReference
+ {
+ private:
+ const Referred m_referred;
+
+ public:
+ // You can initialize a const reference from another one, but you can't assign to it.
+
+ explicit ConstReference(const Referred &referred)
+ noexcept(std::is_nothrow_copy_constructible_v<Referred>)
+ : m_referred(referred) {}
+ explicit ConstReference(Referred &&referred)
+ noexcept(std::is_nothrow_move_constructible_v<Referred>)
+ : m_referred(std::move(referred)) {}
+ ConstReference(const ConstReference &) = default;
+ ConstReference(ConstReference &&) = default;
+ ~ConstReference() = default;
+ ConstReference &operator=(const ConstReference &value) = delete;
+ ConstReference &operator=(ConstReference &&value) = delete;
+
+ // To be specialized for each Referred
+ operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant);
+ };
+
+ template<typename Referred>
+ class Reference
+ {
+ private:
+ Referred m_referred;
+
+ friend void swap(Reference a, Reference b) { return a.swap(std::move(b)); }
+
+ public:
+ // Assigning and initializing are different operations for references.
+
+ explicit Reference(const Referred &referred)
+ noexcept(std::is_nothrow_copy_constructible_v<Referred>)
+ : m_referred(referred) {}
+ explicit Reference(Referred &&referred)
+ noexcept(std::is_nothrow_move_constructible_v<Referred>)
+ : m_referred(std::move(referred)) {}
+ Reference(const Reference &) = default;
+ Reference(Reference &&) = default;
+ ~Reference() = default;
+
+ Reference &operator=(const Reference &value)
+ noexcept(Referred::canNoexceptAssignQVariant)
+ {
+ return operator=(QVariant(value));
+ }
+
+ Reference &operator=(Reference &&value)
+ noexcept(Referred::canNoexceptAssignQVariant)
+ {
+ return operator=(QVariant(value));
+ }
+
+ operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant)
+ {
+ return ConstReference(m_referred);
+ }
+
+ void swap(Reference b)
+ {
+ // swapping a reference is not swapping the referred item, but swapping its contents.
+ QVariant tmp = *this;
+ *this = std::move(b);
+ b = std::move(tmp);
+ }
+
+ // To be specialized for each Referred
+ Reference &operator=(const QVariant &value) noexcept(Referred::canNoexceptAssignQVariant);
+ };
+
+ template<typename Pointed>
+ class ConstPointer
+ {
+ private:
+ Pointed m_pointed;
+
+ public:
+ explicit ConstPointer(const Pointed &pointed)
+ noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ : m_pointed(pointed) {}
+ explicit ConstPointer(Pointed &&pointed)
+ noexcept(std::is_nothrow_move_constructible_v<Pointed>)
+ : m_pointed(std::move(pointed)) {}
+
+ ConstReference<Pointed> operator*()
+ const noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ {
+ return ConstReference<Pointed>(m_pointed);
+ }
+ };
+
+ template<typename Pointed>
+ class Pointer
+ {
+ private:
+ Pointed m_pointed;
+
+ public:
+ explicit Pointer(const Pointed &pointed)
+ noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ : m_pointed(pointed) {}
+ explicit Pointer(Pointed &&pointed)
+ noexcept(std::is_nothrow_move_constructible_v<Pointed>)
+ : m_pointed(std::move(pointed)) {}
+
+ Reference<Pointed> operator*()
+ const noexcept(std::is_nothrow_copy_constructible_v<Pointed>)
+ {
+ return Reference<Pointed>(m_pointed);
+ }
+ };
+
template <typename T, typename... Args,
if_constructible<T, Args...> = true>
explicit QVariant(std::in_place_type_t<T>, Args&&... args)
diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp
index 06dc614c352..93f905afc48 100644
--- a/src/corelib/mimetypes/qmimedatabase.cpp
+++ b/src/corelib/mimetypes/qmimedatabase.cpp
@@ -158,7 +158,7 @@ void QMimeDatabasePrivate::loadProviders()
const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers()
{
-#ifndef Q_OS_WASM // stub implementation always returns true
+#if QT_CONFIG(thread) // stub implementation always returns true
Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex
#endif
if (m_providers.empty()) {
@@ -289,7 +289,9 @@ QStringList QMimeDatabasePrivate::mimeParents(const QString &mimeName)
QStringList QMimeDatabasePrivate::parents(const QString &mimeName)
{
+#if QT_CONFIG(thread) // stub implementation always returns true
Q_ASSERT(!mutex.tryLock());
+#endif
QStringList result;
for (const auto &provider : providers())
provider->addParents(mimeName, result);
diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp
index 4ccc58b3f39..de7043e8c1d 100644
--- a/src/corelib/mimetypes/qmimeprovider.cpp
+++ b/src/corelib/mimetypes/qmimeprovider.cpp
@@ -377,7 +377,6 @@ void QMimeBinaryProvider::findByMagic(const QByteArray &data, QMimeMagicResult &
void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
{
- const QByteArray mimeStr = mime.toLatin1();
const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset);
const int numEntries = m_cacheFile->getUint32(parentListOffset);
@@ -388,7 +387,7 @@ void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
const int off = parentListOffset + 4 + 8 * medium;
const int mimeOffset = m_cacheFile->getUint32(off);
const char *aMime = m_cacheFile->getCharStar(mimeOffset);
- const int cmp = qstrcmp(aMime, mimeStr);
+ const int cmp = QLatin1StringView(aMime).compare(mime);
if (cmp < 0) {
begin = medium + 1;
} else if (cmp > 0) {
@@ -409,7 +408,6 @@ void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
QString QMimeBinaryProvider::resolveAlias(const QString &name)
{
- const QByteArray input = name.toLatin1();
const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
const int numEntries = m_cacheFile->getUint32(aliasListOffset);
int begin = 0;
@@ -419,7 +417,7 @@ QString QMimeBinaryProvider::resolveAlias(const QString &name)
const int off = aliasListOffset + 4 + 8 * medium;
const int aliasOffset = m_cacheFile->getUint32(off);
const char *alias = m_cacheFile->getCharStar(aliasOffset);
- const int cmp = qstrcmp(alias, input);
+ const int cmp = QLatin1StringView(alias).compare(name);
if (cmp < 0) {
begin = medium + 1;
} else if (cmp > 0) {
@@ -435,7 +433,6 @@ QString QMimeBinaryProvider::resolveAlias(const QString &name)
void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
{
- const QByteArray input = name.toLatin1();
const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
const int numEntries = m_cacheFile->getUint32(aliasListOffset);
for (int pos = 0; pos < numEntries; ++pos) {
@@ -443,7 +440,7 @@ void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
const int mimeOffset = m_cacheFile->getUint32(off + 4);
const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
- if (input == mimeType) {
+ if (name == QLatin1StringView(mimeType)) {
const int aliasOffset = m_cacheFile->getUint32(off);
const char *alias = m_cacheFile->getCharStar(aliasOffset);
const QString strAlias = QString::fromLatin1(alias);
@@ -586,7 +583,7 @@ QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName)
// Binary search in the icons or generic-icons list
QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset,
- const QByteArray &inputMime)
+ QStringView inputMime)
{
const int iconsListOffset = cacheFile->getUint32(posListOffset);
const int numIcons = cacheFile->getUint32(iconsListOffset);
@@ -597,7 +594,7 @@ QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int pos
const int off = iconsListOffset + 4 + 8 * medium;
const int mimeOffset = cacheFile->getUint32(off);
const char *mime = cacheFile->getCharStar(mimeOffset);
- const int cmp = qstrcmp(mime, inputMime);
+ const int cmp = QLatin1StringView(mime).compare(inputMime);
if (cmp < 0)
begin = medium + 1;
else if (cmp > 0)
@@ -612,14 +609,12 @@ QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int pos
QString QMimeBinaryProvider::icon(const QString &name)
{
- const QByteArray inputMime = name.toLatin1();
- return iconForMime(m_cacheFile.get(), PosIconsListOffset, inputMime);
+ return iconForMime(m_cacheFile.get(), PosIconsListOffset, name);
}
QString QMimeBinaryProvider::genericIcon(const QString &name)
{
- const QByteArray inputMime = name.toLatin1();
- return iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, inputMime);
+ return iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, name);
}
////
diff --git a/src/corelib/mimetypes/qmimeprovider_p.h b/src/corelib/mimetypes/qmimeprovider_p.h
index aede727d710..d597651b362 100644
--- a/src/corelib/mimetypes/qmimeprovider_p.h
+++ b/src/corelib/mimetypes/qmimeprovider_p.h
@@ -111,7 +111,7 @@ private:
bool caseSensitiveCheck);
bool matchMagicRule(CacheFile *cacheFile, int numMatchlets, int firstOffset,
const QByteArray &data);
- QLatin1StringView iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime);
+ QLatin1StringView iconForMime(CacheFile *cacheFile, int posListOffset, QStringView inputMime);
void loadMimeTypeList();
bool checkCacheChanged();
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index d1fff5388c6..287138bb915 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -177,15 +177,13 @@ Blob Blob::slice(uint32_t begin, uint32_t end) const
ArrayBuffer Blob::arrayBuffer_sync() const
{
- QEventLoop loop;
emscripten::val buffer;
- qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), {
- .thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) {
+ uint32_t handlerIndex = qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), {
+ .thenFunc = [&buffer](emscripten::val arrayBuffer) {
buffer = arrayBuffer;
- loop.quit();
}
});
- loop.exec();
+ Promise::suspendExclusive(handlerIndex);
return ArrayBuffer(buffer);
}
@@ -443,7 +441,7 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name,
}
-void Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
+uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
{
Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc,
"Promise::adoptPromise", "must provide at least one callback function");
@@ -499,13 +497,23 @@ void Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
promise = promise.call<emscripten::val>("finally",
suspendResume->jsEventHandlerAt(*finallyIndex));
+
+ return *finallyIndex;
+}
+
+void Promise::suspendExclusive(uint32_t handlerIndex)
+{
+ QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
+ Q_ASSERT(suspendResume);
+ suspendResume->suspendExclusive(handlerIndex);
+ suspendResume->sendPendingEvents();
}
void Promise::all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks)
{
auto arr = emscripten::val::array(promises);
auto all = val::global("Promise").call<emscripten::val>("all", arr);
- return adoptPromise(all, callbacks);
+ adoptPromise(all, callbacks);
}
// Asyncify and thread blocking: Normally, it's not possible to block the main
@@ -630,6 +638,249 @@ qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
return size;
}
+FileSystemWritableFileStreamIODevice::FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream)
+ : m_stream(std::move(stream))
+{
+}
+
+bool FileSystemWritableFileStreamIODevice::open(QIODevice::OpenMode mode)
+{
+ if (mode.testFlag(QIODevice::ReadOnly))
+ return false;
+ return QIODevice::open(mode);
+}
+
+void FileSystemWritableFileStreamIODevice::close()
+{
+ if (!isOpen()) {
+ QIODevice::close();
+ return;
+ }
+
+ uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("close"), {
+ .thenFunc = [](emscripten::val) {
+ }
+ });
+ Promise::suspendExclusive(handlerIndex);
+
+ QIODevice::close();
+}
+
+bool FileSystemWritableFileStreamIODevice::isSequential() const
+{
+ return false;
+}
+
+qint64 FileSystemWritableFileStreamIODevice::size() const
+{
+ return m_size;
+}
+
+bool FileSystemWritableFileStreamIODevice::seek(qint64 pos)
+{
+ bool success = false;
+
+ emscripten::val seekParams = emscripten::val::object();
+ seekParams.set("type", std::string("seek"));
+ seekParams.set("position", static_cast<double>(pos));
+ uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ .thenFunc = [&success](emscripten::val) {
+ success = true;
+ },
+ .catchFunc = [](emscripten::val) {
+ }
+ }, seekParams);
+ Promise::suspendExclusive(handlerIndex);
+
+ if (!success)
+ return false;
+
+ return QIODevice::seek(pos);
+}
+
+qint64 FileSystemWritableFileStreamIODevice::readData(char *, qint64)
+{
+ Q_UNREACHABLE();
+}
+
+qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64 size)
+{
+ bool success = false;
+
+ Uint8Array array = Uint8Array::copyFrom(data, size);
+ uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ .thenFunc = [&success](emscripten::val) {
+ success = true;
+ },
+ .catchFunc = [](emscripten::val) {
+ }
+ }, array.val());
+ Promise::suspendExclusive(handlerIndex);
+
+ if (success) {
+ qint64 newPos = pos() + size;
+ m_size = std::max(m_size, newPos);
+ return size;
+ }
+ return -1;
+}
+
+FileSystemWritableFileStream::FileSystemWritableFileStream(const emscripten::val &writableStream)
+ : m_writableStream(writableStream)
+{
+}
+
+emscripten::val FileSystemWritableFileStream::val() const
+{
+ return m_writableStream;
+}
+
+FileSystemFileHandle::FileSystemFileHandle(const emscripten::val &fileHandle)
+ : m_fileHandle(fileHandle)
+{
+}
+
+std::string FileSystemFileHandle::name() const
+{
+ return m_fileHandle["name"].as<std::string>();
+}
+
+std::string FileSystemFileHandle::kind() const
+{
+ return m_fileHandle["kind"].as<std::string>();
+}
+
+emscripten::val FileSystemFileHandle::val() const
+{
+ return m_fileHandle;
+}
+
+FileSystemFileIODevice::FileSystemFileIODevice(FileSystemFileHandle fileHandle)
+ : m_fileHandle(fileHandle)
+{
+}
+
+bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
+{
+ if (isOpen())
+ return false;
+
+ // Read mode: get the File and create a BlobIODevice
+ if (mode & QIODevice::ReadOnly) {
+ File file;
+ bool success = false;
+
+ uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("getFile"), {
+ .thenFunc = [&file, &success](emscripten::val fileVal) {
+ file = File(fileVal);
+ success = true;
+ },
+ .catchFunc = [](emscripten::val) {
+ }
+ });
+ Promise::suspendExclusive(handlerIndex);
+
+ if (success) {
+ m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size()));
+ m_size = file.size();
+
+ if (!m_blobDevice->open(mode))
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ // Write mode: create a writable stream
+ if (mode & QIODevice::WriteOnly) {
+ FileSystemWritableFileStream writableStream;
+ bool success = false;
+
+ uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("createWritable"), {
+ .thenFunc = [&writableStream, &success](emscripten::val writable) {
+ writableStream = FileSystemWritableFileStream(writable);
+ success = true;
+ },
+ .catchFunc = [](emscripten::val) {
+ }
+ });
+ Promise::suspendExclusive(handlerIndex);
+
+ if (success) {
+ m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream);
+ if (!m_writableDevice->open(mode))
+ return false;
+ } else {
+ return false;
+ }
+ }
+
+ return QIODevice::open(mode);
+}
+
+void FileSystemFileIODevice::close()
+{
+ if (!isOpen()) {
+ QIODevice::close();
+ return;
+ }
+
+ if (m_writableDevice) {
+ m_writableDevice->close();
+ m_writableDevice.reset();
+ }
+ if (m_blobDevice) {
+ m_blobDevice->close();
+ m_blobDevice.reset();
+ }
+
+ QIODevice::close();
+}
+
+bool FileSystemFileIODevice::isSequential() const
+{
+ return false;
+}
+
+qint64 FileSystemFileIODevice::size() const
+{
+ return m_size;
+}
+
+bool FileSystemFileIODevice::seek(qint64 pos)
+{
+ if (m_blobDevice) {
+ if (!m_blobDevice->seek(pos))
+ return false;
+ }
+ if (m_writableDevice) {
+ if (!m_writableDevice->seek(pos))
+ return false;
+ }
+ return QIODevice::seek(pos);
+}
+
+qint64 FileSystemFileIODevice::readData(char *data, qint64 maxSize)
+{
+ if (!m_blobDevice)
+ return -1;
+
+ return m_blobDevice->read(data, maxSize);
+}
+
+qint64 FileSystemFileIODevice::writeData(const char *data, qint64 size)
+{
+ if (!m_writableDevice)
+ return -1;
+
+ qint64 written = m_writableDevice->write(data, size);
+ if (written > 0) {
+ qint64 newPos = pos() + written;
+ m_size = std::max(m_size, newPos);
+ }
+ return written;
+}
+
} // namespace qstdweb
QT_END_NAMESPACE
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index 711751f65ab..9a97370448e 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -195,6 +195,30 @@ namespace qstdweb {
emscripten::val m_uint8Array = emscripten::val::undefined();
};
+ class Q_CORE_EXPORT FileSystemWritableFileStream {
+ public:
+ FileSystemWritableFileStream() = default;
+ explicit FileSystemWritableFileStream(const emscripten::val &writableStream);
+ emscripten::val val() const;
+
+ private:
+ emscripten::val m_writableStream = emscripten::val::undefined();
+ };
+
+ class Q_CORE_EXPORT FileSystemFileHandle {
+ public:
+ FileSystemFileHandle() = default;
+ explicit FileSystemFileHandle(const emscripten::val &fileHandle);
+
+ std::string name() const;
+ std::string kind() const;
+
+ emscripten::val val() const;
+
+ private:
+ emscripten::val m_fileHandle = emscripten::val::undefined();
+ };
+
// EventCallback here for source compatibility; prefer using QWasmEventHandler directly
class Q_CORE_EXPORT EventCallback : public QWasmEventHandler
{
@@ -214,13 +238,13 @@ namespace qstdweb {
};
namespace Promise {
- void Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+ uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
template<typename... Args>
- void make(emscripten::val target,
- QString methodName,
- PromiseCallbacks callbacks,
- Args... args)
+ uint32_t make(emscripten::val target,
+ QString methodName,
+ PromiseCallbacks callbacks,
+ Args... args)
{
emscripten::val promiseObject = target.call<emscripten::val>(
methodName.toStdString().c_str(), std::forward<Args>(args)...);
@@ -228,9 +252,10 @@ namespace qstdweb {
qFatal("This function did not return a promise");
}
- adoptPromise(std::move(promiseObject), std::move(callbacks));
+ return adoptPromise(std::move(promiseObject), std::move(callbacks));
}
+ void Q_CORE_EXPORT suspendExclusive(uint32_t handlerIndex);
void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
};
@@ -274,6 +299,46 @@ namespace qstdweb {
Uint8Array m_array;
};
+ class Q_CORE_EXPORT FileSystemWritableFileStreamIODevice: public QIODevice
+ {
+ public:
+ FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream);
+ bool open(QIODevice::OpenMode mode) override;
+ void close() override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ bool seek(qint64 pos) override;
+
+ protected:
+ qint64 readData(char *data, qint64 maxSize) override;
+ qint64 writeData(const char *data, qint64 size) override;
+
+ private:
+ FileSystemWritableFileStream m_stream;
+ qint64 m_size = 0;
+ };
+
+ class Q_CORE_EXPORT FileSystemFileIODevice: public QIODevice
+ {
+ public:
+ FileSystemFileIODevice(FileSystemFileHandle fileHandle);
+ bool open(QIODevice::OpenMode mode) override;
+ void close() override;
+ bool isSequential() const override;
+ qint64 size() const override;
+ bool seek(qint64 pos) override;
+
+ protected:
+ qint64 readData(char *data, qint64 maxSize) override;
+ qint64 writeData(const char *data, qint64 size) override;
+
+ private:
+ FileSystemFileHandle m_fileHandle;
+ std::unique_ptr<BlobIODevice> m_blobDevice;
+ std::unique_ptr<FileSystemWritableFileStreamIODevice> m_writableDevice;
+ qint64 m_size = 0;
+ };
+
inline emscripten::val window()
{
static emscripten::val savedWindow = emscripten::val::global("window");
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
index 961bbb53e54..093898c520a 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
@@ -49,6 +49,7 @@ void qtSuspendResumeControlClearJs() {
asyncifyEnabled: false, // asyncify 1 or JSPI enabled
eventHandlers: {},
pendingEvents: [],
+ exclusiveEventHandler: 0,
});
});
}
@@ -118,7 +119,15 @@ void qtRegisterEventHandlerJs(int index) {
});
// Handle the event based on instance state and asyncify flag
- if (control.resume) {
+ if (control.exclusiveEventHandler > 0) {
+ // In exclusive mode, resume on exclusive event handler match only
+ if (index != control.exclusiveEventHandler)
+ return;
+
+ const resume = control.resume;
+ control.resume = null;
+ resume();
+ } else if (control.resume) {
// The instance is suspended in processEvents(), resume and process the event
const resume = control.resume;
control.resume = null;
@@ -148,14 +157,12 @@ QWasmSuspendResumeControl::QWasmSuspendResumeControl()
#endif
qtSuspendResumeControlClearJs();
suspendResumeControlJs().set("asyncifyEnabled", qstdweb::haveAsyncify());
- Q_ASSERT(!QWasmSuspendResumeControl::s_suspendResumeControl);
QWasmSuspendResumeControl::s_suspendResumeControl = this;
}
QWasmSuspendResumeControl::~QWasmSuspendResumeControl()
{
qtSuspendResumeControlClearJs();
- Q_ASSERT(QWasmSuspendResumeControl::s_suspendResumeControl);
QWasmSuspendResumeControl::s_suspendResumeControl = nullptr;
}
@@ -199,13 +206,24 @@ void QWasmSuspendResumeControl::suspend()
qtSuspendJs();
}
-// Sends any pending events. Returns true if an event was sent, false otherwise.
+void QWasmSuspendResumeControl::suspendExclusive(uint32_t eventHandlerIndex)
+{
+ suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndex);
+ qtSuspendJs();
+}
+
+// Sends any pending events. Returns the number of sent events.
int QWasmSuspendResumeControl::sendPendingEvents()
{
#if QT_CONFIG(thread)
Q_ASSERT(emscripten_is_main_runtime_thread());
#endif
- emscripten::val pendingEvents = suspendResumeControlJs()["pendingEvents"];
+ emscripten::val control = suspendResumeControlJs();
+ emscripten::val pendingEvents = control["pendingEvents"];
+
+ if (control["exclusiveEventHandler"].as<int>() > 0)
+ return sendPendingExclusiveEvent();
+
if (pendingEvents["length"].as<int>() == 0)
return 0;
@@ -214,13 +232,28 @@ int QWasmSuspendResumeControl::sendPendingEvents()
// Grab one event (handler and arg), and call it
emscripten::val event = pendingEvents.call<val>("shift");
auto it = m_eventHandlers.find(event["index"].as<int>());
- Q_ASSERT(it != m_eventHandlers.end());
- it->second(event["arg"]);
+ if (it != m_eventHandlers.end())
+ it->second(event["arg"]);
++count;
}
return count;
}
+// Sends the pending exclusive event, and resets the "exclusive" state
+int QWasmSuspendResumeControl::sendPendingExclusiveEvent()
+{
+ emscripten::val control = suspendResumeControlJs();
+ int exclusiveHandlerIndex = control["exclusiveEventHandler"].as<int>();
+ control.set("exclusiveEventHandler", 0);
+ emscripten::val event = control["pendingEvents"].call<val>("pop");
+ int eventHandlerIndex = event["index"].as<int>();
+ Q_ASSERT(exclusiveHandlerIndex == eventHandlerIndex);
+ auto it = m_eventHandlers.find(eventHandlerIndex);
+ Q_ASSERT(it != m_eventHandlers.end());
+ it->second(event["arg"]);
+ return 1;
+}
+
void qtSendPendingEvents()
{
if (QWasmSuspendResumeControl::s_suspendResumeControl)
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
index 2b9962e4be1..37c71ed8123 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
@@ -38,7 +38,9 @@ public:
static emscripten::val suspendResumeControlJs();
void suspend();
+ void suspendExclusive(uint32_t eventHandlerIndex);
int sendPendingEvents();
+ int sendPendingExclusiveEvent();
private:
friend void qtSendPendingEvents();
diff --git a/src/corelib/plugin/qlibrary.h b/src/corelib/plugin/qlibrary.h
index f31047b214a..95c14178b22 100644
--- a/src/corelib/plugin/qlibrary.h
+++ b/src/corelib/plugin/qlibrary.h
@@ -63,6 +63,7 @@ private:
Loaded
};
+ friend class QLibraryPrivate;
QTaggedPointer<QLibraryPrivate, LoadStatusTag> d = nullptr;
Q_DISABLE_COPY(QLibrary)
};
diff --git a/src/corelib/plugin/qlibrary_p.h b/src/corelib/plugin/qlibrary_p.h
index b4e29a79e28..2331c86d844 100644
--- a/src/corelib/plugin/qlibrary_p.h
+++ b/src/corelib/plugin/qlibrary_p.h
@@ -67,7 +67,7 @@ public:
bool load();
QtPluginInstanceFunction loadPlugin(); // loads and resolves instance
- bool unload(UnloadFlag flag = UnloadSys);
+ Q_AUTOTEST_EXPORT bool unload(UnloadFlag flag = UnloadSys);
void release();
QFunctionPointer resolve(const char *);
@@ -103,6 +103,11 @@ public:
void updatePluginState();
bool isPlugin();
+ static QLibraryPrivate* get(QLibrary* lib)
+ {
+ return lib->d.data();
+ }
+
private:
explicit QLibraryPrivate(const QString &canonicalFileName, const QString &version, QLibrary::LoadHints loadHints);
~QLibraryPrivate();
diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp
index 711b70ebf8f..c01c1a9999d 100644
--- a/src/corelib/text/qstring.cpp
+++ b/src/corelib/text/qstring.cpp
@@ -3681,95 +3681,24 @@ QString &QString::remove(QChar ch, Qt::CaseSensitivity cs)
\sa remove()
*/
-
-/*! \internal
- Instead of detaching, or reallocating if "before" is shorter than "after"
- and there isn't enough capacity, create a new string, copy characters to it
- as needed, then swap it with "str".
-*/
-static void replace_with_copy(QString &str, QSpan<size_t> indices, qsizetype blen,
- QStringView after)
-{
- const qsizetype alen = after.size();
- const char16_t *after_b = after.utf16();
-
- const QString::DataPointer &str_d = str.data_ptr();
- auto src_start = str_d.begin();
- const qsizetype newSize = str_d.size + indices.size() * (alen - blen);
- QString copy{ newSize, Qt::Uninitialized };
- QString::DataPointer &copy_d = copy.data_ptr();
- auto dst = copy_d.begin();
- for (size_t index : indices) {
- auto hit = str_d.begin() + index;
- dst = std::copy(src_start, hit, dst);
- dst = std::copy_n(after_b, alen, dst);
- src_start = hit + blen;
- }
- dst = std::copy(src_start, str_d.end(), dst);
- str.swap(copy);
-}
-
-// No detaching or reallocation is needed
-static void replace_in_place(QString &str, QSpan<size_t> indices,
- qsizetype blen, QStringView after)
-{
- const qsizetype alen = after.size();
- const char16_t *after_b = after.utf16();
- const char16_t *after_e = after.utf16() + after.size();
-
- if (blen == alen) { // Replace in place
- for (size_t index : indices)
- std::copy_n(after_b, alen, str.data_ptr().begin() + index);
- } else if (blen > alen) { // Replace from front
- char16_t *begin = str.data_ptr().begin();
- char16_t *hit = begin + indices.front();
- char16_t *to = hit;
- to = std::copy_n(after_b, alen, to);
- char16_t *movestart = hit + blen;
- for (size_t index : indices.sliced(1)) {
- hit = begin + index;
- to = std::move(movestart, hit, to);
- to = std::copy_n(after_b, alen, to);
- movestart = hit + blen;
- }
- to = std::move(movestart, str.data_ptr().end(), to);
- str.resize(std::distance(begin, to));
- } else { // blen < alen, Replace from back
- const qsizetype oldSize = str.data_ptr().size;
- const qsizetype adjust = indices.size() * (alen - blen);
- const qsizetype newSize = oldSize + adjust;
-
- str.resize(newSize);
- char16_t *begin = str.data_ptr().begin();
- char16_t *moveend = begin + oldSize;
- char16_t *to = str.data_ptr().end();
-
- for (auto it = indices.rbegin(), end = indices.rend(); it != end; ++it) {
- char16_t *hit = begin + *it;
- char16_t *movestart = hit + blen;
- to = std::move_backward(movestart, moveend, to);
- to = std::copy_backward(after_b, after_e, to);
- moveend = hit;
- }
- }
-}
-
-static void replace_helper(QString &str, QSpan<size_t> indices, qsizetype blen, QStringView after)
+static void replace_helper(QString &str, QSpan<qsizetype> indices, qsizetype blen, QStringView after)
{
const qsizetype oldSize = str.data_ptr().size;
const qsizetype adjust = indices.size() * (after.size() - blen);
const qsizetype newSize = oldSize + adjust;
+ using A = QStringAlgorithms<QString>;
if (str.data_ptr().needsDetach() || needsReallocate(str, newSize)) {
- replace_with_copy(str, indices, blen, after);
+ A::replace_helper(str, blen, after, indices);
return;
}
- if (QtPrivate::q_points_into_range(after.begin(), str))
+ if (QtPrivate::q_points_into_range(after.begin(), str)) {
// Copy after if it lies inside our own d.b area (which we could
// possibly invalidate via a realloc or modify by replacement)
- replace_in_place(str, indices, blen, QVarLengthArray(after.begin(), after.end()));
- else
- replace_in_place(str, indices, blen, after);
+ A::replace_helper(str, blen, QVarLengthArray(after.begin(), after.end()), indices);
+ } else {
+ A::replace_helper(str, blen, after, indices);
+ }
}
/*!
@@ -3811,8 +3740,8 @@ QString &QString::replace(qsizetype pos, qsizetype len, const QChar *after, qsiz
if (len > this->size() - pos)
len = this->size() - pos;
- size_t index = pos;
- replace_helper(*this, QSpan(&index, 1), len, QStringView{after, alen});
+ qsizetype indices[] = {pos};
+ replace_helper(*this, indices, len, QStringView{after, alen});
return *this;
}
@@ -3890,7 +3819,7 @@ QString &QString::replace(const QChar *before, qsizetype blen,
qsizetype index = 0;
- QVarLengthArray<size_t> indices;
+ QVarLengthArray<qsizetype> indices;
while ((index = matcher.indexIn(*this, index)) != -1) {
indices.push_back(index);
if (blen) // Step over before:
@@ -3925,7 +3854,7 @@ QString& QString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs
const char16_t cc = (cs == Qt::CaseSensitive ? ch.unicode() : ch.toCaseFolded().unicode());
- QVarLengthArray<size_t> indices;
+ QVarLengthArray<qsizetype> indices;
if (cs == Qt::CaseSensitive) {
const char16_t *begin = d.begin();
const char16_t *end = d.end();
diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h
index bb70678a958..6e4e532fd7d 100644
--- a/src/corelib/thread/qthread.h
+++ b/src/corelib/thread/qthread.h
@@ -172,27 +172,15 @@ inline Qt::HANDLE QThread::currentThreadId() noexcept
#elif defined(Q_PROCESSOR_X86_64) && ((defined(Q_OS_LINUX) && defined(__GLIBC__)) || defined(Q_OS_FREEBSD))
// x86_64 Linux, BSD uses FS
__asm__("mov %%fs:%c1, %0" : "=r" (tid) : "i" (2 * sizeof(void*)) : );
-#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN)
+#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) && defined(Q_CC_MSVC)
// See https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
- // First get the pointer to the TIB
- quint8 *tib;
-# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics
- __asm__("movq %%gs:0x30, %0" : "=r" (tib) : :);
-# else
- tib = reinterpret_cast<quint8 *>(__readgsqword(0x30));
-# endif
- // Then read the thread ID
- tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x48);
-#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN)
- // First get the pointer to the TIB
- quint8 *tib;
-# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics
- __asm__("movl %%fs:0x18, %0" : "=r" (tib) : :);
-# else
- tib = reinterpret_cast<quint8 *>(__readfsdword(0x18));
-# endif
- // Then read the thread ID
- tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x24);
+ tid = reinterpret_cast<Qt::HANDLE>(__readgsqword(0x48));
+#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) // !Q_CC_MSVC
+ __asm__("mov %%gs:0x48, %0" : "=r" (tid));
+#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) && defined(Q_CC_MSVC)
+ tid = reinterpret_cast<Qt::HANDLE>(__readfsdword(0x24));
+#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) // !Q_CC_MSVC
+ __asm__("mov %%fs:0x24, %0" : "=r" (tid));
#else
#undef QT_HAS_FAST_CURRENT_THREAD_ID
tid = currentThreadIdImpl();
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index 03eeed84465..974c486b915 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -517,6 +517,7 @@ QDate::QDate(int y, int m, int d, QCalendar cal)
*/
/*!
+ \overload primary
\fn bool QDate::isValid() const
Returns \c true if this date is valid; otherwise returns \c false.
@@ -525,6 +526,8 @@ QDate::QDate(int y, int m, int d, QCalendar cal)
*/
/*!
+ \overload primary
+
Returns the year of this date.
Uses \a cal as calendar, if supplied, else the Gregorian calendar.
@@ -557,8 +560,8 @@ int QDate::year(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload year()
+*/
int QDate::year() const
{
@@ -571,6 +574,8 @@ int QDate::year() const
}
/*!
+ \overload primary
+
Returns the month-number for the date.
Numbers the months of the year starting with 1 for the first. Uses \a cal
@@ -609,8 +614,8 @@ int QDate::month(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload month()
+*/
int QDate::month() const
{
@@ -623,6 +628,8 @@ int QDate::month() const
}
/*!
+ \overload primary
+
Returns the day of the month for this date.
Uses \a cal as calendar if supplied, else the Gregorian calendar (for which
@@ -642,8 +649,8 @@ int QDate::day(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload day()
+*/
int QDate::day() const
{
@@ -656,6 +663,8 @@ int QDate::day() const
}
/*!
+ \overload primary
+
Returns the weekday (1 = Monday to 7 = Sunday) for this date.
Uses \a cal as calendar if supplied, else the Gregorian calendar. Returns 0
@@ -674,8 +683,8 @@ int QDate::dayOfWeek(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload dayOfWeek()
+*/
int QDate::dayOfWeek() const
{
@@ -683,6 +692,8 @@ int QDate::dayOfWeek() const
}
/*!
+ \overload primary
+
Returns the day of the year (1 for the first day) for this date.
Uses \a cal as calendar if supplied, else the Gregorian calendar.
@@ -702,8 +713,8 @@ int QDate::dayOfYear(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload dayOfYear()
+*/
int QDate::dayOfYear() const
{
@@ -715,6 +726,8 @@ int QDate::dayOfYear() const
}
/*!
+ \overload primary
+
Returns the number of days in the month for this date.
Uses \a cal as calendar if supplied, else the Gregorian calendar (for which
@@ -735,8 +748,8 @@ int QDate::daysInMonth(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload daysInMonth()
+*/
int QDate::daysInMonth() const
{
@@ -749,6 +762,8 @@ int QDate::daysInMonth() const
}
/*!
+ \overload primary
+
Returns the number of days in the year for this date.
Uses \a cal as calendar if supplied, else the Gregorian calendar (for which
@@ -766,8 +781,8 @@ int QDate::daysInYear(QCalendar cal) const
}
/*!
- \overload
- */
+ \overload daysInYear()
+*/
int QDate::daysInYear() const
{
@@ -918,6 +933,7 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone)
/*!
\since 5.14
+ \overload primary
Returns the start-moment of the day.
@@ -972,8 +988,8 @@ QDateTime QDate::startOfDay(const QTimeZone &zone) const
}
/*!
- \overload
\since 6.5
+ \overload startOfDay()
*/
QDateTime QDate::startOfDay() const
{
@@ -982,8 +998,8 @@ QDateTime QDate::startOfDay() const
#if QT_DEPRECATED_SINCE(6, 9)
/*!
- \overload
\since 5.14
+ \overload startOfDay()
\deprecated [6.9] Use \c{startOfDay(const QTimeZone &)} instead.
Returns the start-moment of the day.
@@ -1073,6 +1089,7 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone)
/*!
\since 5.14
+ \overload primary
Returns the end-moment of the day.
@@ -1127,8 +1144,8 @@ QDateTime QDate::endOfDay(const QTimeZone &zone) const
}
/*!
- \overload
\since 6.5
+ \overload endOfDay()
*/
QDateTime QDate::endOfDay() const
{
@@ -1137,8 +1154,8 @@ QDateTime QDate::endOfDay() const
#if QT_DEPRECATED_SINCE(6, 9)
/*!
- \overload
\since 5.14
+ \overload endOfDay()
\deprecated [6.9] Use \c{endOfDay(const QTimeZone &) instead.
Returns the end-moment of the day.
@@ -1199,7 +1216,7 @@ static QString toStringIsoDate(QDate date)
}
/*!
- \overload
+ \overload toString()
Returns the date as a string. The \a format parameter determines the format
of the string.
@@ -1245,9 +1262,10 @@ QString QDate::toString(Qt::DateFormat format) const
}
/*!
+ \since 5.14
+ \overload primary
\fn QString QDate::toString(const QString &format, QCalendar cal) const
\fn QString QDate::toString(QStringView format, QCalendar cal) const
- \since 5.14
Returns the date as a string. The \a format parameter determines the format
of the result string. If \a cal is supplied, it determines the calendar used
@@ -1313,8 +1331,8 @@ QString QDate::toString(QStringView format, QCalendar cal) const
// Out-of-line no-calendar overloads, since QCalendar is a non-trivial type
/*!
- \overload
\since 5.10
+ \overload toString()
*/
QString QDate::toString(QStringView format) const
{
@@ -1322,8 +1340,8 @@ QString QDate::toString(QStringView format) const
}
/*!
- \overload
\since 4.6
+ \overload toString()
*/
QString QDate::toString(const QString &format) const
{
@@ -1414,9 +1432,8 @@ QDate QDate::addDays(qint64 ndays) const
}
/*!
- \fn QDate QDate::addDuration(std::chrono::days ndays) const
-
\since 6.4
+ \fn QDate QDate::addDuration(std::chrono::days ndays) const
Returns a QDate object containing a date \a ndays later than the
date of this object (or earlier if \a ndays is negative).
@@ -1436,6 +1453,8 @@ QDate QDate::addDays(qint64 ndays) const
*/
/*!
+ \overload primary
+
Returns a QDate object containing a date \a nmonths later than the
date of this object (or earlier if \a nmonths is negative).
@@ -1477,7 +1496,7 @@ QDate QDate::addMonths(int nmonths, QCalendar cal) const
}
/*!
- \overload
+ \overload addMonths()
*/
QDate QDate::addMonths(int nmonths) const
@@ -1509,6 +1528,8 @@ QDate QDate::addMonths(int nmonths) const
}
/*!
+ \overload primary
+
Returns a QDate object containing a date \a nyears later than the
date of this object (or earlier if \a nyears is negative).
@@ -1542,7 +1563,7 @@ QDate QDate::addYears(int nyears, QCalendar cal) const
}
/*!
- \overload
+ \overload addYears()
*/
QDate QDate::addYears(int nyears) const
@@ -1638,6 +1659,7 @@ qint64 QDate::daysTo(QDate d) const
#if QT_CONFIG(datestring) // depends on, so implies, textdate
/*!
+ \overload
\fn QDate QDate::fromString(const QString &string, Qt::DateFormat format)
Returns the QDate represented by the \a string, using the
@@ -1651,8 +1673,8 @@ qint64 QDate::daysTo(QDate d) const
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QDate QDate::fromString(QStringView string, Qt::DateFormat format)
{
@@ -1702,6 +1724,7 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
}
/*!
+ \overload primary
\fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal)
Returns the QDate represented by the \a string, using the \a
@@ -1833,14 +1856,14 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format)
*/
/*!
- \fn QDate QDate::fromString(QStringView string, QStringView format, QCalendar cal)
- \overload
\since 6.0
+ \overload fromString()
+ \fn QDate QDate::fromString(QStringView string, QStringView format, QCalendar cal)
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QDate QDate::fromString(const QString &string, QStringView format, int baseYear, QCalendar cal)
{
@@ -1860,34 +1883,34 @@ QDate QDate::fromString(const QString &string, QStringView format, int baseYear,
}
/*!
- \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal)
- \overload
\since 5.14
+ \overload fromString()
+ \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal)
*/
/*!
- \fn QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal)
- \overload
\since 6.0
+ \overload fromString()
+ \fn QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal)
*/
/*!
- \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
*/
/*!
- \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear)
Uses a default-constructed QCalendar.
*/
/*!
- \overload
\since 6.7
+ \overload fromString()
Uses a default-constructed QCalendar.
*/
@@ -1897,16 +1920,16 @@ QDate QDate::fromString(const QString &string, QStringView format, int baseYear)
}
/*!
- \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear)
Uses a default-constructed QCalendar.
*/
#endif // datestring
/*!
- \overload
+ \overload isValid()
Returns \c true if the specified date (\a year, \a month, and \a day) is
valid in the Gregorian calendar; otherwise returns \c false.
@@ -2037,6 +2060,8 @@ QTime::QTime(int h, int m, int s, int ms)
*/
/*!
+ \overload primary
+
Returns \c true if the time is valid; otherwise returns \c false. For example,
the time 23:30:55.746 is valid, but 24:12:30 is invalid.
@@ -2115,7 +2140,7 @@ int QTime::msec() const
#if QT_CONFIG(datestring) // depends on, so implies, textdate
/*!
- \overload
+ \overload toString()
Returns the time as a string. The \a format parameter determines
the format of the string.
@@ -2155,6 +2180,7 @@ QString QTime::toString(Qt::DateFormat format) const
}
/*!
+ \overload primary
\fn QString QTime::toString(const QString &format) const
\fn QString QTime::toString(QStringView format) const
@@ -2261,11 +2287,11 @@ QString QTime::toString(Qt::DateFormat format) const
\sa fromString(), QDate::toString(), QDateTime::toString(), QLocale::toString()
*/
-// ### Qt 7 The 't' format specifiers should be specific to QDateTime (compare fromString).
QString QTime::toString(QStringView format) const
{
return QLocale::c().toString(*this, format);
}
+// ### Qt 7 The 't' format specifiers should be specific to QDateTime (compare fromString).
#endif // datestring
/*!
@@ -2557,6 +2583,7 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool *
}
/*!
+ \overload
\fn QTime QTime::fromString(const QString &string, Qt::DateFormat format)
Returns the time represented in the \a string as a QTime using the
@@ -2566,8 +2593,8 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool *
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QTime QTime::fromString(QStringView string, Qt::DateFormat format)
{
@@ -2586,6 +2613,7 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format)
}
/*!
+ \overload primary
\fn QTime QTime::fromString(const QString &string, const QString &format)
Returns the QTime represented by the \a string, using the \a
@@ -2664,14 +2692,14 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format)
*/
/*!
- \fn QTime QTime::fromString(QStringView string, QStringView format)
- \overload
\since 6.0
+ \overload fromString()
+ \fn QTime QTime::fromString(QStringView string, QStringView format)
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QTime QTime::fromString(const QString &string, QStringView format)
{
@@ -2691,7 +2719,7 @@ QTime QTime::fromString(const QString &string, QStringView format)
/*!
- \overload
+ \overload isValid()
Returns \c true if the specified time is valid; otherwise returns
false.
@@ -4012,6 +4040,7 @@ QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSecond
/*!
\since 5.2
+ \overload primary
Constructs a datetime with the given \a date and \a time, using the time
representation described by \a timeZone.
@@ -4620,7 +4649,7 @@ void QDateTime::setSecsSinceEpoch(qint64 secs)
#if QT_CONFIG(datestring) // depends on, so implies, textdate
/*!
- \overload
+ \overload toString()
Returns the datetime as a string in the \a format given.
@@ -4712,9 +4741,10 @@ QString QDateTime::toString(Qt::DateFormat format) const
}
/*!
+ \since 5.14
+ \overload primary
\fn QString QDateTime::toString(const QString &format, QCalendar cal) const
\fn QString QDateTime::toString(QStringView format, QCalendar cal) const
- \since 5.14
Returns the datetime as a string. The \a format parameter determines the
format of the result string. If \a cal is supplied, it determines the
@@ -4761,8 +4791,8 @@ QString QDateTime::toString(QStringView format, QCalendar cal) const
// Out-of-line no-calendar overloads, since QCalendar is a non-trivial type
/*!
- \overload
\since 5.10
+ \overload toString()
*/
QString QDateTime::toString(QStringView format) const
{
@@ -4770,8 +4800,8 @@ QString QDateTime::toString(QStringView format) const
}
/*!
- \overload
\since 4.6
+ \overload toString()
*/
QString QDateTime::toString(const QString &format) const
{
@@ -5370,6 +5400,7 @@ Qt::weak_ordering compareThreeWay(const QDateTime &lhs, const QDateTime &rhs)
/*!
\since 6.5
+ \overload primary
\fn QDateTime QDateTime::currentDateTime(const QTimeZone &zone)
Returns the system clock's current datetime, using the time representation
@@ -5379,8 +5410,8 @@ Qt::weak_ordering compareThreeWay(const QDateTime &lhs, const QDateTime &rhs)
*/
/*!
- \overload
\since 0.90
+ \overload currentDateTime()
*/
QDateTime QDateTime::currentDateTime()
{
@@ -5426,8 +5457,9 @@ QDateTime QDateTime::currentDateTimeUtc()
*/
/*!
- \fn template <typename Clock, typename Duration> QDateTime QDateTime::fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time)
\since 6.4
+ \overload primary
+ \fn template <typename Clock, typename Duration> QDateTime QDateTime::fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time)
Constructs a datetime representing the same point in time as \a time,
using Qt::UTC as its time representation.
@@ -5451,7 +5483,7 @@ QDateTime QDateTime::currentDateTimeUtc()
/*!
\since 6.4
- \overload
+ \overload fromStdTimePoint()
Constructs a datetime representing the same point in time as \a time,
using Qt::UTC as its time representation.
@@ -5627,7 +5659,7 @@ qint64 QDateTime::currentSecsSinceEpoch() noexcept
#if QT_DEPRECATED_SINCE(6, 9)
/*!
\since 5.2
- \overload
+ \overload fromMSecsSinceEpoch()
\deprecated [6.9] Pass a \l QTimeZone instead, or omit \a spec and \a offsetSeconds.
Returns a datetime representing a moment the given number \a msecs of
@@ -5656,7 +5688,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int of
/*!
\since 5.8
- \overload
+ \overload fromSecsSinceEpoch
\deprecated [6.9] Pass a \l QTimeZone instead, or omit \a spec and \a offsetSeconds.
Returns a datetime representing a moment the given number \a secs of seconds
@@ -5686,6 +5718,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs
/*!
\since 5.2
+ \overload primary
Returns a datetime representing a moment the given number \a msecs of
milliseconds after the start, in UTC, of the year 1970, described as
@@ -5707,7 +5740,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone
}
/*!
- \overload
+ \overload fromMSecsSinceEpoch()
*/
QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs)
{
@@ -5716,6 +5749,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs)
/*!
\since 5.8
+ \overload primary
Returns a datetime representing a moment the given number \a secs of seconds
after the start, in UTC, of the year 1970, described as specified by \a
@@ -5737,7 +5771,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone)
}
/*!
- \overload
+ \overload fromSecsSinceEpoch()
*/
QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs)
{
@@ -5747,6 +5781,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs)
#if QT_CONFIG(datestring) // depends on, so implies, textdate
/*!
+ \overload
\fn QDateTime QDateTime::fromString(const QString &string, Qt::DateFormat format)
Returns the QDateTime represented by the \a string, using the
@@ -5759,8 +5794,8 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs)
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
{
@@ -5901,6 +5936,7 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
}
/*!
+ \overload primary
\fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal)
Returns the QDateTime represented by the \a string, using the \a
@@ -5984,14 +6020,14 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format)
*/
/*!
- \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, QCalendar cal)
- \overload
\since 6.0
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, QCalendar cal)
*/
/*!
- \overload
\since 6.0
+ \overload fromString()
*/
QDateTime QDateTime::fromString(const QString &string, QStringView format, int baseYear,
QCalendar cal)
@@ -6015,34 +6051,34 @@ QDateTime QDateTime::fromString(const QString &string, QStringView format, int b
}
/*!
- \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal)
- \overload
\since 5.14
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal)
*/
/*!
- \fn QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal)
- \overload
\since 6.0
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal)
*/
/*!
- \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal)
*/
/*!
- \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear)
Uses a default-constructed QCalendar.
*/
/*!
- \overload
\since 6.7
+ \overload fromString()
Uses a default-constructed QCalendar.
*/
@@ -6052,9 +6088,9 @@ QDateTime QDateTime::fromString(const QString &string, QStringView format, int b
}
/*!
- \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear)
- \overload
\since 6.7
+ \overload fromString()
+ \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear)
Uses a default-constructed QCalendar.
*/
diff --git a/src/corelib/time/qgregoriancalendar.cpp b/src/corelib/time/qgregoriancalendar.cpp
index d46d24ac30d..dfb99c5073d 100644
--- a/src/corelib/time/qgregoriancalendar.cpp
+++ b/src/corelib/time/qgregoriancalendar.cpp
@@ -31,6 +31,7 @@ static_assert(qDivMod<86400>(-172800).remainder == 0);
/*!
\since 5.14
+ \internal
\class QGregorianCalendar
\inmodule QtCore
diff --git a/src/corelib/time/qjalalicalendar.cpp b/src/corelib/time/qjalalicalendar.cpp
index 8bc9fe125e7..683ce6e7712 100644
--- a/src/corelib/time/qjalalicalendar.cpp
+++ b/src/corelib/time/qjalalicalendar.cpp
@@ -39,6 +39,7 @@ qint64 firstDayOfYear(int year, int cycleNo)
/*!
\since 5.14
+ \internal
\class QJalaliCalendar
\inmodule QtCore
diff --git a/src/corelib/time/qjuliancalendar.cpp b/src/corelib/time/qjuliancalendar.cpp
index 47da952b84a..cf3718f471d 100644
--- a/src/corelib/time/qjuliancalendar.cpp
+++ b/src/corelib/time/qjuliancalendar.cpp
@@ -13,6 +13,7 @@ using namespace QRoundingDown;
/*!
\since 5.14
+ \internal
\class QJulianCalendar
\inmodule QtCore
diff --git a/src/corelib/time/qmilankoviccalendar.cpp b/src/corelib/time/qmilankoviccalendar.cpp
index a3ffa2a3053..14aef83afe3 100644
--- a/src/corelib/time/qmilankoviccalendar.cpp
+++ b/src/corelib/time/qmilankoviccalendar.cpp
@@ -13,6 +13,7 @@ using namespace QRoundingDown;
/*!
\since 5.14
+ \internal
\class QMilankovicCalendar
\inmodule QtCore
diff --git a/src/corelib/time/qromancalendar.cpp b/src/corelib/time/qromancalendar.cpp
index ae113cf8323..a8ce027275a 100644
--- a/src/corelib/time/qromancalendar.cpp
+++ b/src/corelib/time/qromancalendar.cpp
@@ -9,6 +9,7 @@ QT_BEGIN_NAMESPACE
/*!
\since 5.14
+ \internal
\class QRomanCalendar
\inmodule QtCore
diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp
index f44c681ea80..7b43aab22d1 100644
--- a/src/corelib/time/qtimezone.cpp
+++ b/src/corelib/time/qtimezone.cpp
@@ -29,7 +29,7 @@ static QTimeZonePrivate *newBackendTimeZone()
return new QMacTimeZonePrivate();
#elif defined(Q_OS_ANDROID)
return new QAndroidTimeZonePrivate();
-#elif defined(Q_OS_UNIX)
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
return new QTzTimeZonePrivate();
#elif QT_CONFIG(icu)
return new QIcuTimeZonePrivate();
@@ -50,7 +50,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId)
return new QMacTimeZonePrivate(ianaId);
#elif defined(Q_OS_ANDROID)
return new QAndroidTimeZonePrivate(ianaId);
-#elif defined(Q_OS_UNIX)
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
return new QTzTimeZonePrivate(ianaId);
#elif QT_CONFIG(icu)
return new QIcuTimeZonePrivate(ianaId);
@@ -1482,7 +1482,8 @@ QTimeZone QTimeZone::utc()
bool QTimeZone::isTimeZoneIdAvailable(const QByteArray &ianaId)
{
-#if defined(Q_OS_UNIX) && !(defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN))
+#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \
+ || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS))
// Keep #if-ery consistent with selection of QTzTimeZonePrivate in
// newBackendTimeZone(). Skip the pre-check, as the TZ backend accepts POSIX
// zone IDs, which need not be valid IANA IDs. See also QTBUG-112006.
diff --git a/src/corelib/time/qtimezonelocale.cpp b/src/corelib/time/qtimezonelocale.cpp
index a794682fe0b..6ad4aa1479c 100644
--- a/src/corelib/time/qtimezonelocale.cpp
+++ b/src/corelib/time/qtimezonelocale.cpp
@@ -611,6 +611,27 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc
// Custom zone with perverse m_id ?
return;
}
+ const auto isMixedCaseAbbrev = [tail](char ch) {
+ // cv-RU and en-GU abbreviate Chamorro as ChST
+ // scn-IT abbreviates Cuba as CuT/CuST/CuDT
+ // blo-BJ abbreviates GMT as Gk
+ switch (tail.size()) {
+ case 2: return tail == "Gk";
+ case 3: return tail == "CuT";
+ case 4:
+ if (tail[0] == 'C' && tail[1] == ch && tail[3] == 'T') {
+ switch (ch) {
+ case 'h': return tail[2] == 'S';
+ case 'u': return tail[2] == 'S' || tail[2] == 'D';
+ default: break;
+ }
+ }
+ return false;
+ default:
+ break;
+ }
+ return false;
+ };
// Even if it is abbr or city name, we don't care if we've found one before.
bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword = false;
@@ -632,7 +653,7 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc
maybeCityName = false;
inword = false;
} else if (QChar::isLower(ch)) {
- maybeAbbr = false;
+ maybeAbbr = isMixedCaseAbbrev(ch);
// Dar_es_Salaam shows both cases as word starts
inword = true;
} else if (QChar::isUpper(ch)) {
@@ -891,8 +912,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
if (best.ianaIdIndex != invalidIanaId)
return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
- // Now try for a region format:
- best = {};
+ // Now try for a region format.
+ // Since we may get the IANA ID directly from a zone, we may not need an
+ // ianaIdIndex from CLDR-derived tables: and the active backend may know
+ // some zones newer than our latest CLDR.
+ NamePrefixMatch found;
for (const qsizetype locInd : indices) {
const LocaleZoneData &locData = localeZoneData[locInd];
const LocaleZoneData &nextData = localeZoneData[locInd + 1];
@@ -928,11 +952,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
QStringView city = row.exemplarCity().viewData(exemplarCityTable);
if (textMatches(city)) {
qsizetype length = cut + city.size() + suffix.size();
- if (length > best.nameLength) {
- bool gotZone = row.ianaIdIndex == best.ianaIdIndex
+ if (length > found.nameLength) {
+ bool gotZone = row.ianaId() == found.ianaId // (cheap pre-test)
|| QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray());
if (gotZone)
- best = { length, timeType, row.ianaIdIndex };
+ found = { row.ianaId().toByteArray(), length, timeType };
}
}
}
@@ -945,38 +969,16 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale,
QString city = QString::fromLatin1(local.replace('_', ' '));
if (textMatches(city)) {
qsizetype length = cut + city.size() + suffix.size();
- if (length > best.nameLength) {
- // Have to find iana in ianaIdData. Although its entries
- // from locale-independent data are nicely sorted, the
- // rest are (sadly) not.
- QByteArrayView run(ianaIdData, qstrlen(ianaIdData));
- // std::size includes the trailing '\0', so subtract one:
- const char *stop = ianaIdData + std::size(ianaIdData) - 1;
- while (run != iana) {
- if (run.end() < stop) { // Step to the next:
- run = QByteArrayView(run.end() + 1);
- } else {
- run = QByteArrayView();
- break;
- }
- }
- if (!run.isEmpty()) {
- Q_ASSERT(run == iana);
- const auto ianaIdIndex = run.begin() - ianaIdData;
- Q_ASSERT(ianaIdIndex <= (std::numeric_limits<quint16>::max)());
- best = { length, timeType, quint16(ianaIdIndex) };
- }
- }
+ if (length > found.nameLength)
+ found = { iana, length, timeType };
}
}
// TODO: similar for territories, at least once localeName() does so.
}
}
- if (best.ianaIdIndex != invalidIanaId)
- return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType };
#undef localeRows
- return {}; // No match found.
+ return found;
}
QTimeZonePrivate::NamePrefixMatch
diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp
index 556f7e21402..3f0254b1c5f 100644
--- a/src/corelib/time/qtimezoneprivate.cpp
+++ b/src/corelib/time/qtimezoneprivate.cpp
@@ -1177,7 +1177,7 @@ QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds)
}
Q_ASSERT(!name.isEmpty());
} else { // Fall back to a UTC-offset name:
- name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName);
+ name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName);
id = name.toUtf8();
}
init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name);
@@ -1292,12 +1292,16 @@ QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType,
if (!(name.startsWith("GMT"_L1) || name.startsWith("UTC"_L1)) || name.size() < 5)
return false;
// Fallback drops trailing ":00" minute:
- QStringView tail{avoid};
+ QStringView tail{avoid}; // TODO: deal with sign earlier ! Also: invisible Unicode !
tail = tail.sliced(3);
- if (tail.endsWith(":00"_L1))
- tail = tail.chopped(3);
if (name.sliced(3) == tail)
return true;
+ while (tail.endsWith(":00"_L1))
+ tail = tail.chopped(3);
+ while (name.endsWith(":00"_L1))
+ name = name.chopped(3);
+ if (name == tail)
+ return true;
// Accept U+2212 as minus sign:
const QChar sign = name[3] == u'\u2212' ? u'-' : name[3];
// Fallback doesn't zero-pad hour:
diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h
index 804c28af372..9a86ded6efb 100644
--- a/src/corelib/time/qtimezoneprivate_p.h
+++ b/src/corelib/time/qtimezoneprivate_p.h
@@ -50,7 +50,7 @@ class Q_AUTOTEST_EXPORT QTimeZonePrivate : public QSharedData
{
// Nothing should be copy-assigning instances of either this or its derived
// classes (only clone() should copy, using the copy-constructor):
- bool operator=(const QTimeZonePrivate &) const = delete;
+ QTimeZonePrivate &operator=(const QTimeZonePrivate &) const = delete;
protected:
QTimeZonePrivate(const QTimeZonePrivate &other) = default;
public:
@@ -210,7 +210,7 @@ Q_DECLARE_TYPEINFO(QTimeZonePrivate::Data, Q_RELOCATABLE_TYPE);
class Q_AUTOTEST_EXPORT QUtcTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QUtcTimeZonePrivate &) const = delete;
+ QUtcTimeZonePrivate &operator=(const QUtcTimeZonePrivate &) const = delete;
QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other);
public:
// Create default UTC time zone
@@ -273,7 +273,7 @@ private:
#if QT_CONFIG(timezone_tzdb)
class QChronoTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QChronoTimeZonePrivate &) const = delete;
+ QChronoTimeZonePrivate &operator=(const QChronoTimeZonePrivate &) const = delete;
QChronoTimeZonePrivate(const QChronoTimeZonePrivate &) = default;
public:
QChronoTimeZonePrivate();
@@ -307,7 +307,7 @@ private:
#elif defined(Q_OS_DARWIN)
class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QMacTimeZonePrivate &) const = delete;
+ QMacTimeZonePrivate &operator=(const QMacTimeZonePrivate &) const = delete;
QMacTimeZonePrivate(const QMacTimeZonePrivate &other);
public:
// Create default time zone
@@ -353,7 +353,7 @@ private:
#elif defined(Q_OS_ANDROID)
class QAndroidTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QAndroidTimeZonePrivate &) const = delete;
+ QAndroidTimeZonePrivate &operator=(const QAndroidTimeZonePrivate &) const = delete;
QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &) = default;
public:
// Create default time zone
@@ -388,7 +388,7 @@ private:
QJniObject androidTimeZone;
};
-#elif defined(Q_OS_UNIX)
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS)
struct QTzTransitionTime
{
qint64 atMSecsSinceEpoch;
@@ -421,7 +421,7 @@ struct QTzTimeZoneCacheEntry
class Q_AUTOTEST_EXPORT QTzTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QTzTimeZonePrivate &) const = delete;
+ QTzTimeZonePrivate &operator=(const QTzTimeZonePrivate &) const = delete;
QTzTimeZonePrivate(const QTzTimeZonePrivate &) = default;
public:
// Create default time zone
@@ -474,7 +474,7 @@ private:
#elif QT_CONFIG(icu)
class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QIcuTimeZonePrivate &) const = delete;
+ QIcuTimeZonePrivate &operator=(const QIcuTimeZonePrivate &) const = delete;
QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other);
public:
// Create default time zone
@@ -518,7 +518,7 @@ private:
#elif defined(Q_OS_WIN)
class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate
{
- bool operator=(const QWinTimeZonePrivate &) const = delete;
+ QWinTimeZonePrivate &operator=(const QWinTimeZonePrivate &) const = delete;
QWinTimeZonePrivate(const QWinTimeZonePrivate &) = default;
public:
struct QWinTransitionRule {
diff --git a/src/corelib/tools/qiterator.qdoc b/src/corelib/tools/qiterator.qdoc
index 3d8ea595167..d517457027a 100644
--- a/src/corelib/tools/qiterator.qdoc
+++ b/src/corelib/tools/qiterator.qdoc
@@ -165,7 +165,7 @@
position between the second and third item, and returns the second
item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -211,7 +211,7 @@
position between the second and third item, returning the second
item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
If you want to find all occurrences of a particular value, use
findNext() in a loop.
@@ -260,7 +260,7 @@
position between the second and third item, returning the second
item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -321,7 +321,7 @@
position between the second and third item, returning the second
item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
If you want to remove items as you iterate over the set, use
remove().
@@ -718,7 +718,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -768,7 +768,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -819,7 +819,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
If you want to find all occurrences of a particular value, use
findNext() in a loop. For example:
@@ -867,7 +867,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -931,7 +931,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
Here's how to iterate over the elements in reverse order:
@@ -994,7 +994,7 @@
next() advances the iterator to the position between the second
and third item; and so on.
- \image javaiterators1.png
+ \image javaiterators1.svg Java-style iterators point between items
If you want to find all occurrences of a particular value, use
findNext() in a loop. For example:
diff --git a/src/corelib/tools/quniquehandle_p.h b/src/corelib/tools/quniquehandle_p.h
index 3ba557e838d..fd6ab693912 100644
--- a/src/corelib/tools/quniquehandle_p.h
+++ b/src/corelib/tools/quniquehandle_p.h
@@ -18,6 +18,7 @@
#include <QtCore/qtconfigmacros.h>
#include <QtCore/qassert.h>
#include <QtCore/qcompare.h>
+#include <QtCore/qfunctionaltools_impl.h>
#include <QtCore/qswap.h>
#include <QtCore/qtclasshelpermacros.h>
@@ -99,6 +100,42 @@ QT_BEGIN_NAMESPACE
...
+ Example 3:
+
+ struct TempFileTraits {
+ using Type = FILE*;
+
+ static Type invalidValue() {
+ return nullptr;
+ }
+
+ static bool close(Type handle) {
+ return fclose(handle) == 0;
+ }
+ };
+
+ struct TempFileDeleter {
+ using Type = TempFileTraits::Type;
+
+ void operator()(Type handle) {
+ if (handle != TempFileTraits::invalidValue()) {
+ TempFileTraits::close(handle);
+ if (path)
+ remove(path);
+ }
+ }
+
+ const char* path{ nullptr };
+ };
+
+ using TempFileHandle = QUniqueHandle<TempFileTraits, TempFileDeleter>;
+
+ Usage:
+
+ TempFileHandle tempFile(fopen("temp.bin", "wb"), TempFileDeleter{ "my_temp.bin" });
+
+ ...
+
NOTE: The QUniqueHandle assumes that closing a resource is
guaranteed to succeed, and provides no support for handling failure
to close a resource. It is therefore only recommended for use cases
@@ -108,9 +145,32 @@ QT_BEGIN_NAMESPACE
// clang-format off
+namespace QtUniqueHandleTraits {
+
template <typename HandleTraits>
-class QUniqueHandle
+struct DefaultDeleter
{
+ using Type = typename HandleTraits::Type;
+
+ void operator()(Type handle) const noexcept
+ {
+ if (handle != HandleTraits::invalidValue()) {
+ const bool success = HandleTraits::close(handle);
+ Q_ASSERT(success);
+ }
+ }
+};
+
+} // namespace QtUniqueHandleTraits
+
+template <typename HandleTraits, typename Deleter = QtUniqueHandleTraits::DefaultDeleter<HandleTraits>>
+class QUniqueHandle : private QtPrivate::CompactStorage<Deleter>
+{
+ using Storage = QtPrivate::CompactStorage<Deleter>;
+
+ template <typename D>
+ using if_default_constructible = std::enable_if_t<std::is_default_constructible_v<D>, bool>;
+
public:
using Type = typename HandleTraits::Type;
static_assert(std::is_nothrow_default_constructible_v<Type>);
@@ -120,6 +180,11 @@ public:
static_assert(std::is_nothrow_copy_assignable_v<Type>);
static_assert(std::is_nothrow_move_assignable_v<Type>);
static_assert(std::is_nothrow_destructible_v<Type>);
+ static_assert(std::is_nothrow_copy_constructible_v<Deleter>);
+ static_assert(std::is_nothrow_move_constructible_v<Deleter>);
+ static_assert(std::is_nothrow_copy_assignable_v<Deleter>);
+ static_assert(std::is_nothrow_move_assignable_v<Deleter>);
+ static_assert(std::is_nothrow_destructible_v<Deleter>);
static_assert(noexcept(std::declval<Type>() == std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() != std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() < std::declval<Type>()));
@@ -127,16 +192,24 @@ public:
static_assert(noexcept(std::declval<Type>() > std::declval<Type>()));
static_assert(noexcept(std::declval<Type>() >= std::declval<Type>()));
- QUniqueHandle() = default;
-
- explicit QUniqueHandle(const Type &handle) noexcept
+ template <if_default_constructible<Deleter> = true>
+ explicit QUniqueHandle(const Type& handle = HandleTraits::invalidValue()) noexcept
: m_handle{ handle }
{}
- QUniqueHandle(QUniqueHandle &&other) noexcept
- : m_handle{ other.release() }
+ QUniqueHandle(const Type &handle, const Deleter &deleter) noexcept
+ : Storage{ deleter }, m_handle{ handle }
+ {}
+
+ QUniqueHandle(const Type &handle, Deleter &&deleter) noexcept
+ : Storage{ std::move(deleter) }, m_handle{ handle }
{}
+ QUniqueHandle(QUniqueHandle &&other) noexcept
+ : Storage{ std::move(other.deleter()) }, m_handle{ other.release() }
+ {
+ }
+
~QUniqueHandle() noexcept
{
close();
@@ -145,6 +218,7 @@ public:
void swap(QUniqueHandle &other) noexcept
{
qSwap(m_handle, other.m_handle);
+ qSwap(deleter(), other.deleter());
}
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QUniqueHandle)
@@ -168,6 +242,16 @@ public:
return m_handle;
}
+ [[nodiscard]] Deleter& deleter() noexcept
+ {
+ return Storage::object();
+ }
+
+ [[nodiscard]] const Deleter& deleter() const noexcept
+ {
+ return Storage::object();
+ }
+
void reset(const Type& handle = HandleTraits::invalidValue()) noexcept
{
if (handle == m_handle)
@@ -193,8 +277,7 @@ public:
if (!isValid())
return;
- const bool success = HandleTraits::close(m_handle);
- Q_ASSERT(success);
+ deleter()(m_handle);
m_handle = HandleTraits::invalidValue();
}
@@ -222,8 +305,8 @@ private:
// clang-format on
-template <typename Trait>
-void swap(QUniqueHandle<Trait> &lhs, QUniqueHandle<Trait> &rhs) noexcept
+template <typename Trait, typename Deleter>
+void swap(QUniqueHandle<Trait, Deleter> &lhs, QUniqueHandle<Trait, Deleter> &rhs) noexcept
{
lhs.swap(rhs);
}
diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake
index ad76bb095a0..5001f2deeec 100644
--- a/src/gui/configure.cmake
+++ b/src/gui/configure.cmake
@@ -1217,17 +1217,6 @@ qt_feature("xcb-egl-plugin" PRIVATE
CONDITION QT_FEATURE_egl AND QT_FEATURE_opengl
EMIT_IF QT_FEATURE_xcb
)
-qt_feature("xcb-native-painting" PRIVATE
- LABEL "Native painting (experimental)"
- AUTODETECT OFF
- CONDITION QT_FEATURE_xcb_xlib AND QT_FEATURE_fontconfig AND XRender_FOUND
- EMIT_IF QT_FEATURE_xcb
-)
-qt_feature("xrender" PRIVATE
- LABEL "XRender for native painting"
- CONDITION QT_FEATURE_xcb_native_painting
- EMIT_IF QT_FEATURE_xcb AND QT_FEATURE_xcb_native_painting
-)
qt_feature("xcb-xlib" PRIVATE
LABEL "XCB Xlib"
CONDITION QT_FEATURE_xlib AND X11_XCB_FOUND
diff --git a/src/gui/doc/src/richtext.qdoc b/src/gui/doc/src/richtext.qdoc
index 2fa49a31e03..f94c436bd80 100644
--- a/src/gui/doc/src/richtext.qdoc
+++ b/src/gui/doc/src/richtext.qdoc
@@ -1047,6 +1047,12 @@
\li \c type (\c 1, \c a, \c A, \c square, \c disc, \c circle)
\endlist
+ Additionally, the following attribute is supported by the \c ol tag:
+
+ \list
+ \li \c start
+ \endlist
+
\section1 Table Cell Attributes
The following attributes are supported by the \c td and \c th
diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp
index 6ba113ef8fb..4a8b409379c 100644
--- a/src/gui/image/qimage.cpp
+++ b/src/gui/image/qimage.cpp
@@ -47,6 +47,9 @@
#include <memory>
+#define QT_XFORM_TYPE_MSBFIRST 0
+#define QT_XFORM_TYPE_LSBFIRST 1
+
QT_BEGIN_NAMESPACE
class QCmyk32;
@@ -4447,6 +4450,8 @@ int QImage::metric(PaintDeviceMetric metric) const
trigx += m11; \
trigy += m12;
// END OF MACRO
+
+static
bool qt_xForm_helper(const QTransform &trueMat, int xoffset, int type, int depth,
uchar *dptr, qsizetype dbpl, int p_inc, int dHeight,
const uchar *sptr, qsizetype sbpl, int sWidth, int sHeight)
diff --git a/src/gui/image/qplatformpixmap.h b/src/gui/image/qplatformpixmap.h
index 5621afa4da5..3346ca85375 100644
--- a/src/gui/image/qplatformpixmap.h
+++ b/src/gui/image/qplatformpixmap.h
@@ -33,7 +33,7 @@ public:
enum ClassId { RasterClass, DirectFBClass,
BlitterClass, Direct2DClass,
- X11Class, CustomClass = 1024 };
+ CustomClass = 1024 };
QPlatformPixmap(PixelType pixelType, int classId);
virtual ~QPlatformPixmap();
@@ -111,7 +111,6 @@ protected:
private:
friend class QPixmap;
- friend class QX11PlatformPixmap;
friend class QImagePixmapCleanupHooks; // Needs to set is_cached
int detach_no;
@@ -122,10 +121,6 @@ private:
uint is_cached;
};
-# define QT_XFORM_TYPE_MSBFIRST 0
-# define QT_XFORM_TYPE_LSBFIRST 1
-Q_GUI_EXPORT bool qt_xForm_helper(const QTransform&, int, int, int, uchar*, qsizetype, int, int, const uchar*, qsizetype, int, int);
-
QT_END_NAMESPACE
#endif // QPLATFORMPIXMAP_H
diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp
index 622c2e5bc92..9eb31f1b6d4 100644
--- a/src/gui/itemmodels/qfilesystemmodel.cpp
+++ b/src/gui/itemmodels/qfilesystemmodel.cpp
@@ -1787,14 +1787,17 @@ bool QFileSystemModel::event(QEvent *event)
bool QFileSystemModel::rmdir(const QModelIndex &aindex)
{
+ Q_D(QFileSystemModel);
+
QString path = filePath(aindex);
const bool success = QDir().rmdir(path);
-#if QT_CONFIG(filesystemwatcher)
if (success) {
- QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func());
+#if QT_CONFIG(filesystemwatcher)
d->fileInfoGatherer->removePath(path);
- }
#endif
+ QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(aindex.parent());
+ d->removeNode(parentNode, fileName(aindex));
+ }
return success;
}
diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp
index ae730a9b3af..7b1e76cc78f 100644
--- a/src/gui/kernel/qevent.cpp
+++ b/src/gui/kernel/qevent.cpp
@@ -1146,6 +1146,33 @@ Q_IMPL_POINTER_EVENT(QHoverEvent)
*/
/*!
+ \property QWheelEvent::device
+ \brief the device from which the wheel event originated
+
+ \sa pointingDevice()
+*/
+
+/*!
+ \property QWheelEvent::inverted
+ \since 5.7
+ \brief whether the delta values delivered with the event are inverted
+
+ Normally, a vertical wheel will produce a QWheelEvent with positive delta
+ values if the top of the wheel is rotating away from the hand operating it.
+ Similarly, a horizontal wheel movement will produce a QWheelEvent with
+ positive delta values if the top of the wheel is moved to the left.
+
+ However, on some platforms this is configurable, so that the same
+ operations described above will produce negative delta values (but with the
+ same magnitude). With the inverted property a wheel event consumer can
+ choose to always follow the direction of the wheel, regardless of the
+ system settings, but only for specific widgets.
+
+ \note Many platforms provide no such information. On such platforms
+ \l inverted always returns false.
+*/
+
+/*!
\fn bool QWheelEvent::inverted() const
\since 5.7
@@ -1236,6 +1263,24 @@ bool QWheelEvent::isEndEvent() const
#endif // QT_CONFIG(wheelevent)
/*!
+ \property QWheelEvent::pixelDelta
+ \brief the scrolling distance in pixels on screen
+
+ This value is provided on platforms that support high-resolution
+ pixel-based delta values, such as \macos. The value should be used
+ directly to scroll content on screen.
+
+ \note On platforms that support scrolling \l{phase()}{phases}, the delta
+ may be null when scrolling is about to begin (Qt::ScrollBegin) or has
+ ended (Qt::ScrollEnd).
+
+ \note On X11 this value is driver-specific and unreliable, use
+ angleDelta() instead.
+
+ \sa angleDelta()
+*/
+
+/*!
\fn QPoint QWheelEvent::pixelDelta() const
Returns the scrolling distance in pixels on screen. This value is
@@ -1256,6 +1301,27 @@ bool QWheelEvent::isEndEvent() const
*/
/*!
+ \property QWheelEvent::angleDelta
+ \brief the relative amount that the wheel was rotated, in eighths of a degree
+
+ A positive value indicates that the wheel was rotated forwards away from the
+ user; a negative value indicates that the wheel was rotated backwards toward
+ the user. \c angleDelta().y() provides the angle through which the common
+ vertical mouse wheel was rotated since the previous event. \c angleDelta().x()
+ provides the angle through which the horizontal mouse wheel was rotated, if
+ the mouse has a horizontal wheel; otherwise it stays at zero.
+
+ Most mouse types work in steps of 15 degrees, in which case the delta value
+ is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees.
+
+ \note On platforms that support scrolling \l{phase()}{phases}, the delta
+ may be null when scrolling is about to begin (Qt::ScrollBegin) or has
+ ended (Qt::ScrollEnd).
+
+ \sa pixelDelta()
+*/
+
+/*!
\fn QPoint QWheelEvent::angleDelta() const
Returns the relative amount that the wheel was rotated, in eighths of a
@@ -1294,6 +1360,15 @@ bool QWheelEvent::isEndEvent() const
*/
/*!
+ \property QWheelEvent::phase
+ \since 5.2
+ \brief the scrolling phase of this wheel event
+
+ \note The Qt::ScrollBegin and Qt::ScrollEnd phases are currently
+ supported only on \macos.
+*/
+
+/*!
\fn Qt::ScrollPhase QWheelEvent::phase() const
\since 5.2
diff --git a/src/gui/kernel/qinputdevice.cpp b/src/gui/kernel/qinputdevice.cpp
index 8e5c38922e0..caed0fc6135 100644
--- a/src/gui/kernel/qinputdevice.cpp
+++ b/src/gui/kernel/qinputdevice.cpp
@@ -187,6 +187,11 @@ QString QInputDevice::name() const
}
/*!
+ \property QInputDevice::type
+ \brief the device type
+*/
+
+/*!
Returns the device type.
*/
QInputDevice::DeviceType QInputDevice::type() const
diff --git a/src/gui/kernel/qpointingdevice.cpp b/src/gui/kernel/qpointingdevice.cpp
index dcce354688a..7062340b287 100644
--- a/src/gui/kernel/qpointingdevice.cpp
+++ b/src/gui/kernel/qpointingdevice.cpp
@@ -241,6 +241,11 @@ void QPointingDevice::setMaximumTouchPoints(int c)
#endif // QT_DEPRECATED_SINCE(6, 0)
/*!
+ \property QPointingDevice::pointerType
+ \brief the pointer type
+*/
+
+/*!
Returns the pointer type.
*/
QPointingDevice::PointerType QPointingDevice::pointerType() const
@@ -250,6 +255,12 @@ QPointingDevice::PointerType QPointingDevice::pointerType() const
}
/*!
+ \property QPointingDevice::maximumPoints
+ \brief the maximum number of simultaneous touch points (fingers) that
+ can be detected
+*/
+
+/*!
Returns the maximum number of simultaneous touch points (fingers) that
can be detected.
*/
@@ -260,6 +271,11 @@ int QPointingDevice::maximumPoints() const
}
/*!
+ \property QPointingDevice::buttonCount
+ \brief the maximum number of on-device buttons that can be detected
+*/
+
+/*!
Returns the maximum number of on-device buttons that can be detected.
*/
int QPointingDevice::buttonCount() const
@@ -269,6 +285,13 @@ int QPointingDevice::buttonCount() const
}
/*!
+ \property QPointingDevice::uniqueId
+ \brief a unique ID (of dubious utility) for the device
+
+ You probably should rather be concerned with QPointerEventPoint::uniqueId().
+*/
+
+/*!
Returns a unique ID (of dubious utility) for the device.
You probably should rather be concerned with QPointerEventPoint::uniqueId().
diff --git a/src/gui/painting/qtextureglyphcache.cpp b/src/gui/painting/qtextureglyphcache.cpp
index d27539b2419..c9df5d28a45 100644
--- a/src/gui/painting/qtextureglyphcache.cpp
+++ b/src/gui/painting/qtextureglyphcache.cpp
@@ -301,6 +301,8 @@ void QImageTextureGlyphCache::fillTexture(const Coord &c,
const QFixedPoint &subPixelPosition)
{
QImage mask = textureMapForGlyph(g, subPixelPosition);
+ if (mask.isNull())
+ return;
#ifdef CACHE_DEBUG
printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height());
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 62b7ab6efb0..5590f2fb431 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -7186,6 +7186,26 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const
*/
/*!
+ \fn bool QRhiGraphicsPipeline::hasDepthClamp() const
+ \return true if depth clamp is enabled.
+
+ \since 6.11
+ */
+
+/*!
+ \fn void QRhiGraphicsPipeline::setDepthClamp(bool enable)
+
+ Enables depth clamping when \a enable is true. When depth clamping is
+ enabled, primitives that would otherwise be clipped by the near or far
+ clip plane are rasterized and their depth values are clamped to the
+ depth range. When disabled (the default), such primitives are clipped.
+
+ \note This setting is ignored on OpenGL ES.
+
+ \since 6.11
+ */
+
+/*!
\fn QRhiGraphicsPipeline::CompareOp QRhiGraphicsPipeline::depthOp() const
\return the depth comparison function.
*/
diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h
index b5a3f7b43be..5ee2a9acd13 100644
--- a/src/gui/rhi/qrhi.h
+++ b/src/gui/rhi/qrhi.h
@@ -1453,6 +1453,9 @@ public:
bool hasDepthWrite() const { return m_depthWrite; }
void setDepthWrite(bool enable) { m_depthWrite = enable; }
+ bool hasDepthClamp() const { return m_depthClamp; }
+ void setDepthClamp(bool enable) { m_depthClamp = enable; }
+
CompareOp depthOp() const { return m_depthOp; }
void setDepthOp(CompareOp op) { m_depthOp = op; }
@@ -1524,6 +1527,7 @@ protected:
QVarLengthArray<TargetBlend, 8> m_targetBlends;
bool m_depthTest = false;
bool m_depthWrite = false;
+ bool m_depthClamp = false;
CompareOp m_depthOp = Less;
bool m_stencilTest = false;
StencilOpState m_stencilFront;
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index a5f860e7724..1441be24043 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -4673,7 +4673,7 @@ bool QD3D11GraphicsPipeline::create()
rastDesc.FrontCounterClockwise = m_frontFace == CCW;
rastDesc.DepthBias = m_depthBias;
rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias;
- rastDesc.DepthClipEnable = true;
+ rastDesc.DepthClipEnable = m_depthClamp ? FALSE : TRUE;
rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor);
rastDesc.MultisampleEnable = rhiD->effectiveSampleDesc(m_sampleCount).Count > 1;
HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState);
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index 4f09b3c136b..b68b65b1063 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -6263,7 +6263,7 @@ bool QD3D12GraphicsPipeline::create()
stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW;
stream.rasterizerState.object.DepthBias = m_depthBias;
stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias;
- stream.rasterizerState.object.DepthClipEnable = TRUE;
+ stream.rasterizerState.object.DepthClipEnable = m_depthClamp ? FALSE : TRUE;
stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1;
stream.depthStencilState.object.DepthEnable = m_depthTest;
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index 15f5cd8f7e8..1308d4362e5 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -585,6 +585,10 @@ QT_BEGIN_NAMESPACE
#define GL_PROGRAM 0x82E2
#endif
+#ifndef GL_DEPTH_CLAMP
+#define GL_DEPTH_CLAMP 0x864F
+#endif
+
/*!
Constructs a new QRhiGles2InitParams.
@@ -998,6 +1002,13 @@ bool QRhiGles2::create(QRhi::Flags flags)
}
if (caps.gles)
+ caps.depthClamp = false;
+ else
+ caps.depthClamp = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // Desktop 3.2
+ if (!caps.depthClamp)
+ caps.depthClamp = ctx->hasExtension("GL_EXT_depth_clamp") || ctx->hasExtension("GL_ARB_depth_clamp");
+
+ if (caps.gles)
caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0
else
caps.textureCompareMode = true;
@@ -3959,6 +3970,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
break;
case QGles2CommandBuffer::Command::InvalidateFramebuffer:
if (caps.gles && caps.ctxMajor >= 3) {
+ f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.invalidateFramebuffer.fbo);
f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER,
cmd.args.invalidateFramebuffer.attCount,
cmd.args.invalidateFramebuffer.att);
@@ -4095,6 +4107,15 @@ void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2Grap
f->glDepthMask(depthWrite);
}
+ const bool depthClamp = psD->m_depthClamp;
+ if (caps.depthClamp && (forceUpdate || depthClamp != state.depthClamp)) {
+ state.depthClamp = depthClamp;
+ if (depthClamp)
+ f->glEnable(GL_DEPTH_CLAMP);
+ else
+ f->glDisable(GL_DEPTH_CLAMP);
+ }
+
const GLenum depthFunc = toGlCompareOp(psD->m_depthOp);
if (forceUpdate || depthFunc != state.depthFunc) {
state.depthFunc = depthFunc;
@@ -4881,6 +4902,7 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
if (mayDiscardDepthStencil) {
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer;
+ cmd.args.invalidateFramebuffer.fbo = rtTex->framebuffer;
if (caps.needsDepthStencilCombinedAttach) {
cmd.args.invalidateFramebuffer.attCount = 1;
cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT;
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
index 725e71a3665..70dd96a8dc7 100644
--- a/src/gui/rhi/qrhigles2_p.h
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -547,6 +547,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
GLbitfield barriers;
} barrier;
struct {
+ GLuint fbo;
int attCount;
GLenum att[3];
} invalidateFramebuffer;
@@ -592,6 +593,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
} blend[16];
bool depthTest;
bool depthWrite;
+ bool depthClamp;
GLenum depthFunc;
bool stencilTest;
GLuint stencilReadMask;
@@ -1074,6 +1076,7 @@ public:
uint baseVertex : 1;
uint compute : 1;
uint textureCompareMode : 1;
+ uint depthClamp : 1;
uint properMapBuffer : 1;
uint nonBaseLevelFramebufferTexture : 1;
uint texelFetch : 1;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index c3f1031b44b..7fa05f69232 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -403,6 +403,7 @@ struct QMetalGraphicsPipelineData
MTLWinding winding;
MTLCullMode cullMode;
MTLTriangleFillMode triangleFillMode;
+ MTLDepthClipMode depthClipMode;
float depthBias;
float slopeScaledDepthBias;
QMetalShader vs;
@@ -1477,7 +1478,6 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand
[cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds];
cbD->d->currentDepthStencilState = d->ds;
}
-
if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) {
[cbD->d->currentRenderPassEncoder setCullMode: d->cullMode];
cbD->currentCullMode = int(d->cullMode);
@@ -1486,6 +1486,10 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand
[cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode];
cbD->currentTriangleFillMode = int(d->triangleFillMode);
}
+ if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) {
+ [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode];
+ cbD->currentDepthClipMode = int(d->depthClipMode);
+ }
if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) {
[cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding];
cbD->currentFrontFaceWinding = int(d->winding);
@@ -5035,6 +5039,7 @@ void QMetalGraphicsPipeline::mapStates()
d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise;
d->cullMode = toMetalCullMode(m_cullMode);
d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode);
+ d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip;
d->depthBias = float(m_depthBias);
d->slopeScaledDepthBias = m_slopeScaledDepthBias;
}
@@ -6257,6 +6262,7 @@ void QMetalCommandBuffer::resetPerPassCachedState()
currentIndexFormat = QRhiCommandBuffer::IndexUInt16;
currentCullMode = -1;
currentTriangleFillMode = -1;
+ currentDepthClipMode = -1;
currentFrontFaceWinding = -1;
currentDepthBiasValues = { 0.0f, 0.0f };
diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h
index 7c19ae9e767..6649a6cd304 100644
--- a/src/gui/rhi/qrhimetal_p.h
+++ b/src/gui/rhi/qrhimetal_p.h
@@ -299,6 +299,7 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer
QRhiCommandBuffer::IndexFormat currentIndexFormat;
int currentCullMode;
int currentTriangleFillMode;
+ int currentDepthClipMode;
int currentFrontFaceWinding;
std::pair<float, float> currentDepthBiasValues;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index c5167a6e7de..481ffd57b5d 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -949,6 +949,8 @@ bool QRhiVulkan::create(QRhi::Flags flags)
// elsewhere states that the minimum bufferOffset is 4...
texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment);
+ caps.depthClamp = physDevFeatures.depthClamp;
+
caps.wideLines = physDevFeatures.wideLines;
caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1);
@@ -8402,6 +8404,8 @@ bool QVkGraphicsPipeline::create()
VkPipelineRasterizationStateCreateInfo rastInfo = {};
rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ if (m_depthClamp && rhiD->caps.depthClamp)
+ rastInfo.depthClampEnable = m_depthClamp;
rastInfo.cullMode = toVkCullMode(m_cullMode);
rastInfo.frontFace = toVkFrontFace(m_frontFace);
if (m_depthBias != 0 || !qFuzzyIsNull(m_slopeScaledDepthBias)) {
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index d141a84c5fb..1e9318513fd 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -936,6 +936,7 @@ public:
struct {
bool compute = false;
+ bool depthClamp = false;
bool wideLines = false;
bool debugUtils = false;
bool vertexAttribDivisor = false;
diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h
index d1509e4a251..75550439521 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -166,6 +166,7 @@ public:
QFontPrivate();
QFontPrivate(const QFontPrivate &other);
+ QFontPrivate &operator=(const QFontPrivate &) = delete;
~QFontPrivate();
QFontEngine *engineForScript(int script) const;
@@ -206,9 +207,6 @@ public:
void setVariableAxis(QFont::Tag tag, float value);
void unsetVariableAxis(QFont::Tag tag);
bool hasVariableAxis(QFont::Tag tag, float value) const;
-
-private:
- QFontPrivate &operator=(const QFontPrivate &) { return *this; }
};
diff --git a/src/gui/text/qfontvariableaxis.cpp b/src/gui/text/qfontvariableaxis.cpp
index be83a3e02ce..bbc14f4cd11 100644
--- a/src/gui/text/qfontvariableaxis.cpp
+++ b/src/gui/text/qfontvariableaxis.cpp
@@ -60,6 +60,18 @@ QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QFontVariableAxisPrivate)
QFontVariableAxis::QFontVariableAxis(const QFontVariableAxis &axis) = default;
/*!
+ \property QFontVariableAxis::tag
+ \brief the tag of the axis
+
+ This is a four-character sequence which identifies the axis. Certain tags
+ have standardized meanings, such as "wght" (weight) and "wdth" (width),
+ but any sequence of four latin-1 characters is a valid tag. By convention,
+ non-standard/custom axes are denoted by tags in all uppercase.
+
+ \sa QFont::setVariableAxis(), name()
+*/
+
+/*!
Returns the tag of the axis. This is a four-character sequence which identifies the axis.
Certain tags have standardized meanings, such as "wght" (weight) and "wdth" (width), but any
sequence of four latin-1 characters is a valid tag. By convention, non-standard/custom axes
@@ -91,6 +103,13 @@ void QFontVariableAxis::setTag(QFont::Tag tag)
}
/*!
+ \property QFontVariableAxis::name
+ \brief the name of the axis, if provided by the font
+
+ \sa tag()
+*/
+
+/*!
Returns the name of the axis, if provided by the font.
\sa tag()
@@ -153,6 +172,15 @@ void QFontVariableAxis::setMinimumValue(qreal minimumValue)
}
/*!
+ \property QFontVariableAxis::maximumValue
+ \brief the maximum value of the axis
+
+ Setting the axis to a value which is higher than this is not supported.
+
+ \sa minimumValue(), defaultValue()
+*/
+
+/*!
Returns the maximum value of the axis. Setting the axis to a value which is higher than this
is not supported.
@@ -182,6 +210,16 @@ void QFontVariableAxis::setMaximumValue(qreal maximumValue)
}
/*!
+ \property QFontVariableAxis::defaultValue
+ \brief the default value of the axis
+
+ This is the value the axis will have if none has been provided in the
+ QFont query.
+
+ \sa minimumValue(), maximumValue()
+*/
+
+/*!
Returns the default value of the axis. This is the value the axis will have if none has been
provided in the QFont query.
diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp
index 11c79f23d25..29fda652ef6 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.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
// Qt-Security score:critical reason:data-parser
+#include <QtCore/private/qflatmap_p.h>
#include <QtGui/private/qtguiglobal_p.h>
#include "qdebug.h"
#include "qtextformat.h"
@@ -1613,11 +1614,15 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
{
uint glyphs_shaped = 0;
- hb_buffer_t *buffer = hb_buffer_create();
- hb_buffer_set_unicode_funcs(buffer, hb_qt_get_unicode_funcs());
+ if (!buffer) {
+ buffer = hb_buffer_create();
+ hb_buffer_set_unicode_funcs(buffer, hb_qt_get_unicode_funcs());
+ }
+
hb_buffer_pre_allocate(buffer, itemLength);
if (Q_UNLIKELY(!hb_buffer_allocation_successful(buffer))) {
hb_buffer_destroy(buffer);
+ buffer = nullptr;
return 0;
}
@@ -1671,26 +1676,26 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
bool dontLigate = hasLetterSpacing && !scriptRequiresOpenType;
- QHash<QFont::Tag, quint32> features;
- features.insert(QFont::Tag("kern"), !!kerningEnabled);
+ QVarLengthFlatMap<QFont::Tag, hb_feature_t, 16> features;
+ auto insertFeature = [&features](QFont::Tag tag, quint32 value) {
+ features.insert(tag, { tag.value(),
+ value,
+ HB_FEATURE_GLOBAL_START,
+ HB_FEATURE_GLOBAL_END });
+ };
+ // fontFeatures have precedence
+ for (const auto &[tag, value]: fontFeatures.asKeyValueRange())
+ insertFeature(tag, value);
+ insertFeature(QFont::Tag("kern"), !!kerningEnabled);
if (dontLigate) {
- features.insert(QFont::Tag("liga"), false);
- features.insert(QFont::Tag("clig"), false);
- features.insert(QFont::Tag("dlig"), false);
- features.insert(QFont::Tag("hlig"), false);
- }
- features.insert(fontFeatures);
-
- QVarLengthArray<hb_feature_t, 16> featureArray;
- for (auto it = features.constBegin(); it != features.constEnd(); ++it) {
- featureArray.append({ it.key().value(),
- it.value(),
- HB_FEATURE_GLOBAL_START,
- HB_FEATURE_GLOBAL_END });
+ insertFeature(QFont::Tag("liga"), false);
+ insertFeature(QFont::Tag("clig"), false);
+ insertFeature(QFont::Tag("dlig"), false);
+ insertFeature(QFont::Tag("hlig"), false);
}
// whitelist cross-platforms shapers only
- static const char *shaper_list[] = {
+ constexpr const char *shaper_list[] = {
"graphite2",
"ot",
"fallback",
@@ -1699,13 +1704,11 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
bool shapedOk = hb_shape_full(hb_font,
buffer,
- featureArray.constData(),
- features.size(),
+ features.values().constData(),
+ features.values().size(),
shaper_list);
- if (Q_UNLIKELY(!shapedOk)) {
- hb_buffer_destroy(buffer);
+ if (Q_UNLIKELY(!shapedOk))
return 0;
- }
if (Q_UNLIKELY(HB_DIRECTION_IS_BACKWARD(props.direction)))
hb_buffer_reverse(buffer);
@@ -1718,10 +1721,8 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
num_glyphs = 1;
// ensure we have enough space for shaped glyphs and metrics
- if (Q_UNLIKELY(!ensureSpace(glyphs_shaped + num_glyphs))) {
- hb_buffer_destroy(buffer);
+ if (Q_UNLIKELY(!ensureSpace(glyphs_shaped + num_glyphs)))
return 0;
- }
// fetch the shaped glyphs and metrics
QGlyphLayout g = availableGlyphs(&si).mid(glyphs_shaped, num_glyphs);
@@ -1781,8 +1782,6 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
glyphs_shaped += num_glyphs;
}
- hb_buffer_destroy(buffer);
-
return glyphs_shaped;
}
@@ -1826,6 +1825,12 @@ QTextEngine::~QTextEngine()
delete layoutData;
delete specialData;
resetFontEngineCache();
+#if QT_CONFIG(harfbuzz)
+ if (buffer) {
+ hb_buffer_destroy(buffer);
+ buffer = nullptr;
+ }
+#endif
}
const QCharAttributes *QTextEngine::attributes() const
diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h
index dffbc129b3a..e513fd598ba 100644
--- a/src/gui/text/qtextengine_p.h
+++ b/src/gui/text/qtextengine_p.h
@@ -40,6 +40,8 @@
#include <stdlib.h>
#include <vector>
+struct hb_buffer_t;
+
QT_BEGIN_NAMESPACE
class QFontPrivate;
@@ -583,6 +585,8 @@ private:
void indexFormats();
void resolveFormats() const;
+ mutable hb_buffer_t *buffer = nullptr;
+
public:
bool atWordSeparator(int position) const;
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index f680950701c..f76d79571c3 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -272,6 +272,11 @@ void QNetworkReplyHttpImpl::close()
void QNetworkReplyHttpImpl::abort()
{
+ abortImpl(QNetworkReply::OperationCanceledError);
+}
+
+void QNetworkReplyHttpImpl::abortImpl(QNetworkReply::NetworkError error)
+{
Q_D(QNetworkReplyHttpImpl);
// FIXME
if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted)
@@ -282,7 +287,8 @@ void QNetworkReplyHttpImpl::abort()
if (d->state != QNetworkReplyPrivate::Finished) {
// call finished which will emit signals
// FIXME shouldn't this be emitted Queued?
- d->error(OperationCanceledError, tr("Operation canceled"));
+ d->error(error,
+ error == TimeoutError ? tr("Operation timed out") : tr("Operation canceled"));
d->finished();
}
@@ -2120,7 +2126,7 @@ void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData()
void QNetworkReplyHttpImplPrivate::_q_transferTimedOut()
{
Q_Q(QNetworkReplyHttpImpl);
- q->abort();
+ q->abortImpl(QNetworkReply::TimeoutError);
}
void QNetworkReplyHttpImplPrivate::setupTransferTimeout()
@@ -2242,8 +2248,10 @@ void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, c
// Can't set and emit multiple errors.
if (errorCode != QNetworkReply::NoError) {
// But somewhat unavoidable if we have cancelled the request:
- if (errorCode != QNetworkReply::OperationCanceledError)
+ if (errorCode != QNetworkReply::OperationCanceledError
+ && errorCode != QNetworkReply::TimeoutError) {
qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
+ }
return;
}
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
index 0d16d02ff53..a354b388ad6 100644
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -59,6 +59,7 @@ public:
void close() override;
void abort() override;
+ void abortImpl(QNetworkReply::NetworkError error);
qint64 bytesAvailable() const override;
bool isSequential () const override;
qint64 size() const override;
diff --git a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
index 62d24eefd33..e5ada714ba5 100644
--- a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
+++ b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
@@ -110,7 +110,8 @@ QGlibNetworkInformationBackend::QGlibNetworkInformationBackend()
connectivityHandlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity",
G_CALLBACK(updateConnectivity), this);
- networkHandlerId = g_signal_connect_swapped(networkMonitor, "network-changed",
+ // needed until GLib 2.86 for netlink support
+ networkHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-available",
G_CALLBACK(updateConnectivity), this);
meteredHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-metered",
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
index 08c9f5d5ba2..2989b4d6df3 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
@@ -161,6 +161,7 @@ static void populateRoleMap()
roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
roleMap[QAccessible::BlockQuote] = NSAccessibilityGroupRole;
+ roleMap[QAccessible::LayeredPane] = NSAccessibilityGroupRole;
}
/*
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index e0ef6cec794..4c4e5fac962 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -491,7 +491,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
return;
if (m_panel.visible) {
- const QString selection = QString::fromNSString(m_panel.URL.path);
+ const QString selection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
if (selection != m_currentSelection) {
m_currentSelection = selection;
emit m_helper->currentChanged(QUrl::fromLocalFile(selection));
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
index dab348beaa4..7a6f010ba8f 100644
--- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
@@ -88,6 +88,11 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w
return false;
}
+ // Tahoe has issues with window-modal alert buttons not responding to mouse
+ if (windowModality == Qt::WindowModal
+ && QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe)
+ return false;
+
// And without options we don't know what to show
if (!options())
return false;
diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h
index b7ff3bb2a58..7af9cf80355 100644
--- a/src/plugins/platforms/ios/qiostheme.h
+++ b/src/plugins/platforms/ios/qiostheme.h
@@ -26,6 +26,7 @@ public:
Qt::ColorScheme colorScheme() const override;
void requestColorScheme(Qt::ColorScheme scheme) override;
+ Qt::ContrastPreference contrastPreference() const override;
#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QPlatformMenuItem* createPlatformMenuItem() const override;
diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm
index cdf669f921c..435c21bf579 100644
--- a/src/plugins/platforms/ios/qiostheme.mm
+++ b/src/plugins/platforms/ios/qiostheme.mm
@@ -202,6 +202,12 @@ void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme)
#endif
}
+Qt::ContrastPreference QIOSTheme::contrastPreference() const
+{
+ return UIAccessibilityDarkerSystemColorsEnabled() ? Qt::ContrastPreference::HighContrast : Qt::ContrastPreference::NoPreference;
+}
+
+
void QIOSTheme::applyTheme(UIWindow *window)
{
const UIUserInterfaceStyle style = []{
diff --git a/src/plugins/platforms/ios/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm
index bb4268e1c88..62fd9c5d770 100644
--- a/src/plugins/platforms/ios/quiwindow.mm
+++ b/src/plugins/platforms/ios/quiwindow.mm
@@ -54,7 +54,9 @@
if (self.screen == UIScreen.mainScreen) {
// Check if the current userInterfaceStyle reports a different appearance than
// the platformTheme's appearance. We might have set that one based on the UIScreen
+ // Check for changes in the "Increase contrast" setting too.
if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle
+ || previousTraitCollection.accessibilityContrast != self.traitCollection.accessibilityContrast
|| QGuiApplicationPrivate::platformTheme()->colorScheme() != colorScheme) {
QIOSTheme::initializeSystemPalette();
QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
index a87c33c8346..5807d157636 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
@@ -586,14 +586,7 @@ void QWasmAccessibility::linkToParent(QAccessibleInterface *iface)
void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible)
{
emscripten::val element = getHtmlElement(iface);
-
- if (visible) {
- setAttribute(element, "aria-hidden", false);
- setAttribute(element, "tabindex", "");
- } else {
- setAttribute(element, "aria-hidden", true); // aria-hidden mean completely hidden; maybe some sort of soft-hidden should be used.
- setAttribute(element, "tabindex", "-1");
- }
+ setAttribute(element, "aria-hidden", !visible);
}
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
index 18a457198f1..a0546fdc215 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -40,8 +40,23 @@ void QWasmInputContext::inputCallback(emscripten::val event)
// Some of them should be implemented here later.
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString;
if (!inputTypeString.compare("deleteContentBackward")) {
- QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
- QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier);
+
+ QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
+ QCoreApplication::sendEvent(m_focusObject, &queryEvent);
+ int cursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt();
+
+ int deleteLength = rangesPair.second - rangesPair.first;
+ int deleteFrom = -1;
+ if (cursorPosition > rangesPair.first) {
+ deleteFrom = -(cursorPosition - rangesPair.first);
+ }
+ QInputMethodEvent e;
+ e.setCommitString(QString(), deleteFrom, deleteLength);
+ QCoreApplication::sendEvent(m_focusObject, &e);
+
+ rangesPair.first = 0;
+ rangesPair.second = 0;
+
event.call<void>("stopImmediatePropagation");
return;
} else if (!inputTypeString.compare("deleteContentForward")) {
@@ -138,41 +153,23 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event)
const auto compositionStr = QString::fromEcmaString(event["data"]);
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr;
- // WA for IOS.
- // Not sure now because I cannot test it anymore.
-// int replaceSize = 0;
-// emscripten::val win = emscripten::val::global("window");
-// emscripten::val sel = win.call<emscripten::val>("getSelection");
-// if (!sel.isNull() && !sel.isUndefined()
-// && sel["rangeCount"].as<int>() > 0) {
-// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll);
-// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent);
-// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString();
-// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString();
-// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString();
-// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString();
-//
-// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString"));
-// const auto &preeditStr = preeditString();
-// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>();
-// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr;
-// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr;
-// if (!sel["type"].as<std::string>().compare("Range")) {
-// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString();
-// if (surroundingTextBeforeCursor.endsWith(selectedStr)) {
-// replaceSize = selectedStr.size();
-// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize;
-// }
-// }
-// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0);
-// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>();
-// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>();
-// }
-//
-// setPreeditString(compositionStr, replaceSize);
setPreeditString(compositionStr, 0);
}
+void QWasmInputContext::beforeInputCallback(emscripten::val event)
+{
+ emscripten::val ranges = event.call<emscripten::val>("getTargetRanges");
+
+ auto length = ranges["length"].as<int>();
+ for (auto i = 0; i < length; i++) {
+ emscripten::val range = ranges[i];
+ qCDebug(qLcQpaWasmInputContext) << "startOffset" << range["startOffset"].as<int>();
+ qCDebug(qLcQpaWasmInputContext) << "endOffset" << range["endOffset"].as<int>();
+ rangesPair.first = range["startOffset"].as<int>();
+ rangesPair.second = range["endOffset"].as<int>();
+ }
+}
+
QWasmInputContext::QWasmInputContext()
{
qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO;
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
index 6d24c7fea0d..97415451b2a 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.h
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -45,6 +45,7 @@ public:
void compositionEndCallback(emscripten::val event);
void compositionStartCallback(emscripten::val event);
void compositionUpdateCallback(emscripten::val event);
+ void beforeInputCallback(emscripten::val event);
void updateGeometry();
@@ -62,6 +63,7 @@ private:
bool m_inputMethodAccepted = false;
QObject *m_focusObject = nullptr;
emscripten::val m_inputElement = emscripten::val::null();
+ QPair<int, int> rangesPair;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index 04f52ac9b1b..e49c1dc49f1 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -110,6 +110,7 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
// Set up m_inputElement, which takes focus whenever a Qt text input UI element has
// foucus.
m_inputElement["classList"].call<void>("add", emscripten::val("qt-window-input-element"));
+ m_inputElement.call<void>("setAttribute", std::string("contenteditable"), std::string("true"));
m_inputElement.set("type", "text");
m_inputElement["style"].set("position", "absolute");
m_inputElement["style"].set("left", 0);
@@ -227,6 +228,8 @@ void QWasmWindow::registerEventHandlers()
[this](emscripten::val event){ handleCompositionStartEvent(event); });
m_compositionEndCallback = QWasmEventHandler(m_window, "compositionend",
[this](emscripten::val event){ handleCompositionEndEvent(event); });
+ m_beforeInputCallback = QWasmEventHandler(m_window, "beforeinput",
+ [this](emscripten::val event){ handleBeforeInputEvent(event); });
}
QWasmWindow::~QWasmWindow()
@@ -789,6 +792,16 @@ void QWasmWindow::handleCompositionEndEvent(emscripten::val event)
m_focusHelper.set("innerHTML", std::string());
}
+void QWasmWindow::handleBeforeInputEvent(emscripten::val event)
+{
+ qWarning() << Q_FUNC_INFO;
+
+ if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive())
+ inputContext->beforeInputCallback(event);
+ // else
+ // m_focusHelper.set("innerHTML", std::string());
+}
+
void QWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event)
{
if (processPointerEnterLeave(event))
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index cbe930dce89..8e6e5021dcf 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -145,6 +145,7 @@ private:
void handleCompositionStartEvent(emscripten::val event);
void handleCompositionUpdateEvent(emscripten::val event);
void handleCompositionEndEvent(emscripten::val event);
+ void handleBeforeInputEvent(emscripten::val event);
void handlePointerEnterLeaveEvent(const PointerEvent &event);
bool processPointerEnterLeave(const PointerEvent &event);
@@ -183,6 +184,7 @@ private:
QWasmEventHandler m_compositionStartCallback;
QWasmEventHandler m_compositionUpdateCallback;
QWasmEventHandler m_compositionEndCallback;
+ QWasmEventHandler m_beforeInputCallback;
QWasmEventHandler m_pointerDownCallback;
QWasmEventHandler m_pointerMoveCallback;
diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt
index 7e3589def6b..0ce9a4b091c 100644
--- a/src/plugins/platforms/wayland/CMakeLists.txt
+++ b/src/plugins/platforms/wayland/CMakeLists.txt
@@ -17,14 +17,6 @@ qt_internal_add_module(WaylandGlobalPrivate
NO_GENERATE_CPP_EXPORTS
)
-# Work around 115101.
-# If nothing depends on the WaylandGlobalPrivate target it doesn't run custom commands that the
-# target depends on. WaylandGlobalPrivate_ensure_sync_headers makes sure that 'all' depends on
-# WaylandGlobalPrivate_sync_headers.
-# TODO: This needs to be removed once the fix for QTBUG-115101 is merged in qtbase.
-add_custom_target(WaylandGlobalPrivate_ensure_sync_headers ALL)
-add_dependencies(WaylandGlobalPrivate_ensure_sync_headers WaylandGlobalPrivate_sync_headers)
-
# special case begin
# TODO: Ideally these macros would be part of the qtwaylandscanner tool, and not the compositor/client
include(../../../../src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake)
diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
index 0ccc4dba57a..5ab285ad97d 100644
--- a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
+++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp
@@ -192,10 +192,12 @@ void QWaylandInputContext::setFocusObject(QObject *object)
if (window && window->handle()) {
if (mCurrentWindow.data() != window) {
if (!inputMethodAccepted()) {
- auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
- if (surface)
- inputInterface->disableSurface(surface);
- mCurrentWindow.clear();
+ if (mCurrentWindow) {
+ auto *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->wlSurface();
+ if (surface)
+ inputInterface->disableSurface(surface);
+ mCurrentWindow.clear();
+ }
} else {
auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface();
if (surface) {
diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp
index 71103183183..edc1ea3a9dc 100644
--- a/src/plugins/platforms/windows/qwindowsiconengine.cpp
+++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp
@@ -63,8 +63,8 @@ static QString getGlyphs(QStringView iconName)
{"go-home"_L1, u"\ue80f"},
// {"go-jump"_L1, u"\uf719"},
//{"go-last"_L1, u"\ue5dd"},
- {"go-next"_L1, u"\ue893"},
- {"go-previous"_L1, u"\ue892"},
+ {"go-next"_L1, u"\ue72a"},
+ {"go-previous"_L1, u"\ue72b"},
//{"go-top"_L1, u"\ue25a"},
{"go-up"_L1, u"\ue74a"},
{"help-about"_L1, u"\ue946"},
@@ -100,7 +100,7 @@ static QString getGlyphs(QStringView iconName)
//{"object-flip-vertical"_L1, u"\u"},
{"object-rotate-left"_L1, u"\ue80c"},
{"object-rotate-right"_L1, u"\ue80d"},
- //{"process-stop"_L1, u"\ue5c9"},
+ {"process-stop"_L1, u"\uf140"},
{"system-lock-screen"_L1, u"\uee3f"},
{"system-log-out"_L1, u"\uf3b1"},
//{"system-run"_L1, u"\u"},
diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
index 482810d5b7e..7908f22d99d 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.cpp
+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
@@ -874,9 +874,8 @@ const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const
return nullptr;
}
-const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const
+const QWindowsScreen *QWindowsScreenManager::screenForMonitor(HMONITOR hMonitor) const
{
- HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
if (hMonitor == nullptr)
return nullptr;
const auto it =
@@ -889,4 +888,18 @@ const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const
return it != m_screens.cend() ? *it : nullptr;
}
+const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const
+{
+ HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
+ return screenForMonitor(hMonitor);
+}
+
+const QWindowsScreen *QWindowsScreenManager::screenForRect(const RECT *rect) const
+{
+ if (rect == nullptr)
+ return nullptr;
+ HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONULL);
+ return screenForMonitor(hMonitor);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
index 8d555998388..0032fd462cb 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.h
+++ b/src/plugins/platforms/windows/qwindowsscreen.h
@@ -115,12 +115,14 @@ public:
const QWindowsScreen *screenAtDp(const QPoint &p) const;
const QWindowsScreen *screenForHwnd(HWND hwnd) const;
+ const QWindowsScreen *screenForRect(const RECT *rect) const;
static bool isSingleScreen();
private:
void addScreen(const QWindowsScreenData &screenData);
void removeScreen(int index);
+ const QWindowsScreen *screenForMonitor(HMONITOR monitor) const;
HWND m_displayChangeObserver = nullptr;
WindowsScreenList m_screens;
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index 01716fba60c..72daffb56b1 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -466,15 +466,17 @@ static bool applyBlurBehindWindow(HWND hwnd)
return result;
}
+static bool shouldShowTitlebarButton(Qt::WindowFlags flags, Qt::WindowFlags button)
+{
+ return !flags.testFlag(Qt::CustomizeWindowHint) || flags.testFlags(Qt::CustomizeWindowHint | button);
+}
+
// from qwidget_win.cpp, pass flags separately in case they have been "autofixed".
static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags)
{
- if ((flags & Qt::MSWindowsFixedSizeDialogHint) || !(flags & Qt::WindowMaximizeButtonHint))
- return false;
- // if the user explicitly asked for the maximize button, we try to add
- // it even if the window has fixed size.
- return (flags & Qt::CustomizeWindowHint) ||
- w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX);
+ return !flags.testFlag(Qt::MSWindowsFixedSizeDialogHint) &&
+ (shouldShowTitlebarButton(flags, Qt::WindowMaximizeButtonHint) ||
+ w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX));
}
bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags)
@@ -805,6 +807,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
if (topLevel) {
if ((type == Qt::Window || dialog || tool)) {
+ const bool defaultTitlebar = !flags.testFlag(Qt::CustomizeWindowHint);
if (!(flags & Qt::FramelessWindowHint)) {
style |= WS_POPUP;
if (flags & Qt::MSWindowsFixedSizeDialogHint) {
@@ -812,16 +815,16 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
} else {
style |= WS_THICKFRAME;
}
- if (flags & Qt::WindowTitleHint)
+ if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint))
style |= WS_CAPTION; // Contains WS_DLGFRAME
}
- if (flags & Qt::WindowSystemMenuHint)
+ if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowSystemMenuHint))
style |= WS_SYSMENU;
- else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) {
+ else if (dialog && (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint)) && !(flags & Qt::FramelessWindowHint)) {
style |= WS_SYSMENU | WS_BORDER; // QTBUG-2027, dialogs without system menu.
exStyle |= WS_EX_DLGMODALFRAME;
}
- const bool showMinimizeButton = flags & Qt::WindowMinimizeButtonHint;
+ const bool showMinimizeButton = shouldShowTitlebarButton(flags, Qt::WindowMinimizeButtonHint);
if (showMinimizeButton)
style |= WS_MINIMIZEBOX;
const bool showMaximizeButton = shouldShowMaximizeButton(w, flags);
@@ -2113,7 +2116,8 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
QWindowsThemeCache::clearThemeCache(hwnd);
// Send screen change first, so that the new screen is set during any following resize
- checkForScreenChanged(QWindowsWindow::FromDpiChange);
+ const auto prcNewWindow = reinterpret_cast<const RECT *>(lParam);
+ checkForScreenChanged(QWindowsWindow::FromDpiChange, !m_inSetgeometry ? prcNewWindow : nullptr);
if (!IsZoomed(hwnd))
m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale);
@@ -2136,7 +2140,6 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
// making the SetWindowPos() call.
if (!m_inSetgeometry) {
updateFullFrameMargins();
- const auto prcNewWindow = reinterpret_cast<RECT *>(lParam);
SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
@@ -2351,14 +2354,15 @@ static inline bool equalDpi(const QDpi &d1, const QDpi &d2)
return qFuzzyCompare(d1.first, d2.first) && qFuzzyCompare(d1.second, d2.second);
}
-void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode)
+void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode, const RECT *suggestedRect)
{
if ((parent() && !parent()->isForeignWindow()) || QWindowsScreenManager::isSingleScreen())
return;
QPlatformScreen *currentScreen = screen();
auto topLevel = isTopLevel_sys() ? m_data.hwnd : GetAncestor(m_data.hwnd, GA_ROOT);
- const QWindowsScreen *newScreen =
+ const QWindowsScreen *newScreen = suggestedRect ?
+ QWindowsContext::instance()->screenManager().screenForRect(suggestedRect) :
QWindowsContext::instance()->screenManager().screenForHwnd(topLevel);
if (newScreen == nullptr || newScreen == currentScreen)
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
index 499b2a9ca86..ee348f1ac16 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
@@ -342,7 +342,7 @@ public:
void stopAlertWindow();
enum ScreenChangeMode { FromGeometryChange, FromDpiChange, FromScreenAdded };
- void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange);
+ void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange, const RECT *suggestedRect = nullptr);
void registerTouchWindow();
static void setHasBorderInFullScreenStatic(QWindow *window, bool border);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
index b2675d5b884..835499d3554 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
@@ -154,6 +154,7 @@ long roleToControlTypeId(QAccessible::Role role)
{QAccessible::WebDocument, UIA_DocumentControlTypeId},
{QAccessible::Heading, UIA_TextControlTypeId},
{QAccessible::BlockQuote, UIA_GroupControlTypeId},
+ {QAccessible::LayeredPane, UIA_PaneControlTypeId},
};
long controlType = mapping.value(role, UIA_CustomControlTypeId);
diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt
index 492d274c2fd..204acd72ba0 100644
--- a/src/plugins/platforms/xcb/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/CMakeLists.txt
@@ -120,30 +120,6 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION CLANG
-ftemplate-depth=1024
)
-qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting
- SOURCES
- nativepainting/qbackingstore_x11.cpp nativepainting/qbackingstore_x11_p.h
- nativepainting/qcolormap_x11.cpp nativepainting/qcolormap_x11_p.h
- nativepainting/qpaintengine_x11.cpp nativepainting/qpaintengine_x11_p.h
- nativepainting/qpixmap_x11.cpp nativepainting/qpixmap_x11_p.h
- nativepainting/qpolygonclipper_p.h
- nativepainting/qt_x11_p.h
- nativepainting/qtessellator.cpp nativepainting/qtessellator_p.h
- nativepainting/qxcbnativepainting.cpp nativepainting/qxcbnativepainting.h
- INCLUDE_DIRECTORIES
- nativepainting
-)
-
-qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting AND QT_FEATURE_xrender
- PUBLIC_LIBRARIES
- PkgConfig::XRender
-)
-
-qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_FEATURE_xcb_native_painting
- LIBRARIES
- WrapFreetype::WrapFreetype
-)
-
if(QT_FEATURE_system_xcb_xinput)
qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT)
else()
diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
deleted file mode 100644
index 1ac5f6a64bc..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright (C) 2018 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
-
-#include "qbackingstore_x11_p.h"
-#include "qxcbwindow.h"
-#include "qpixmap_x11_p.h"
-
-#include <private/qhighdpiscaling_p.h>
-#include <QPainter>
-
-#if QT_CONFIG(xrender)
-# include <X11/extensions/Xrender.h>
-#endif
-
-#define register /* C++17 deprecated register */
-#include <X11/Xlib.h>
-#undef register
-
-QT_BEGIN_NAMESPACE
-
-QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window)
- : QPlatformBackingStore(window)
- , m_translucentBackground(false)
-{
- if (QXcbWindow *w = static_cast<QXcbWindow *>(window->handle())) {
- m_translucentBackground = w->connection()->hasXRender() &&
- QImage::toPixelFormat(w->imageFormat()).alphaUsage() == QPixelFormat::UsesAlpha;
- }
-}
-
-QXcbNativeBackingStore::~QXcbNativeBackingStore()
-{}
-
-QPaintDevice *QXcbNativeBackingStore::paintDevice()
-{
- return &m_pixmap;
-}
-
-void QXcbNativeBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
-{
- if (m_pixmap.isNull())
- return;
-
- QSize pixmapSize = m_pixmap.size();
-
- QRegion clipped = region;
- clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window));
- clipped &= QRect(0, 0, pixmapSize.width(), pixmapSize.height()).translated(-offset);
-
- QRect br = clipped.boundingRect();
- if (br.isNull())
- return;
-
- QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
- if (!platformWindow) {
- qWarning("QXcbBackingStore::flush: QWindow has no platform window (QTBUG-32681)");
- return;
- }
-
- Window wid = platformWindow->xcb_window();
- Pixmap pid = qt_x11PixmapHandle(m_pixmap);
-
- QList<XRectangle> clipRects = qt_region_to_xrectangles(clipped);
-
-#if QT_CONFIG(xrender)
- if (m_translucentBackground)
- {
- XWindowAttributes attrib;
- XGetWindowAttributes(display(), wid, &attrib);
- XRenderPictFormat *format = XRenderFindVisualFormat(display(), attrib.visual);
-
- Picture srcPic = qt_x11PictureHandle(m_pixmap);
- Picture dstPic = XRenderCreatePicture(display(), wid, format, 0, 0);
-
- XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size());
-
- XRenderComposite(display(), PictOpSrc, srcPic, 0L /*None*/, dstPic, br.x() + offset.x(),
- br.y() + offset.y(), 0, 0, br.x(), br.y(), br.width(), br.height());
-
- XRenderFreePicture(display(), dstPic);
- }
- else
-#endif
- {
- GC gc = XCreateGC(display(), wid, 0, nullptr);
-
- if (clipRects.size() != 1)
- XSetClipRectangles(display(), gc, 0, 0, clipRects.data(), clipRects.size(), YXBanded);
-
- XCopyArea(display(), pid, wid, gc, br.x() + offset.x(), br.y() + offset.y(), br.width(), br.height(), br.x(), br.y());
- XFreeGC(display(), gc);
- }
-
-
- if (platformWindow->needsSync()) {
- platformWindow->updateSyncRequestCounter();
- } else {
- XFlush(display());
- }
-}
-
-QImage QXcbNativeBackingStore::toImage() const
-{
- return m_pixmap.toImage();
-}
-
-void QXcbNativeBackingStore::resize(const QSize &size, const QRegion &staticContents)
-{
- if (size == m_pixmap.size())
- return;
-
- QPixmap newPixmap(size);
-
-#if QT_CONFIG(xrender)
- if (m_translucentBackground && newPixmap.depth() != 32)
- qt_x11Pixmap(newPixmap)->convertToARGB32();
-#endif
-
- if (!m_pixmap.isNull()) {
- Pixmap from = qt_x11PixmapHandle(m_pixmap);
- Pixmap to = qt_x11PixmapHandle(newPixmap);
- QRect br = staticContents.boundingRect().intersected(QRect(QPoint(0, 0), size));
-
- if (!br.isEmpty()) {
- GC gc = XCreateGC(display(), to, 0, nullptr);
- XCopyArea(display(), from, to, gc, br.x(), br.y(), br.width(), br.height(), br.x(), br.y());
- XFreeGC(display(), gc);
- }
- }
-
- m_pixmap = newPixmap;
-}
-
-bool QXcbNativeBackingStore::scroll(const QRegion &area, int dx, int dy)
-{
- if (m_pixmap.isNull())
- return false;
-
- QRect rect = area.boundingRect();
- Pixmap pix = qt_x11PixmapHandle(m_pixmap);
-
- GC gc = XCreateGC(display(), pix, 0, nullptr);
- XCopyArea(display(), pix, pix, gc,
- rect.x(), rect.y(), rect.width(), rect.height(),
- rect.x()+dx, rect.y()+dy);
- XFreeGC(display(), gc);
- return true;
-}
-
-void QXcbNativeBackingStore::beginPaint(const QRegion &region)
-{
- QX11PlatformPixmap *x11pm = qt_x11Pixmap(m_pixmap);
- if (x11pm)
- x11pm->setIsBackingStore(true);
-
-#if QT_CONFIG(xrender)
- if (m_translucentBackground) {
- const QList<XRectangle> xrects = qt_region_to_xrectangles(region);
- const XRenderColor color = { 0, 0, 0, 0 };
- XRenderFillRectangles(display(), PictOpSrc,
- qt_x11PictureHandle(m_pixmap), &color,
- xrects.constData(), xrects.size());
- }
-#else
- Q_UNUSED(region);
-#endif
-}
-
-Display *QXcbNativeBackingStore::display() const
-{
- return static_cast<Display *>(static_cast<QXcbWindow *>(window()->handle())->connection()->xlib_display());
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h
deleted file mode 100644
index b730b076d3f..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <qpa/qplatformbackingstore.h>
-
-typedef struct _XDisplay Display;
-
-QT_BEGIN_NAMESPACE
-
-class QXcbWindow;
-
-class QXcbNativeBackingStore : public QPlatformBackingStore
-{
-public:
- QXcbNativeBackingStore(QWindow *window);
- ~QXcbNativeBackingStore();
-
- QPaintDevice *paintDevice() override;
- void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
-
- QImage toImage() const override;
-
- void resize(const QSize &size, const QRegion &staticContents) override;
- bool scroll(const QRegion &area, int dx, int dy) override;
-
- void beginPaint(const QRegion &region) override;
-
-private:
- Display *display() const;
-
- QPixmap m_pixmap;
- bool m_translucentBackground;
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp
deleted file mode 100644
index fddd3e03989..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp
+++ /dev/null
@@ -1,615 +0,0 @@
-// Copyright (C) 2018 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
-
-#include <QVarLengthArray>
-
-#include <private/qguiapplication_p.h>
-
-#include "qcolormap_x11_p.h"
-#include "qxcbnativepainting.h"
-#include "qt_x11_p.h"
-
-QT_BEGIN_NAMESPACE
-
-class QXcbColormapPrivate
-{
-public:
- QXcbColormapPrivate()
- : ref(1), mode(QXcbColormap::Direct), depth(0),
- colormap(0), defaultColormap(true),
- visual(0), defaultVisual(true),
- r_max(0), g_max(0), b_max(0),
- r_shift(0), g_shift(0), b_shift(0)
- {}
-
- QAtomicInt ref;
-
- QXcbColormap::Mode mode;
- int depth;
-
- Colormap colormap;
- bool defaultColormap;
-
- Visual *visual;
- bool defaultVisual;
-
- int r_max;
- int g_max;
- int b_max;
-
- uint r_shift;
- uint g_shift;
- uint b_shift;
-
- QList<QColor> colors;
- QList<int> pixels;
-};
-
-static uint right_align(uint v)
-{
- while (!(v & 0x1))
- v >>= 1;
- return v;
-}
-
-static int cube_root(int v)
-{
- if (v == 1)
- return 1;
- // brute force algorithm
- int i = 1;
- for (;;) {
- const int b = i * i * i;
- if (b <= v) {
- ++i;
- } else {
- --i;
- break;
- }
- }
- return i;
-}
-
-static Visual *find_visual(Display *display,
- int screen,
- int visual_class,
- int visual_id,
- int *depth,
- bool *defaultVisual)
-{
- XVisualInfo *vi, rvi;
- int count;
-
- uint mask = VisualScreenMask;
- rvi.screen = screen;
-
- if (visual_class != -1) {
- rvi.c_class = visual_class;
- mask |= VisualClassMask;
- }
- if (visual_id != -1) {
- rvi.visualid = visual_id;
- mask |= VisualIDMask;
- }
-
- Visual *visual = DefaultVisual(display, screen);
- *defaultVisual = true;
- *depth = DefaultDepth(display, screen);
-
- vi = XGetVisualInfo(display, mask, &rvi, &count);
- if (vi) {
- int best = 0;
- for (int x = 0; x < count; ++x) {
- if (vi[x].depth > vi[best].depth)
- best = x;
- }
- if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) {
- visual = vi[best].visual;
- *defaultVisual = (visual == DefaultVisual(display, screen));
- *depth = vi[best].depth;
- }
- }
- if (vi)
- XFree((char *)vi);
- return visual;
-}
-
-static void query_colormap(QXcbColormapPrivate *d, int screen)
-{
- Display *display = X11->display;
-
- // query existing colormap
- int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth));
- XColor queried[256];
- memset(queried, 0, sizeof(queried));
- for (int x = 0; x < q_colors; ++x)
- queried[x].pixel = x;
- XQueryColors(display, d->colormap, queried, q_colors);
-
- d->colors.resize(q_colors);
- for (int x = 0; x < q_colors; ++x) {
- if (queried[x].red == 0
- && queried[x].green == 0
- && queried[x].blue == 0
- && queried[x].pixel != BlackPixel(display, screen)) {
- // unallocated color cell, skip it
- continue;
- }
-
- d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX),
- queried[x].green / float(USHRT_MAX),
- queried[x].blue / float(USHRT_MAX));
- }
-
- // for missing colors, find the closest color in the existing colormap
- Q_ASSERT(d->pixels.size());
- for (int x = 0; x < d->pixels.size(); ++x) {
- if (d->pixels.at(x) != -1)
- continue;
-
- QRgb rgb;
- if (d->mode == QXcbColormap::Indexed) {
- const int r = (x / (d->g_max * d->b_max)) % d->r_max;
- const int g = (x / d->b_max) % d->g_max;
- const int b = x % d->b_max;
- rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1),
- (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1),
- (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1));
- } else {
- rgb = qRgb(x, x, x);
- }
-
- // find closest color
- int mindist = INT_MAX, best = -1;
- for (int y = 0; y < q_colors; ++y) {
- int r = qRed(rgb) - (queried[y].red >> 8);
- int g = qGreen(rgb) - (queried[y].green >> 8);
- int b = qBlue(rgb) - (queried[y].blue >> 8);
- int dist = (r * r) + (g * g) + (b * b);
- if (dist < mindist) {
- mindist = dist;
- best = y;
- }
- }
-
- Q_ASSERT(best >= 0 && best < q_colors);
- if (d->visual->c_class & 1) {
- XColor xcolor;
- xcolor.red = queried[best].red;
- xcolor.green = queried[best].green;
- xcolor.blue = queried[best].blue;
- xcolor.pixel = queried[best].pixel;
-
- if (XAllocColor(display, d->colormap, &xcolor)) {
- d->pixels[x] = xcolor.pixel;
- } else {
- // some weird stuff is going on...
- d->pixels[x] = (qGray(rgb) < 127
- ? BlackPixel(display, screen)
- : WhitePixel(display, screen));
- }
- } else {
- d->pixels[x] = best;
- }
- }
-}
-
-static void init_gray(QXcbColormapPrivate *d, int screen)
-{
- d->pixels.resize(d->r_max);
-
- for (int g = 0; g < d->g_max; ++g) {
- const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1);
- const QRgb rgb = qRgb(gray, gray, gray);
-
- d->pixels[g] = -1;
-
- if (d->visual->c_class & 1) {
- XColor xcolor;
- xcolor.red = qRed(rgb) * 0x101;
- xcolor.green = qGreen(rgb) * 0x101;
- xcolor.blue = qBlue(rgb) * 0x101;
- xcolor.pixel = 0ul;
-
- if (XAllocColor(X11->display, d->colormap, &xcolor))
- d->pixels[g] = xcolor.pixel;
- }
- }
-
- query_colormap(d, screen);
-}
-
-static void init_indexed(QXcbColormapPrivate *d, int screen)
-{
- d->pixels.resize(d->r_max * d->g_max * d->b_max);
-
- // create color cube
- for (int x = 0, r = 0; r < d->r_max; ++r) {
- for (int g = 0; g < d->g_max; ++g) {
- for (int b = 0; b < d->b_max; ++b, ++x) {
- const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1),
- (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1),
- (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1));
-
- d->pixels[x] = -1;
-
- if (d->visual->c_class & 1) {
- XColor xcolor;
- xcolor.red = qRed(rgb) * 0x101;
- xcolor.green = qGreen(rgb) * 0x101;
- xcolor.blue = qBlue(rgb) * 0x101;
- xcolor.pixel = 0ul;
-
- if (XAllocColor(X11->display, d->colormap, &xcolor))
- d->pixels[x] = xcolor.pixel;
- }
- }
- }
- }
-
- query_colormap(d, screen);
-}
-
-static void init_direct(QXcbColormapPrivate *d, bool ownColormap)
-{
- if (d->visual->c_class != DirectColor || !ownColormap)
- return;
-
- // preallocate 768 on the stack, so that we don't have to malloc
- // for the common case (<= 24 bpp)
- QVarLengthArray<XColor, 768> colorTable(d->r_max + d->g_max + d->b_max);
- int i = 0;
-
- for (int r = 0; r < d->r_max; ++r) {
- colorTable[i].red = r << 8 | r;
- colorTable[i].pixel = r << d->r_shift;
- colorTable[i].flags = DoRed;
- ++i;
- }
-
- for (int g = 0; g < d->g_max; ++g) {
- colorTable[i].green = g << 8 | g;
- colorTable[i].pixel = g << d->g_shift;
- colorTable[i].flags = DoGreen;
- ++i;
- }
-
- for (int b = 0; b < d->b_max; ++b) {
- colorTable[i].blue = (b << 8 | b);
- colorTable[i].pixel = b << d->b_shift;
- colorTable[i].flags = DoBlue;
- ++i;
- }
-
- XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count());
-}
-
-static QXcbColormap **cmaps = nullptr;
-
-void QXcbColormap::initialize()
-{
- Display *display = X11->display;
- const int screens = ScreenCount(display);
-
- cmaps = new QXcbColormap*[screens];
-
- for (int i = 0; i < screens; ++i) {
- cmaps[i] = new QXcbColormap;
- QXcbColormapPrivate * const d = cmaps[i]->d;
-
- bool use_stdcmap = false;
- int color_count = X11->color_count;
-
- // defaults
- d->depth = DefaultDepth(display, i);
- d->colormap = DefaultColormap(display, i);
- d->defaultColormap = true;
- d->visual = DefaultVisual(display, i);
- d->defaultVisual = true;
-
- Visual *argbVisual = nullptr;
-
- if (X11->visual && i == DefaultScreen(display)) {
- // only use the outside colormap on the default screen
- d->visual = find_visual(display, i, X11->visual->c_class,
- XVisualIDFromVisual(X11->visual),
- &d->depth, &d->defaultVisual);
- } else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6)
- || (X11->visual_id != -1)) {
- // look for a specific visual or type of visual
- d->visual = find_visual(display, i, X11->visual_class, X11->visual_id,
- &d->depth, &d->defaultVisual);
- } else if (!X11->custom_cmap) {
- XStandardColormap *stdcmap = nullptr;
- int ncmaps = 0;
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- int nvi;
- XVisualInfo templ;
- templ.screen = i;
- templ.depth = 32;
- templ.c_class = TrueColor;
- XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask |
- VisualDepthMask |
- VisualClassMask, &templ, &nvi);
- for (int idx = 0; idx < nvi; ++idx) {
- XRenderPictFormat *format = XRenderFindVisualFormat(X11->display,
- xvi[idx].visual);
- if (format->type == PictTypeDirect && format->direct.alphaMask) {
- argbVisual = xvi[idx].visual;
- break;
- }
- }
- XFree(xvi);
- }
-#endif
- if (XGetRGBColormaps(display, RootWindow(display, i),
- &stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) {
- if (stdcmap) {
- for (int c = 0; c < ncmaps; ++c) {
- if (!stdcmap[c].red_max ||
- !stdcmap[c].green_max ||
- !stdcmap[c].blue_max ||
- !stdcmap[c].red_mult ||
- !stdcmap[c].green_mult ||
- !stdcmap[c].blue_mult)
- continue; // invalid stdcmap
-
- XVisualInfo proto;
- proto.visualid = stdcmap[c].visualid;
- proto.screen = i;
-
- int nvisuals = 0;
- XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask,
- &proto, &nvisuals);
- if (vi) {
- if (nvisuals > 0) {
- use_stdcmap = true;
-
- d->mode = ((vi[0].visual->c_class < StaticColor)
- ? Gray
- : ((vi[0].visual->c_class < TrueColor)
- ? Indexed
- : Direct));
-
- d->depth = vi[0].depth;
- d->colormap = stdcmap[c].colormap;
- d->defaultColormap = true;
- d->visual = vi[0].visual;
- d->defaultVisual = (d->visual == DefaultVisual(display, i));
-
- d->r_max = stdcmap[c].red_max + 1;
- d->g_max = stdcmap[c].green_max + 1;
- d->b_max = stdcmap[c].blue_max + 1;
-
- if (d->mode == Direct) {
- // calculate offsets
- d->r_shift = lowest_bit(d->visual->red_mask);
- d->g_shift = lowest_bit(d->visual->green_mask);
- d->b_shift = lowest_bit(d->visual->blue_mask);
- } else {
- d->r_shift = 0;
- d->g_shift = 0;
- d->b_shift = 0;
- }
- }
- XFree(vi);
- }
- break;
- }
- XFree(stdcmap);
- }
- }
- }
- if (!use_stdcmap) {
- switch (d->visual->c_class) {
- case StaticGray:
- d->mode = Gray;
-
- d->r_max = d->g_max = d->b_max = d->visual->map_entries;
- break;
-
- case XGrayScale:
- d->mode = Gray;
-
- // follow precedent set in libXmu...
- if (color_count != 0)
- d->r_max = d->g_max = d->b_max = color_count;
- else if (d->visual->map_entries > 65000)
- d->r_max = d->g_max = d->b_max = 4096;
- else if (d->visual->map_entries > 4000)
- d->r_max = d->g_max = d->b_max = 512;
- else if (d->visual->map_entries > 250)
- d->r_max = d->g_max = d->b_max = 12;
- else
- d->r_max = d->g_max = d->b_max = 4;
- break;
-
- case StaticColor:
- d->mode = Indexed;
-
- d->r_max = right_align(d->visual->red_mask) + 1;
- d->g_max = right_align(d->visual->green_mask) + 1;
- d->b_max = right_align(d->visual->blue_mask) + 1;
- break;
-
- case PseudoColor:
- d->mode = Indexed;
-
- // follow precedent set in libXmu...
- if (color_count != 0)
- d->r_max = d->g_max = d->b_max = cube_root(color_count);
- else if (d->visual->map_entries > 65000)
- d->r_max = d->g_max = d->b_max = 27;
- else if (d->visual->map_entries > 4000)
- d->r_max = d->g_max = d->b_max = 12;
- else if (d->visual->map_entries > 250)
- d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125);
- else
- d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries);
- break;
-
- case TrueColor:
- case DirectColor:
- d->mode = Direct;
-
- d->r_max = right_align(d->visual->red_mask) + 1;
- d->g_max = right_align(d->visual->green_mask) + 1;
- d->b_max = right_align(d->visual->blue_mask) + 1;
-
- d->r_shift = lowest_bit(d->visual->red_mask);
- d->g_shift = lowest_bit(d->visual->green_mask);
- d->b_shift = lowest_bit(d->visual->blue_mask);
- break;
- }
- }
-
- bool ownColormap = false;
- if (X11->colormap && i == DefaultScreen(display)) {
- // only use the outside colormap on the default screen
- d->colormap = X11->colormap;
- d->defaultColormap = (d->colormap == DefaultColormap(display, i));
- } else if ((!use_stdcmap
- && (((d->visual->c_class & 1) && X11->custom_cmap)
- || d->visual != DefaultVisual(display, i)))
- || d->visual->c_class == DirectColor) {
- // allocate custom colormap (we always do this when using DirectColor visuals)
- d->colormap =
- XCreateColormap(display, RootWindow(display, i), d->visual,
- d->visual->c_class == DirectColor ? AllocAll : AllocNone);
- d->defaultColormap = false;
- ownColormap = true;
- }
-
- switch (d->mode) {
- case Gray:
- init_gray(d, i);
- break;
- case Indexed:
- init_indexed(d, i);
- break;
- case Direct:
- init_direct(d, ownColormap);
- break;
- }
-
- QX11InfoData *screen = X11->screens + i;
- screen->depth = d->depth;
- screen->visual = d->visual;
- screen->defaultVisual = d->defaultVisual;
- screen->colormap = d->colormap;
- screen->defaultColormap = d->defaultColormap;
- screen->cells = screen->visual->map_entries;
-
- if (argbVisual) {
- X11->argbVisuals[i] = argbVisual;
- X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone);
- }
-
- // ###
- // We assume that 8bpp == pseudocolor, but this is not
- // always the case (according to the X server), so we need
- // to make sure that our internal data is setup in a way
- // that is compatible with our assumptions
- if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8)
- screen->cells = 256;
- }
-}
-
-void QXcbColormap::cleanup()
-{
- Display *display = X11->display;
- const int screens = ScreenCount(display);
-
- for (int i = 0; i < screens; ++i)
- delete cmaps[i];
-
- delete [] cmaps;
- cmaps = 0;
-}
-
-
-QXcbColormap QXcbColormap::instance(int screen)
-{
- if (screen == -1)
- screen = QXcbX11Info::appScreen();
- return *cmaps[screen];
-}
-
-/*! \internal
- Constructs a new colormap.
-*/
-QXcbColormap::QXcbColormap()
- : d(new QXcbColormapPrivate)
-{}
-
-QXcbColormap::QXcbColormap(const QXcbColormap &colormap)
- :d (colormap.d)
-{ d->ref.ref(); }
-
-QXcbColormap::~QXcbColormap()
-{
- if (!d->ref.deref()) {
- if (!d->defaultColormap)
- XFreeColormap(X11->display, d->colormap);
- delete d;
- }
-}
-
-QXcbColormap::Mode QXcbColormap::mode() const
-{ return d->mode; }
-
-int QXcbColormap::depth() const
-{ return d->depth; }
-
-int QXcbColormap::size() const
-{
- return (d->mode == Gray
- ? d->r_max
- : (d->mode == Indexed
- ? d->r_max * d->g_max * d->b_max
- : -1));
-}
-
-uint QXcbColormap::pixel(const QColor &color) const
-{
- const QRgba64 rgba64 = color.rgba64();
- // XXX We emulate the raster engine here by getting the
- // 8-bit values, but we could instead use the 16-bit
- // values for slightly better color accuracy
- const uint r = (rgba64.red8() * d->r_max) >> 8;
- const uint g = (rgba64.green8() * d->g_max) >> 8;
- const uint b = (rgba64.blue8() * d->b_max) >> 8;
- if (d->mode != Direct) {
- if (d->mode == Gray)
- return d->pixels.at((r * 30 + g * 59 + b * 11) / 100);
- return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b);
- }
- return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift);
-}
-
-const QColor QXcbColormap::colorAt(uint pixel) const
-{
- if (d->mode != Direct) {
- Q_ASSERT(pixel <= (uint)d->colors.size());
- return d->colors.at(pixel);
- }
-
- const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max;
- const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max;
- const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max;
- return QColor(r, g, b);
-}
-
-const QList<QColor> QXcbColormap::colormap() const
-{ return d->colors; }
-
-QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap)
-{
- qAtomicAssign(d, colormap.d);
- return *this;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h
deleted file mode 100644
index 9c9cce424c9..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <QColor>
-#include <QList>
-
-QT_BEGIN_NAMESPACE
-
-class QXcbColormapPrivate;
-class QXcbColormap
-{
-public:
- enum Mode { Direct, Indexed, Gray };
-
- static void initialize();
- static void cleanup();
-
- static QXcbColormap instance(int screen = -1);
-
- QXcbColormap(const QXcbColormap &colormap);
- ~QXcbColormap();
-
- QXcbColormap &operator=(const QXcbColormap &colormap);
-
- Mode mode() const;
-
- int depth() const;
- int size() const;
-
- uint pixel(const QColor &color) const;
- const QColor colorAt(uint pixel) const;
-
- const QList<QColor> colormap() const;
-
-private:
- QXcbColormap();
- QXcbColormapPrivate *d;
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
deleted file mode 100644
index 2f3827025f3..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
+++ /dev/null
@@ -1,2807 +0,0 @@
-// Copyright (C) 2018 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
-
-#include <QtCore/qrandom.h>
-
-#include <private/qpixmapcache_p.h>
-#include <private/qpaintengine_p.h>
-#include <private/qpainterpath_p.h>
-#include <private/qdrawhelper_p.h>
-#include <private/qfontengineglyphcache_p.h>
-
-#if QT_CONFIG(fontconfig)
-#include <private/qfontengine_ft_p.h>
-#endif
-
-#include "qpaintengine_x11_p.h"
-#include "qpolygonclipper_p.h"
-#include "qtessellator_p.h"
-#include "qpixmap_x11_p.h"
-#include "qcolormap_x11_p.h"
-#include "qt_x11_p.h"
-#include "qxcbexport.h"
-#include "qxcbnativepainting.h"
-
-QT_BEGIN_NAMESPACE
-
-using namespace Qt::StringLiterals;
-
-#if QT_CONFIG(xrender)
-
-class QXRenderTessellator : public QTessellator
-{
-public:
- QXRenderTessellator() : traps(0), allocated(0), size(0) {}
- ~QXRenderTessellator() { free(traps); }
- XTrapezoid *traps;
- int allocated;
- int size;
- void addTrap(const Trapezoid &trap) override;
- QRect tessellate(const QPointF *points, int nPoints, bool winding) {
- size = 0;
- setWinding(winding);
- return QTessellator::tessellate(points, nPoints).toRect();
- }
- void done() {
- if (allocated > 64) {
- free(traps);
- traps = 0;
- allocated = 0;
- }
- }
-};
-
-void QXRenderTessellator::addTrap(const Trapezoid &trap)
-{
- if (size == allocated) {
- allocated = qMax(2*allocated, 64);
- traps = q_check_ptr((XTrapezoid *)realloc(traps, allocated * sizeof(XTrapezoid)));
- }
- traps[size].top = Q27Dot5ToXFixed(trap.top);
- traps[size].bottom = Q27Dot5ToXFixed(trap.bottom);
- traps[size].left.p1.x = Q27Dot5ToXFixed(trap.topLeft->x);
- traps[size].left.p1.y = Q27Dot5ToXFixed(trap.topLeft->y);
- traps[size].left.p2.x = Q27Dot5ToXFixed(trap.bottomLeft->x);
- traps[size].left.p2.y = Q27Dot5ToXFixed(trap.bottomLeft->y);
- traps[size].right.p1.x = Q27Dot5ToXFixed(trap.topRight->x);
- traps[size].right.p1.y = Q27Dot5ToXFixed(trap.topRight->y);
- traps[size].right.p2.x = Q27Dot5ToXFixed(trap.bottomRight->x);
- traps[size].right.p2.y = Q27Dot5ToXFixed(trap.bottomRight->y);
- ++size;
-}
-
-#endif // QT_CONFIG(xrender)
-
-class QX11PaintEnginePrivate : public QPaintEnginePrivate
-{
- Q_DECLARE_PUBLIC(QX11PaintEngine)
-public:
- QX11PaintEnginePrivate()
- {
- opacity = 1.0;
- scrn = -1;
- hd = 0;
- picture = 0;
- gc = gc_brush = 0;
- dpy = 0;
- xinfo = 0;
- txop = QTransform::TxNone;
- has_clipping = false;
- render_hints = 0;
- xform_scale = 1;
-#if QT_CONFIG(xrender)
- tessellator = 0;
-#endif
- }
- enum GCMode {
- PenGC,
- BrushGC
- };
-
- void init();
- void fillPolygon_translated(const QPointF *points, int pointCount, GCMode gcMode,
- QPaintEngine::PolygonDrawMode mode);
- void fillPolygon_dev(const QPointF *points, int pointCount, GCMode gcMode,
- QPaintEngine::PolygonDrawMode mode);
- void fillPath(const QPainterPath &path, GCMode gcmode, bool transform);
- void strokePolygon_dev(const QPointF *points, int pointCount, bool close);
- void strokePolygon_translated(const QPointF *points, int pointCount, bool close);
- void setupAdaptedOrigin(const QPoint &p);
- void resetAdaptedOrigin();
- void decidePathFallback() {
- use_path_fallback = has_alpha_brush
- || has_alpha_pen
- || has_custom_pen
- || has_complex_xform
- || (render_hints & QPainter::Antialiasing);
- }
- void decideCoordAdjust() {
- adjust_coords = false;
- }
- void clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly);
- void systemStateChanged() override;
- inline bool isCosmeticPen() const {
- return cpen.isCosmetic();
- }
-
- Display *dpy;
- int scrn;
- int pdev_depth;
- unsigned long hd;
- QPixmap brush_pm;
-#if QT_CONFIG(xrender)
- unsigned long picture;
- unsigned long current_brush;
- QPixmap bitmap_texture;
- int composition_mode;
-#else
- unsigned long picture;
-#endif
- GC gc;
- GC gc_brush;
-
- QPen cpen;
- QBrush cbrush;
- QRegion crgn;
- QTransform matrix;
- qreal opacity;
-
- uint has_complex_xform : 1;
- uint has_scaling_xform : 1;
- uint has_non_scaling_xform : 1;
- uint has_custom_pen : 1;
- uint use_path_fallback : 1;
- uint adjust_coords : 1;
- uint has_clipping : 1;
- uint adapted_brush_origin : 1;
- uint adapted_pen_origin : 1;
- uint has_pen : 1;
- uint has_brush : 1;
- uint has_texture : 1;
- uint has_alpha_texture : 1;
- uint has_pattern : 1;
- uint has_alpha_pen : 1;
- uint has_alpha_brush : 1;
- uint use_sysclip : 1;
- uint render_hints;
-
- const QXcbX11Info *xinfo;
- QPointF bg_origin;
- QTransform::TransformationType txop;
- qreal xform_scale;
-
- struct qt_float_point
- {
- qreal x, y;
- };
- QPolygonClipper<qt_float_point, qt_float_point, float> polygonClipper;
-
- int xlibMaxLinePoints;
-#if QT_CONFIG(xrender)
- QXRenderTessellator *tessellator;
-#endif
-};
-
-#if QT_CONFIG(xrender)
-class QXRenderGlyphCache : public QFontEngineGlyphCache
-{
-public:
- QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix);
- ~QXRenderGlyphCache();
-
- bool addGlyphs(const QTextItemInt &ti,
- const QVarLengthArray<glyph_t> &glyphs,
- const QVarLengthArray<QFixedPoint> &positions);
- bool draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti);
-
- inline GlyphSet glyphSet();
- inline int glyphBufferSize(const QFontEngineFT::Glyph &glyph) const;
-
- inline QImage::Format imageFormat() const;
- inline const XRenderPictFormat *renderPictFormat() const;
-
- static inline QFontEngine::GlyphFormat glyphFormatForDepth(QFontEngine *fontEngine, int depth);
- static inline Glyph glyphId(glyph_t glyph, QFixed subPixelPosition);
-
- static inline bool isValidCoordinate(const QFixedPoint &fp);
-
-private:
- QXcbX11Info xinfo;
- GlyphSet gset;
- QSet<Glyph> cachedGlyphs;
-};
-#endif // QT_CONFIG(xrender)
-
-extern QPixmap qt_pixmapForBrush(int brushStyle, bool invert); //in qbrush.cpp
-extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap);
-
-extern "C" {
-Q_XCB_EXPORT Drawable qt_x11Handle(const QPaintDevice *pd)
-{
- if (!pd)
- return 0;
-
-// if (pd->devType() == QInternal::Widget)
-// return static_cast<const QWidget *>(pd)->handle();
-
- if (pd->devType() == QInternal::Pixmap)
- return qt_x11PixmapHandle(*static_cast<const QPixmap *>(pd));
-
- return 0;
-}
-}
-
-static const QXcbX11Info *qt_x11Info(const QPaintDevice *pd)
-{
- if (!pd)
- return 0;
-
-// if (pd->devType() == QInternal::Widget)
-// return &static_cast<const QWidget *>(pd)->x11Info();
-
- if (pd->devType() == QInternal::Pixmap)
- return &qt_x11Info(*static_cast<const QPixmap *>(pd));
-
- return 0;
-}
-
-// use the same rounding as in qrasterizer.cpp (6 bit fixed point)
-static const qreal aliasedCoordinateDelta = 0.5 - 0.015625;
-
-#undef X11 // defined in qt_x11_p.h
-extern "C" {
-/*!
- Returns the X11 specific pen GC for the painter \a p. Note that
- QPainter::begin() must be called before this function returns a
- valid GC.
-*/
-GC Q_XCB_EXPORT qt_x11_get_pen_gc(QPainter *p)
-{
- if (p && p->paintEngine()
- && p->paintEngine()->isActive()
- && p->paintEngine()->type() == QPaintEngine::X11) {
- return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc;
- }
- return 0;
-}
-
-/*!
- Returns the X11 specific brush GC for the painter \a p. Note that
- QPainter::begin() must be called before this function returns a
- valid GC.
-*/
-GC Q_XCB_EXPORT qt_x11_get_brush_gc(QPainter *p)
-{
- if (p && p->paintEngine()
- && p->paintEngine()->isActive()
- && p->paintEngine()->type() == QPaintEngine::X11) {
- return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc_brush;
- }
- return 0;
-}
-}
-#define X11 qt_x11Data
-
-// internal helper. Converts an integer value to an unique string token
-template <typename T>
- struct HexString
-{
- inline HexString(const T t)
- : val(t)
- {}
-
- inline void write(QChar *&dest) const
- {
- const ushort hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
- const char *c = reinterpret_cast<const char *>(&val);
- for (uint i = 0; i < sizeof(T); ++i) {
- *dest++ = hexChars[*c & 0xf];
- *dest++ = hexChars[(*c & 0xf0) >> 4];
- ++c;
- }
- }
- const T val;
-};
-
-// specialization to enable fast concatenating of our string tokens to a string
-template <typename T>
- struct QConcatenable<HexString<T> >
-{
- typedef HexString<T> type;
- enum { ExactSize = true };
- static int size(const HexString<T> &) { return sizeof(T) * 2; }
- static inline void appendTo(const HexString<T> &str, QChar *&out) { str.write(out); }
- typedef QString ConvertTo;
-};
-
-#if QT_CONFIG(xrender)
-static const int compositionModeToRenderOp[QPainter::CompositionMode_Xor + 1] = {
- PictOpOver, //CompositionMode_SourceOver,
- PictOpOverReverse, //CompositionMode_DestinationOver,
- PictOpClear, //CompositionMode_Clear,
- PictOpSrc, //CompositionMode_Source,
- PictOpDst, //CompositionMode_Destination,
- PictOpIn, //CompositionMode_SourceIn,
- PictOpInReverse, //CompositionMode_DestinationIn,
- PictOpOut, //CompositionMode_SourceOut,
- PictOpOutReverse, //CompositionMode_DestinationOut,
- PictOpAtop, //CompositionMode_SourceAtop,
- PictOpAtopReverse, //CompositionMode_DestinationAtop,
- PictOpXor //CompositionMode_Xor
-};
-
-static inline int qpainterOpToXrender(QPainter::CompositionMode mode)
-{
- Q_ASSERT(mode <= QPainter::CompositionMode_Xor);
- return compositionModeToRenderOp[mode];
-}
-
-static inline bool complexPictOp(int op)
-{
- return op != PictOpOver && op != PictOpSrc;
-}
-#endif
-
-static inline void x11SetClipRegion(Display *dpy, GC gc, GC gc2,
-#if QT_CONFIG(xrender)
- Picture picture,
-#else
- Qt::HANDLE picture,
-#endif
- const QRegion &r)
-{
-// int num;
-// XRectangle *rects = (XRectangle *)qt_getClipRects(r, num);
- QList<XRectangle> rects = qt_region_to_xrectangles(r);
- int num = rects.size();
-
- if (gc)
- XSetClipRectangles( dpy, gc, 0, 0, rects.data(), num, Unsorted );
- if (gc2)
- XSetClipRectangles( dpy, gc2, 0, 0, rects.data(), num, Unsorted );
-
-#if QT_CONFIG(xrender)
- if (picture)
- XRenderSetPictureClipRectangles(dpy, picture, 0, 0, rects.data(), num);
-#else
- Q_UNUSED(picture);
-#endif // QT_CONFIG(xrender)
-}
-
-
-static inline void x11ClearClipRegion(Display *dpy, GC gc, GC gc2,
-#if QT_CONFIG(xrender)
- Picture picture
-#else
- Qt::HANDLE picture
-#endif
- )
-{
- if (gc)
- XSetClipMask(dpy, gc, XNone);
- if (gc2)
- XSetClipMask(dpy, gc2, XNone);
-
-#if QT_CONFIG(xrender)
- if (picture) {
- XRenderPictureAttributes attrs;
- attrs.clip_mask = XNone;
- XRenderChangePicture (dpy, picture, CPClipMask, &attrs);
- }
-#else
- Q_UNUSED(picture);
-#endif // QT_CONFIG(xrender)
-}
-
-
-#define DITHER_SIZE 16
-static const uchar base_dither_matrix[DITHER_SIZE][DITHER_SIZE] = {
- { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 },
- { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 },
- { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 },
- { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 },
- { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 },
- { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 },
- { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 },
- { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 },
- { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 },
- { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 },
- { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 },
- { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 },
- { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 },
- { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 },
- { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 },
- { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 }
-};
-
-static QPixmap qt_patternForAlpha(uchar alpha, int screen)
-{
- QPixmap pm;
- QString key = "$qt-alpha-brush$"_L1
- % HexString<uchar>(alpha)
- % HexString<int>(screen);
-
- if (!QPixmapCache::find(key, &pm)) {
- // #### why not use a mono image here????
- QImage pattern(DITHER_SIZE, DITHER_SIZE, QImage::Format_ARGB32);
- pattern.fill(0xffffffff);
- for (int y = 0; y < DITHER_SIZE; ++y) {
- for (int x = 0; x < DITHER_SIZE; ++x) {
- if (base_dither_matrix[x][y] <= alpha)
- pattern.setPixel(x, y, 0x00000000);
- }
- }
- pm = QBitmap::fromImage(pattern);
- qt_x11SetScreen(pm, screen);
- //pm.x11SetScreen(screen);
- QPixmapCache::insert(key, pm);
- }
- return pm;
-}
-
-
-#if QT_CONFIG(xrender)
-static Picture getPatternFill(int screen, const QBrush &b)
-{
- if (!X11->use_xrender)
- return XNone;
-
- XRenderColor color = X11->preMultiply(b.color());
- XRenderColor bg_color;
-
- bg_color = X11->preMultiply(QColor(0, 0, 0, 0));
-
- for (int i = 0; i < X11->pattern_fill_count; ++i) {
- if (X11->pattern_fills[i].screen == screen
- && X11->pattern_fills[i].opaque == false
- && X11->pattern_fills[i].style == b.style()
- && X11->pattern_fills[i].color.alpha == color.alpha
- && X11->pattern_fills[i].color.red == color.red
- && X11->pattern_fills[i].color.green == color.green
- && X11->pattern_fills[i].color.blue == color.blue
- && X11->pattern_fills[i].bg_color.alpha == bg_color.alpha
- && X11->pattern_fills[i].bg_color.red == bg_color.red
- && X11->pattern_fills[i].bg_color.green == bg_color.green
- && X11->pattern_fills[i].bg_color.blue == bg_color.blue)
- return X11->pattern_fills[i].picture;
- }
- // none found, replace one
- int i = QRandomGenerator::global()->generate() % 16;
-
- if (X11->pattern_fills[i].screen != screen && X11->pattern_fills[i].picture) {
- XRenderFreePicture (QXcbX11Info::display(), X11->pattern_fills[i].picture);
- X11->pattern_fills[i].picture = 0;
- }
-
- if (!X11->pattern_fills[i].picture) {
- Pixmap pixmap = XCreatePixmap (QXcbX11Info::display(), RootWindow (QXcbX11Info::display(), screen), 8, 8, 32);
- XRenderPictureAttributes attrs;
- attrs.repeat = True;
- X11->pattern_fills[i].picture = XRenderCreatePicture (QXcbX11Info::display(), pixmap,
- XRenderFindStandardFormat(QXcbX11Info::display(), PictStandardARGB32),
- CPRepeat, &attrs);
- XFreePixmap (QXcbX11Info::display(), pixmap);
- }
-
- X11->pattern_fills[i].screen = screen;
- X11->pattern_fills[i].color = color;
- X11->pattern_fills[i].bg_color = bg_color;
- X11->pattern_fills[i].opaque = false;
- X11->pattern_fills[i].style = b.style();
-
- XRenderFillRectangle(QXcbX11Info::display(), PictOpSrc, X11->pattern_fills[i].picture, &bg_color, 0, 0, 8, 8);
-
- QPixmap pattern(qt_pixmapForBrush(b.style(), true));
- XRenderPictureAttributes attrs;
- attrs.repeat = true;
- XRenderChangePicture(QXcbX11Info::display(), qt_x11PictureHandle(pattern), CPRepeat, &attrs);
-
- Picture fill_fg = X11->getSolidFill(screen, b.color());
- XRenderComposite(QXcbX11Info::display(), PictOpOver, fill_fg, qt_x11PictureHandle(pattern),
- X11->pattern_fills[i].picture,
- 0, 0, 0, 0, 0, 0, 8, 8);
-
- return X11->pattern_fills[i].picture;
-}
-
-static void qt_render_bitmap(Display *dpy, int scrn, Picture src, Picture dst,
- int sx, int sy, int x, int y, int sw, int sh,
- const QPen &pen)
-{
- Picture fill_fg = X11->getSolidFill(scrn, pen.color());
- XRenderComposite(dpy, PictOpOver,
- fill_fg, src, dst, sx, sy, sx, sy, x, y, sw, sh);
-}
-#endif
-
-void QX11PaintEnginePrivate::init()
-{
- dpy = 0;
- scrn = 0;
- hd = 0;
- picture = 0;
- xinfo = 0;
-#if QT_CONFIG(xrender)
- current_brush = 0;
- composition_mode = PictOpOver;
- tessellator = new QXRenderTessellator;
-#endif
-}
-
-void QX11PaintEnginePrivate::setupAdaptedOrigin(const QPoint &p)
-{
- if (adapted_pen_origin)
- XSetTSOrigin(dpy, gc, p.x(), p.y());
- if (adapted_brush_origin)
- XSetTSOrigin(dpy, gc_brush, p.x(), p.y());
-}
-
-void QX11PaintEnginePrivate::resetAdaptedOrigin()
-{
- if (adapted_pen_origin)
- XSetTSOrigin(dpy, gc, 0, 0);
- if (adapted_brush_origin)
- XSetTSOrigin(dpy, gc_brush, 0, 0);
-}
-
-void QX11PaintEnginePrivate::clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly)
-{
- int clipped_count = 0;
- qt_float_point *clipped_points = 0;
- polygonClipper.clipPolygon((qt_float_point *) poly.data(), poly.size(),
- &clipped_points, &clipped_count);
- clipped_poly->resize(clipped_count);
- for (int i=0; i<clipped_count; ++i)
- (*clipped_poly)[i] = *((QPointF *)(&clipped_points[i]));
-}
-
-void QX11PaintEnginePrivate::systemStateChanged()
-{
- Q_Q(QX11PaintEngine);
- QPainter *painter = q->state ? q->state->painter() : nullptr;
- if (painter && painter->hasClipping()) {
- if (q->testDirty(QPaintEngine::DirtyTransform))
- q->updateMatrix(q->state->transform());
- QPolygonF clip_poly_dev(matrix.map(painter->clipPath().toFillPolygon()));
- QPolygonF clipped_poly_dev;
- clipPolygon_dev(clip_poly_dev, &clipped_poly_dev);
- q->updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip);
- } else {
- q->updateClipRegion_dev(QRegion(), Qt::NoClip);
- }
-}
-
-static QPaintEngine::PaintEngineFeatures qt_decide_features()
-{
- QPaintEngine::PaintEngineFeatures features =
- QPaintEngine::PrimitiveTransform
- | QPaintEngine::PatternBrush
- | QPaintEngine::AlphaBlend
- | QPaintEngine::PainterPaths
- | QPaintEngine::RasterOpModes;
-
- if (X11->use_xrender) {
- features |= QPaintEngine::Antialiasing;
- features |= QPaintEngine::PorterDuff;
- features |= QPaintEngine::MaskedBrush;
-#if 0
- if (X11->xrender_version > 10) {
- features |= QPaintEngine::LinearGradientFill;
- // ###
- }
-#endif
- }
-
- return features;
-}
-
-/*
- * QX11PaintEngine members
- */
-
-QX11PaintEngine::QX11PaintEngine()
- : QPaintEngine(*(new QX11PaintEnginePrivate), qt_decide_features())
-{
- Q_D(QX11PaintEngine);
- d->init();
-}
-
-QX11PaintEngine::QX11PaintEngine(QX11PaintEnginePrivate &dptr)
- : QPaintEngine(dptr, qt_decide_features())
-{
- Q_D(QX11PaintEngine);
- d->init();
-}
-
-QX11PaintEngine::~QX11PaintEngine()
-{
-#if QT_CONFIG(xrender)
- Q_D(QX11PaintEngine);
- delete d->tessellator;
-#endif
-}
-
-bool QX11PaintEngine::begin(QPaintDevice *pdev)
-{
- Q_D(QX11PaintEngine);
- d->xinfo = qt_x11Info(pdev);
-#if QT_CONFIG(xrender)
- if (pdev->devType() == QInternal::Pixmap) {
- const QPixmap *pm = static_cast<const QPixmap *>(pdev);
- QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(pm->handle());
- if (X11->use_xrender && data->depth() != 32 && data->x11_mask)
- data->convertToARGB32();
- d->picture = qt_x11PictureHandle(*static_cast<const QPixmap *>(pdev));
- }
-#else
- d->picture = 0;
-#endif
- d->hd = qt_x11Handle(pdev);
-
- Q_ASSERT(d->xinfo != 0);
- d->dpy = d->xinfo->display(); // get display variable
- d->scrn = d->xinfo->screen(); // get screen variable
-
- d->crgn = QRegion();
- d->gc = XCreateGC(d->dpy, d->hd, 0, 0);
- d->gc_brush = XCreateGC(d->dpy, d->hd, 0, 0);
- d->has_alpha_brush = false;
- d->has_alpha_pen = false;
- d->has_clipping = false;
- d->has_complex_xform = false;
- d->has_scaling_xform = false;
- d->has_non_scaling_xform = true;
- d->xform_scale = 1;
- d->has_custom_pen = false;
- d->matrix = QTransform();
- d->pdev_depth = d->pdev->depth();
- d->render_hints = 0;
- d->txop = QTransform::TxNone;
- d->use_path_fallback = false;
-#if QT_CONFIG(xrender)
- d->composition_mode = PictOpOver;
-#endif
- d->xlibMaxLinePoints = 32762; // a safe number used to avoid, call to XMaxRequestSize(d->dpy) - 3;
- d->opacity = 1;
-
- QX11PlatformPixmap *x11pm = paintDevice()->devType() == QInternal::Pixmap ? qt_x11Pixmap(*static_cast<QPixmap *>(paintDevice())) : nullptr;
- d->use_sysclip = paintDevice()->devType() == QInternal::Widget || (x11pm ? x11pm->isBackingStore() : false);
-
- // Set up the polygon clipper. Note: This will only work in
- // polyline mode as long as we have a buffer zone, since a
- // polyline may be clipped into several non-connected polylines.
- const int BUFFERZONE = 1000;
- QRect devClipRect(-BUFFERZONE, -BUFFERZONE,
- pdev->width() + 2*BUFFERZONE, pdev->height() + 2*BUFFERZONE);
- d->polygonClipper.setBoundingRect(devClipRect);
-
- setSystemClip(systemClip());
- d->systemStateChanged();
-
- qt_x11SetDefaultScreen(d->xinfo->screen());
-
- updatePen(QPen(Qt::black));
- updateBrush(QBrush(Qt::white), QPoint());
-
- setDirty(QPaintEngine::DirtyClipRegion);
- setDirty(QPaintEngine::DirtyPen);
- setDirty(QPaintEngine::DirtyBrush);
- setDirty(QPaintEngine::DirtyBackground);
-
- setActive(true);
- return true;
-}
-
-bool QX11PaintEngine::end()
-{
- Q_D(QX11PaintEngine);
-
-#if QT_CONFIG(xrender)
- if (d->picture) {
- // reset clipping/subwindow mode on our render picture
- XRenderPictureAttributes attrs;
- attrs.subwindow_mode = ClipByChildren;
- attrs.clip_mask = XNone;
- XRenderChangePicture(d->dpy, d->picture, CPClipMask|CPSubwindowMode, &attrs);
- }
-#endif
-
- if (d->gc_brush && d->pdev->painters < 2) {
- XFreeGC(d->dpy, d->gc_brush);
- d->gc_brush = 0;
- }
-
- if (d->gc && d->pdev->painters < 2) {
- XFreeGC(d->dpy, d->gc);
- d->gc = 0;
- }
-
- // Restore system clip for alien widgets painting outside the paint event.
-// if (d->pdev->devType() == QInternal::Widget && !static_cast<QWidget *>(d->pdev)->internalWinId())
- d->currentClipDevice = nullptr;
- setSystemClip(QRegion());
-
- setActive(false);
- return true;
-}
-
-static bool clipLine(QLineF *line, const QRect &rect)
-{
- qreal x1 = line->x1();
- qreal x2 = line->x2();
- qreal y1 = line->y1();
- qreal y2 = line->y2();
-
- qreal left = rect.x();
- qreal right = rect.x() + rect.width() - 1;
- qreal top = rect.y();
- qreal bottom = rect.y() + rect.height() - 1;
-
- enum { Left, Right, Top, Bottom };
- // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html
- int p1 = ((x1 < left) << Left)
- | ((x1 > right) << Right)
- | ((y1 < top) << Top)
- | ((y1 > bottom) << Bottom);
- int p2 = ((x2 < left) << Left)
- | ((x2 > right) << Right)
- | ((y2 < top) << Top)
- | ((y2 > bottom) << Bottom);
-
- if (p1 & p2)
- // completely outside
- return false;
-
- if (p1 | p2) {
- qreal dx = x2 - x1;
- qreal dy = y2 - y1;
-
- // clip x coordinates
- if (x1 < left) {
- y1 += dy/dx * (left - x1);
- x1 = left;
- } else if (x1 > right) {
- y1 -= dy/dx * (x1 - right);
- x1 = right;
- }
- if (x2 < left) {
- y2 += dy/dx * (left - x2);
- x2 = left;
- } else if (x2 > right) {
- y2 -= dy/dx * (x2 - right);
- x2 = right;
- }
- p1 = ((y1 < top) << Top)
- | ((y1 > bottom) << Bottom);
- p2 = ((y2 < top) << Top)
- | ((y2 > bottom) << Bottom);
- if (p1 & p2)
- return false;
- // clip y coordinates
- if (y1 < top) {
- x1 += dx/dy * (top - y1);
- y1 = top;
- } else if (y1 > bottom) {
- x1 -= dx/dy * (y1 - bottom);
- y1 = bottom;
- }
- if (y2 < top) {
- x2 += dx/dy * (top - y2);
- y2 = top;
- } else if (y2 > bottom) {
- x2 -= dx/dy * (y2 - bottom);
- y2 = bottom;
- }
- *line = QLineF(QPointF(x1, y1), QPointF(x2, y2));
- }
- return true;
-}
-
-void QX11PaintEngine::drawLines(const QLine *lines, int lineCount)
-{
- Q_ASSERT(lines);
- Q_ASSERT(lineCount);
- Q_D(QX11PaintEngine);
-
- if (d->has_alpha_brush
- || d->has_alpha_pen
- || d->has_custom_pen
- || (d->cpen.widthF() > 0 && d->has_complex_xform
- && !d->has_non_scaling_xform)
- || (d->render_hints & QPainter::Antialiasing)) {
- for (int i = 0; i < lineCount; ++i) {
- QPainterPath path(lines[i].p1());
- path.lineTo(lines[i].p2());
- drawPath(path);
- }
- return;
- }
-
- if (d->has_pen) {
- for (int i = 0; i < lineCount; ++i) {
- QLineF linef;
- if (d->txop == QTransform::TxNone) {
- linef = lines[i];
- } else {
- linef = d->matrix.map(QLineF(lines[i]));
- }
- if (clipLine(&linef, d->polygonClipper.boundingRect())) {
- int x1 = qRound(linef.x1() + aliasedCoordinateDelta);
- int y1 = qRound(linef.y1() + aliasedCoordinateDelta);
- int x2 = qRound(linef.x2() + aliasedCoordinateDelta);
- int y2 = qRound(linef.y2() + aliasedCoordinateDelta);
-
- XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2);
- }
- }
- }
-}
-
-void QX11PaintEngine::drawLines(const QLineF *lines, int lineCount)
-{
- Q_ASSERT(lines);
- Q_ASSERT(lineCount);
- Q_D(QX11PaintEngine);
-
- if (d->has_alpha_brush
- || d->has_alpha_pen
- || d->has_custom_pen
- || (d->cpen.widthF() > 0 && d->has_complex_xform
- && !d->has_non_scaling_xform)
- || (d->render_hints & QPainter::Antialiasing)) {
- for (int i = 0; i < lineCount; ++i) {
- QPainterPath path(lines[i].p1());
- path.lineTo(lines[i].p2());
- drawPath(path);
- }
- return;
- }
-
- if (d->has_pen) {
- for (int i = 0; i < lineCount; ++i) {
- QLineF linef = d->matrix.map(lines[i]);
- if (clipLine(&linef, d->polygonClipper.boundingRect())) {
- int x1 = qRound(linef.x1() + aliasedCoordinateDelta);
- int y1 = qRound(linef.y1() + aliasedCoordinateDelta);
- int x2 = qRound(linef.x2() + aliasedCoordinateDelta);
- int y2 = qRound(linef.y2() + aliasedCoordinateDelta);
-
- XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2);
- }
- }
- }
-}
-
-static inline QLine clipStraightLine(const QRect &clip, const QLine &l)
-{
- if (l.p1().x() == l.p2().x()) {
- int x = qBound(clip.left(), l.p1().x(), clip.right());
- int y1 = qBound(clip.top(), l.p1().y(), clip.bottom());
- int y2 = qBound(clip.top(), l.p2().y(), clip.bottom());
-
- return QLine(x, y1, x, y2);
- } else {
- Q_ASSERT(l.p1().y() == l.p2().y());
-
- int x1 = qBound(clip.left(), l.p1().x(), clip.right());
- int x2 = qBound(clip.left(), l.p2().x(), clip.right());
- int y = qBound(clip.top(), l.p1().y(), clip.bottom());
-
- return QLine(x1, y, x2, y);
- }
-}
-
-void QX11PaintEngine::drawRects(const QRectF *rects, int rectCount)
-{
- Q_D(QX11PaintEngine);
- Q_ASSERT(rects);
- Q_ASSERT(rectCount);
-
- if (rectCount != 1
- || d->has_pen
- || d->has_alpha_brush
- || d->has_complex_xform
- || d->has_custom_pen
- || d->cbrush.style() != Qt::SolidPattern
-#if QT_CONFIG(xrender)
- || complexPictOp(d->composition_mode)
-#endif
- )
- {
- QPaintEngine::drawRects(rects, rectCount);
- return;
- }
-
- QPoint alignedOffset;
- if (d->txop == QTransform::TxTranslate) {
- QPointF offset(d->matrix.dx(), d->matrix.dy());
- alignedOffset = offset.toPoint();
- if (offset != QPointF(alignedOffset)) {
- QPaintEngine::drawRects(rects, rectCount);
- return;
- }
- }
-
- const QRectF& r = rects[0];
- QRect alignedRect = r.toAlignedRect();
- if (r != QRectF(alignedRect)) {
- QPaintEngine::drawRects(rects, rectCount);
- return;
- }
- alignedRect.translate(alignedOffset);
-
- QRect clip(d->polygonClipper.boundingRect());
- alignedRect = alignedRect.intersected(clip);
- if (alignedRect.isEmpty())
- return;
-
- // simple-case:
- // the rectangle is pixel-aligned
- // the fill brush is just a solid non-alpha color
- // the painter transform is only integer translation
- // ignore: antialiasing and just XFillRectangles directly
- XRectangle xrect;
- xrect.x = short(alignedRect.x());
- xrect.y = short(alignedRect.y());
- xrect.width = ushort(alignedRect.width());
- xrect.height = ushort(alignedRect.height());
- XFillRectangles(d->dpy, d->hd, d->gc_brush, &xrect, 1);
-}
-
-void QX11PaintEngine::drawRects(const QRect *rects, int rectCount)
-{
- Q_D(QX11PaintEngine);
- Q_ASSERT(rects);
- Q_ASSERT(rectCount);
-
- if (d->has_alpha_pen
- || d->has_complex_xform
- || d->has_custom_pen
- || (d->render_hints & QPainter::Antialiasing))
- {
- for (int i = 0; i < rectCount; ++i) {
- QPainterPath path;
- path.addRect(rects[i]);
- drawPath(path);
- }
- return;
- }
-
- QRect clip(d->polygonClipper.boundingRect());
- QPoint offset(qRound(d->matrix.dx()), qRound(d->matrix.dy()));
-#if QT_CONFIG(xrender)
- ::Picture pict = d->picture;
-
- if (X11->use_xrender && pict && d->has_brush && d->pdev_depth != 1
- && (d->has_texture || d->has_alpha_brush || complexPictOp(d->composition_mode)))
- {
- XRenderColor xc;
- if (!d->has_texture && !d->has_pattern)
- xc = X11->preMultiply(d->cbrush.color());
-
- for (int i = 0; i < rectCount; ++i) {
- QRect r(rects[i]);
- if (d->txop == QTransform::TxTranslate)
- r.translate(offset);
-
- if (r.width() == 0 || r.height() == 0) {
- if (d->has_pen) {
- const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height()));
- XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y());
- }
- continue;
- }
-
- r = r.intersected(clip);
- if (r.isEmpty())
- continue;
- if (d->has_texture || d->has_pattern) {
- XRenderComposite(d->dpy, d->composition_mode, d->current_brush, 0, pict,
- qRound(r.x() - d->bg_origin.x()), qRound(r.y() - d->bg_origin.y()),
- 0, 0, r.x(), r.y(), r.width(), r.height());
- } else {
- XRenderFillRectangle(d->dpy, d->composition_mode, pict, &xc, r.x(), r.y(), r.width(), r.height());
- }
- if (d->has_pen)
- XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height());
- }
- } else
-#endif // QT_CONFIG(xrender)
- {
- if (d->has_brush && d->has_pen) {
- for (int i = 0; i < rectCount; ++i) {
- QRect r(rects[i]);
- if (d->txop == QTransform::TxTranslate)
- r.translate(offset);
-
- if (r.width() == 0 || r.height() == 0) {
- const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height()));
- XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y());
- continue;
- }
-
- r = r.intersected(clip);
- if (r.isEmpty())
- continue;
- d->setupAdaptedOrigin(r.topLeft());
- XFillRectangle(d->dpy, d->hd, d->gc_brush, r.x(), r.y(), r.width(), r.height());
- XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height());
- }
- d->resetAdaptedOrigin();
- } else {
- QVarLengthArray<XRectangle> xrects(rectCount);
- int numClipped = rectCount;
- for (int i = 0; i < rectCount; ++i) {
- QRect r(rects[i]);
- if (d->txop == QTransform::TxTranslate)
- r.translate(offset);
-
- if (r.width() == 0 || r.height() == 0) {
- --numClipped;
- if (d->has_pen) {
- const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height()));
- XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y());
- }
- continue;
- }
-
- r = r.intersected(clip);
- if (r.isEmpty()) {
- --numClipped;
- continue;
- }
- xrects[i].x = short(r.x());
- xrects[i].y = short(r.y());
- xrects[i].width = ushort(r.width());
- xrects[i].height = ushort(r.height());
- }
- if (numClipped) {
- d->setupAdaptedOrigin(rects[0].topLeft());
- if (d->has_brush)
- XFillRectangles(d->dpy, d->hd, d->gc_brush, xrects.data(), numClipped);
- else if (d->has_pen)
- XDrawRectangles(d->dpy, d->hd, d->gc, xrects.data(), numClipped);
- d->resetAdaptedOrigin();
- }
- }
- }
-}
-
-static inline void setCapStyle(int cap_style, GC gc)
-{
- ulong mask = GCCapStyle;
- XGCValues vals;
- vals.cap_style = cap_style;
- XChangeGC(QXcbX11Info::display(), gc, mask, &vals);
-}
-
-void QX11PaintEngine::drawPoints(const QPoint *points, int pointCount)
-{
- Q_ASSERT(points);
- Q_ASSERT(pointCount);
- Q_D(QX11PaintEngine);
-
- if (!d->has_pen)
- return;
-
- // use the same test here as in drawPath to ensure that we don't use the path fallback
- // and end up in XDrawLines for pens with width <= 1
- if (d->cpen.widthF() > 1.0f
- || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing)))
- || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate))
- {
- Qt::PenCapStyle capStyle = d->cpen.capStyle();
- if (capStyle == Qt::FlatCap) {
- setCapStyle(CapProjecting, d->gc);
- d->cpen.setCapStyle(Qt::SquareCap);
- }
- const QPoint *end = points + pointCount;
- while (points < end) {
- QPainterPath path;
- path.moveTo(*points);
- path.lineTo(points->x()+.005, points->y());
- drawPath(path);
- ++points;
- }
-
- if (capStyle == Qt::FlatCap) {
- setCapStyle(CapButt, d->gc);
- d->cpen.setCapStyle(capStyle);
- }
- return;
- }
-
- static const int BUF_SIZE = 1024;
- XPoint xPoints[BUF_SIZE];
- int i = 0, j = 0;
- while (i < pointCount) {
- while (i < pointCount && j < BUF_SIZE) {
- const QPoint &xformed = d->matrix.map(points[i]);
- int x = xformed.x();
- int y = xformed.y();
- if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) {
- xPoints[j].x = x;
- xPoints[j].y = y;
- ++j;
- }
- ++i;
- }
- if (j)
- XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin);
-
- j = 0;
- }
-}
-
-void QX11PaintEngine::drawPoints(const QPointF *points, int pointCount)
-{
- Q_ASSERT(points);
- Q_ASSERT(pointCount);
- Q_D(QX11PaintEngine);
-
- if (!d->has_pen)
- return;
-
- // use the same test here as in drawPath to ensure that we don't use the path fallback
- // and end up in XDrawLines for pens with width <= 1
- if (d->cpen.widthF() > 1.0f
- || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing)))
- || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate))
- {
- Qt::PenCapStyle capStyle = d->cpen.capStyle();
- if (capStyle == Qt::FlatCap) {
- setCapStyle(CapProjecting, d->gc);
- d->cpen.setCapStyle(Qt::SquareCap);
- }
-
- const QPointF *end = points + pointCount;
- while (points < end) {
- QPainterPath path;
- path.moveTo(*points);
- path.lineTo(points->x() + 0.005, points->y());
- drawPath(path);
- ++points;
- }
- if (capStyle == Qt::FlatCap) {
- setCapStyle(CapButt, d->gc);
- d->cpen.setCapStyle(capStyle);
- }
- return;
- }
-
- static const int BUF_SIZE = 1024;
- XPoint xPoints[BUF_SIZE];
- int i = 0, j = 0;
- while (i < pointCount) {
- while (i < pointCount && j < BUF_SIZE) {
- const QPointF &xformed = d->matrix.map(points[i]);
- int x = qFloor(xformed.x());
- int y = qFloor(xformed.y());
-
- if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) {
- xPoints[j].x = x;
- xPoints[j].y = y;
- ++j;
- }
- ++i;
- }
- if (j)
- XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin);
-
- j = 0;
- }
-}
-
-QPainter::RenderHints QX11PaintEngine::supportedRenderHints() const
-{
-#if QT_CONFIG(xrender)
- if (X11->use_xrender)
- return QPainter::Antialiasing;
-#endif
- return QFlag(0);
-}
-
-void QX11PaintEngine::updateState(const QPaintEngineState &state)
-{
- Q_D(QX11PaintEngine);
- QPaintEngine::DirtyFlags flags = state.state();
-
-
- if (flags & DirtyOpacity) {
- d->opacity = state.opacity();
- // Force update pen/brush as to get proper alpha colors propagated
- flags |= DirtyPen;
- flags |= DirtyBrush;
- }
-
- if (flags & DirtyTransform) updateMatrix(state.transform());
- if (flags & DirtyPen) updatePen(state.pen());
- if (flags & (DirtyBrush | DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin());
- if (flags & DirtyFont) updateFont(state.font());
-
- if (state.state() & DirtyClipEnabled) {
- if (state.isClipEnabled()) {
- QPolygonF clip_poly_dev(d->matrix.map(painter()->clipPath().toFillPolygon()));
- QPolygonF clipped_poly_dev;
- d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev);
- updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip);
- } else {
- updateClipRegion_dev(QRegion(), Qt::NoClip);
- }
- }
-
- if (flags & DirtyClipPath) {
- QPolygonF clip_poly_dev(d->matrix.map(state.clipPath().toFillPolygon()));
- QPolygonF clipped_poly_dev;
- d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev);
- updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon(), state.clipPath().fillRule()),
- state.clipOperation());
- } else if (flags & DirtyClipRegion) {
- extern QPainterPath qt_regionToPath(const QRegion &region);
- QPainterPath clip_path = qt_regionToPath(state.clipRegion());
- QPolygonF clip_poly_dev(d->matrix.map(clip_path.toFillPolygon()));
- QPolygonF clipped_poly_dev;
- d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev);
- updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), state.clipOperation());
- }
- if (flags & DirtyHints) updateRenderHints(state.renderHints());
- if (flags & DirtyCompositionMode) {
- int function = GXcopy;
- if (state.compositionMode() >= QPainter::RasterOp_SourceOrDestination) {
- switch (state.compositionMode()) {
- case QPainter::RasterOp_SourceOrDestination:
- function = GXor;
- break;
- case QPainter::RasterOp_SourceAndDestination:
- function = GXand;
- break;
- case QPainter::RasterOp_SourceXorDestination:
- function = GXxor;
- break;
- case QPainter::RasterOp_NotSourceAndNotDestination:
- function = GXnor;
- break;
- case QPainter::RasterOp_NotSourceOrNotDestination:
- function = GXnand;
- break;
- case QPainter::RasterOp_NotSourceXorDestination:
- function = GXequiv;
- break;
- case QPainter::RasterOp_NotSource:
- function = GXcopyInverted;
- break;
- case QPainter::RasterOp_SourceAndNotDestination:
- function = GXandReverse;
- break;
- case QPainter::RasterOp_NotSourceAndDestination:
- function = GXandInverted;
- break;
- default:
- function = GXcopy;
- }
- }
-#if QT_CONFIG(xrender)
- else {
- d->composition_mode =
- qpainterOpToXrender(state.compositionMode());
- }
-#endif
- XSetFunction(X11->display, d->gc, function);
- XSetFunction(X11->display, d->gc_brush, function);
- }
- d->decidePathFallback();
- d->decideCoordAdjust();
-}
-
-void QX11PaintEngine::updateRenderHints(QPainter::RenderHints hints)
-{
- Q_D(QX11PaintEngine);
- d->render_hints = hints;
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender && d->picture) {
- XRenderPictureAttributes attrs;
- attrs.poly_edge = (hints & QPainter::Antialiasing) ? PolyEdgeSmooth : PolyEdgeSharp;
- XRenderChangePicture(d->dpy, d->picture, CPPolyEdge, &attrs);
- }
-#endif
-}
-
-void QX11PaintEngine::updatePen(const QPen &pen)
-{
- Q_D(QX11PaintEngine);
- d->cpen = pen;
- int cp = CapButt;
- int jn = JoinMiter;
- int ps = pen.style();
-
- if (d->opacity < 1.0) {
- QColor c = d->cpen.color();
- c.setAlpha(qRound(c.alpha()*d->opacity));
- d->cpen.setColor(c);
- }
-
- d->has_pen = (ps != Qt::NoPen);
- d->has_alpha_pen = (pen.color().alpha() != 255);
-
- switch (pen.capStyle()) {
- case Qt::SquareCap:
- cp = CapProjecting;
- break;
- case Qt::RoundCap:
- cp = CapRound;
- break;
- case Qt::FlatCap:
- default:
- cp = CapButt;
- break;
- }
- switch (pen.joinStyle()) {
- case Qt::BevelJoin:
- jn = JoinBevel;
- break;
- case Qt::RoundJoin:
- jn = JoinRound;
- break;
- case Qt::MiterJoin:
- default:
- jn = JoinMiter;
- break;
- }
-
- d->adapted_pen_origin = false;
-
- char dashes[10]; // custom pen dashes
- int dash_len = 0; // length of dash list
- int xStyle = LineSolid;
-
- /*
- We are emulating Windows here. Windows treats cpen.width() == 1
- (or 0) as a very special case. The fudge variable unifies this
- case with the general case.
- */
- qreal pen_width = pen.widthF();
- int scale = qRound(pen_width < 1 ? 1 : pen_width);
- int space = (pen_width < 1 && pen_width > 0 ? 1 : (2 * scale));
- int dot = 1 * scale;
- int dash = 4 * scale;
-
- d->has_custom_pen = false;
-
- switch (ps) {
- case Qt::NoPen:
- case Qt::SolidLine:
- xStyle = LineSolid;
- break;
- case Qt::DashLine:
- dashes[0] = dash;
- dashes[1] = space;
- dash_len = 2;
- xStyle = LineOnOffDash;
- break;
- case Qt::DotLine:
- dashes[0] = dot;
- dashes[1] = space;
- dash_len = 2;
- xStyle = LineOnOffDash;
- break;
- case Qt::DashDotLine:
- dashes[0] = dash;
- dashes[1] = space;
- dashes[2] = dot;
- dashes[3] = space;
- dash_len = 4;
- xStyle = LineOnOffDash;
- break;
- case Qt::DashDotDotLine:
- dashes[0] = dash;
- dashes[1] = space;
- dashes[2] = dot;
- dashes[3] = space;
- dashes[4] = dot;
- dashes[5] = space;
- dash_len = 6;
- xStyle = LineOnOffDash;
- break;
- case Qt::CustomDashLine:
- d->has_custom_pen = true;
- break;
- }
-
- ulong mask = GCForeground | GCBackground | GCGraphicsExposures | GCLineWidth
- | GCCapStyle | GCJoinStyle | GCLineStyle;
- XGCValues vals;
- vals.graphics_exposures = false;
- if (d->pdev_depth == 1) {
- vals.foreground = qGray(pen.color().rgb()) > 127 ? 0 : 1;
- vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1;
- } else if (d->pdev->devType() == QInternal::Pixmap && d->pdev_depth == 32
- && X11->use_xrender) {
- vals.foreground = pen.color().rgba();
- vals.background = QColor(Qt::transparent).rgba();
- } else {
- QXcbColormap cmap = QXcbColormap::instance(d->scrn);
- vals.foreground = cmap.pixel(pen.color());
- vals.background = cmap.pixel(QColor(Qt::transparent));
- }
-
-
- vals.line_width = qRound(pen.widthF());
- vals.cap_style = cp;
- vals.join_style = jn;
- vals.line_style = xStyle;
-
- XChangeGC(d->dpy, d->gc, mask, &vals);
-
- if (dash_len) { // make dash list
- XSetDashes(d->dpy, d->gc, 0, dashes, dash_len);
- }
-
- if (!d->has_clipping) { // if clipping is set the paintevent clip region is merged with the clip region
- QRegion sysClip = d->use_sysclip ? systemClip() : QRegion();
- if (!sysClip.isEmpty())
- x11SetClipRegion(d->dpy, d->gc, 0, d->picture, sysClip);
- else
- x11ClearClipRegion(d->dpy, d->gc, 0, d->picture);
- }
-}
-
-void QX11PaintEngine::updateBrush(const QBrush &brush, const QPointF &origin)
-{
- Q_D(QX11PaintEngine);
- d->cbrush = brush;
- d->bg_origin = origin;
- d->adapted_brush_origin = false;
-#if QT_CONFIG(xrender)
- d->current_brush = 0;
-#endif
- if (d->opacity < 1.0) {
- QColor c = d->cbrush.color();
- c.setAlpha(qRound(c.alpha()*d->opacity));
- d->cbrush.setColor(c);
- }
-
- int s = FillSolid;
- int bs = d->cbrush.style();
- d->has_brush = (bs != Qt::NoBrush);
- d->has_pattern = bs >= Qt::Dense1Pattern && bs <= Qt::DiagCrossPattern;
- d->has_texture = bs == Qt::TexturePattern;
- d->has_alpha_brush = brush.color().alpha() != 255;
- d->has_alpha_texture = d->has_texture && d->cbrush.texture().hasAlphaChannel();
-
- ulong mask = GCForeground | GCBackground | GCGraphicsExposures
- | GCLineStyle | GCCapStyle | GCJoinStyle | GCFillStyle;
- XGCValues vals;
- vals.graphics_exposures = false;
- if (d->pdev_depth == 1) {
- vals.foreground = qGray(d->cbrush.color().rgb()) > 127 ? 0 : 1;
- vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1;
- } else if (X11->use_xrender && d->pdev->devType() == QInternal::Pixmap
- && d->pdev_depth == 32) {
- vals.foreground = d->cbrush.color().rgba();
- vals.background = QColor(Qt::transparent).rgba();
- } else {
- QXcbColormap cmap = QXcbColormap::instance(d->scrn);
- vals.foreground = cmap.pixel(d->cbrush.color());
- vals.background = cmap.pixel(QColor(Qt::transparent));
-
- if (!X11->use_xrender && d->has_brush && !d->has_pattern && !brush.isOpaque()) {
- QPixmap pattern = qt_patternForAlpha(brush.color().alpha(), d->scrn);
- mask |= GCStipple;
- vals.stipple = qt_x11PixmapHandle(pattern);
- s = FillStippled;
- d->adapted_brush_origin = true;
- }
- }
- vals.cap_style = CapButt;
- vals.join_style = JoinMiter;
- vals.line_style = LineSolid;
-
- if (d->has_pattern || d->has_texture) {
- if (bs == Qt::TexturePattern) {
- d->brush_pm = qt_toX11Pixmap(d->cbrush.texture());
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- XRenderPictureAttributes attrs;
- attrs.repeat = true;
- XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->brush_pm), CPRepeat, &attrs);
- QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle());
- if (data->mask_picture)
- XRenderChangePicture(d->dpy, data->mask_picture, CPRepeat, &attrs);
- }
-#endif
- } else {
- d->brush_pm = qt_toX11Pixmap(qt_pixmapForBrush(bs, true));
- }
- qt_x11SetScreen(d->brush_pm, d->scrn);
- if (d->brush_pm.depth() == 1) {
- mask |= GCStipple;
- vals.stipple = qt_x11PixmapHandle(d->brush_pm);
- s = FillStippled;
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- d->bitmap_texture = QPixmap(d->brush_pm.size());
- d->bitmap_texture.fill(Qt::transparent);
- d->bitmap_texture = qt_toX11Pixmap(d->bitmap_texture);
- qt_x11SetScreen(d->bitmap_texture, d->scrn);
-
- ::Picture src = X11->getSolidFill(d->scrn, d->cbrush.color());
- XRenderComposite(d->dpy, PictOpSrc, src, qt_x11PictureHandle(d->brush_pm),
- qt_x11PictureHandle(d->bitmap_texture),
- 0, 0, d->brush_pm.width(), d->brush_pm.height(),
- 0, 0, d->brush_pm.width(), d->brush_pm.height());
-
- XRenderPictureAttributes attrs;
- attrs.repeat = true;
- XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->bitmap_texture), CPRepeat, &attrs);
-
- d->current_brush = qt_x11PictureHandle(d->bitmap_texture);
- }
-#endif
- } else {
- mask |= GCTile;
-#if QT_CONFIG(xrender)
- if (d->pdev_depth == 32 && d->brush_pm.depth() != 32) {
- d->brush_pm.detach();
- QX11PlatformPixmap *brushData = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle());
- brushData->convertToARGB32();
- }
-#endif
- vals.tile = (d->brush_pm.depth() == d->pdev_depth
- ? qt_x11PixmapHandle(d->brush_pm)
- : static_cast<QX11PlatformPixmap*>(d->brush_pm.handle())->x11ConvertToDefaultDepth());
- s = FillTiled;
-#if QT_CONFIG(xrender)
- d->current_brush = qt_x11PictureHandle(d->cbrush.texture());
-#endif
- }
-
- mask |= GCTileStipXOrigin | GCTileStipYOrigin;
- vals.ts_x_origin = qRound(origin.x());
- vals.ts_y_origin = qRound(origin.y());
- }
-#if QT_CONFIG(xrender)
- else if (d->has_alpha_brush) {
- d->current_brush = X11->getSolidFill(d->scrn, d->cbrush.color());
- }
-#endif
-
- vals.fill_style = s;
- XChangeGC(d->dpy, d->gc_brush, mask, &vals);
- if (!d->has_clipping) {
- QRegion sysClip = d->use_sysclip ? systemClip() : QRegion();
- if (!sysClip.isEmpty())
- x11SetClipRegion(d->dpy, d->gc_brush, 0, d->picture, sysClip);
- else
- x11ClearClipRegion(d->dpy, d->gc_brush, 0, d->picture);
- }
-}
-
-void QX11PaintEngine::drawEllipse(const QRectF &rect)
-{
- QRect aligned = rect.toAlignedRect();
- if (aligned == rect)
- drawEllipse(aligned);
- else
- QPaintEngine::drawEllipse(rect);
-}
-
-void QX11PaintEngine::drawEllipse(const QRect &rect)
-{
- if (rect.isEmpty()) {
- drawRects(&rect, 1);
- return;
- }
-
- Q_D(QX11PaintEngine);
- QRect devclip(SHRT_MIN, SHRT_MIN, SHRT_MAX*2 - 1, SHRT_MAX*2 - 1);
- QRect r(rect);
- if (d->txop < QTransform::TxRotate) {
- r = d->matrix.mapRect(rect);
- } else if (d->txop == QTransform::TxRotate && rect.width() == rect.height()) {
- QPainterPath path;
- path.addEllipse(rect);
- r = d->matrix.map(path).boundingRect().toRect();
- }
-
- if (d->has_alpha_brush || d->has_alpha_pen || d->has_custom_pen || (d->render_hints & QPainter::Antialiasing)
- || d->has_alpha_texture || devclip.intersected(r) != r
- || (d->has_complex_xform
- && !(d->has_non_scaling_xform && rect.width() == rect.height())))
- {
- QPainterPath path;
- path.addEllipse(rect);
- drawPath(path);
- return;
- }
-
- int x = r.x();
- int y = r.y();
- int w = r.width();
- int h = r.height();
- if (w < 1 || h < 1)
- return;
- if (w == 1 && h == 1) {
- XDrawPoint(d->dpy, d->hd, d->has_pen ? d->gc : d->gc_brush, x, y);
- return;
- }
- d->setupAdaptedOrigin(rect.topLeft());
- if (d->has_brush) { // draw filled ellipse
- XFillArc(d->dpy, d->hd, d->gc_brush, x, y, w, h, 0, 360*64);
- if (!d->has_pen) // make smoother outline
- XDrawArc(d->dpy, d->hd, d->gc_brush, x, y, w-1, h-1, 0, 360*64);
- }
- if (d->has_pen) // draw outline
- XDrawArc(d->dpy, d->hd, d->gc, x, y, w, h, 0, 360*64);
- d->resetAdaptedOrigin();
-}
-
-
-
-void QX11PaintEnginePrivate::fillPolygon_translated(const QPointF *polygonPoints, int pointCount,
- QX11PaintEnginePrivate::GCMode gcMode,
- QPaintEngine::PolygonDrawMode mode)
-{
-
- QVarLengthArray<QPointF> translated_points(pointCount);
- QPointF offset(matrix.dx(), matrix.dy());
-
- qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0;
- if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing))
- offset += QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta);
-
- for (int i = 0; i < pointCount; ++i) {
- translated_points[i] = polygonPoints[i] + offset;
-
- translated_points[i].rx() = qRound(translated_points[i].x()) + offs;
- translated_points[i].ry() = qRound(translated_points[i].y()) + offs;
- }
-
- fillPolygon_dev(translated_points.data(), pointCount, gcMode, mode);
-}
-
-#if QT_CONFIG(xrender)
-static void qt_XRenderCompositeTrapezoids(Display *dpy,
- int op,
- Picture src,
- Picture dst,
- _Xconst XRenderPictFormat *maskFormat,
- int xSrc,
- int ySrc,
- const XTrapezoid *traps, int size)
-{
- const int MAX_TRAPS = 50000;
- while (size) {
- int to_draw = size;
- if (to_draw > MAX_TRAPS)
- to_draw = MAX_TRAPS;
- XRenderCompositeTrapezoids(dpy, op, src, dst,
- maskFormat,
- xSrc, ySrc,
- traps, to_draw);
- size -= to_draw;
- traps += to_draw;
- }
-}
-#endif
-
-void QX11PaintEnginePrivate::fillPolygon_dev(const QPointF *polygonPoints, int pointCount,
- QX11PaintEnginePrivate::GCMode gcMode,
- QPaintEngine::PolygonDrawMode mode)
-{
- Q_Q(QX11PaintEngine);
-
- int clippedCount = 0;
- qt_float_point *clippedPoints = 0;
-
-#if QT_CONFIG(xrender)
- //can change if we switch to pen if gcMode != BrushGC
- bool has_fill_texture = has_texture;
- bool has_fill_pattern = has_pattern;
- ::Picture src;
-#endif
- QBrush fill;
- GC fill_gc;
- if (gcMode == BrushGC) {
- fill = cbrush;
- fill_gc = gc_brush;
-#if QT_CONFIG(xrender)
- if (current_brush)
- src = current_brush;
- else
- src = X11->getSolidFill(scrn, fill.color());
-#endif
- } else {
- fill = QBrush(cpen.brush());
- fill_gc = gc;
-#if QT_CONFIG(xrender)
- //we use the pens brush
- has_fill_texture = (fill.style() == Qt::TexturePattern);
- has_fill_pattern = (fill.style() >= Qt::Dense1Pattern && fill.style() <= Qt::DiagCrossPattern);
- if (has_fill_texture)
- src = qt_x11PictureHandle(fill.texture());
- else if (has_fill_pattern)
- src = getPatternFill(scrn, fill);
- else
- src = X11->getSolidFill(scrn, fill.color());
-#endif
- }
-
- polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount,
- &clippedPoints, &clippedCount);
-
-#if QT_CONFIG(xrender)
- bool solid_fill = fill.color().alpha() == 255;
- if (has_fill_texture && fill.texture().depth() == 1 && solid_fill) {
- has_fill_texture = false;
- has_fill_pattern = true;
- }
-
- bool antialias = render_hints & QPainter::Antialiasing;
-
- if (X11->use_xrender
- && picture
- && !has_fill_pattern
- && (clippedCount > 0)
- && (fill.style() != Qt::NoBrush)
- && ((has_fill_texture && fill.texture().hasAlpha()) || antialias || !solid_fill || has_alpha_pen != has_alpha_brush))
- {
- tessellator->tessellate((QPointF *)clippedPoints, clippedCount,
- mode == QPaintEngine::WindingMode);
- if (tessellator->size > 0) {
- XRenderPictureAttributes attrs;
- attrs.poly_edge = antialias ? PolyEdgeSmooth : PolyEdgeSharp;
- XRenderChangePicture(dpy, picture, CPPolyEdge, &attrs);
- int x_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.x) - bg_origin.x());
- int y_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.y) - bg_origin.y());
- qt_XRenderCompositeTrapezoids(dpy, composition_mode, src, picture,
- antialias
- ? XRenderFindStandardFormat(dpy, PictStandardA8)
- : XRenderFindStandardFormat(dpy, PictStandardA1),
- x_offset, y_offset,
- tessellator->traps, tessellator->size);
- tessellator->done();
- }
- } else
-#endif
- if (fill.style() != Qt::NoBrush) {
- if (clippedCount > 200000) {
- QPolygon poly;
- for (int i = 0; i < clippedCount; ++i)
- poly << QPoint(qFloor(clippedPoints[i].x), qFloor(clippedPoints[i].y));
-
- const QRect bounds = poly.boundingRect();
- const QRect aligned = bounds
- & QRect(QPoint(), QSize(pdev->width(), pdev->height()));
-
- QImage img(aligned.size(), QImage::Format_ARGB32_Premultiplied);
- img.fill(0);
-
- QPainter painter(&img);
- painter.translate(-aligned.x(), -aligned.y());
- painter.setPen(Qt::NoPen);
- painter.setBrush(fill);
- if (gcMode == BrushGC)
- painter.setBrushOrigin(q->painter()->brushOriginF());
- painter.drawPolygon(poly);
- painter.end();
-
- q->drawImage(aligned, img, img.rect(), Qt::AutoColor);
- } else if (clippedCount > 0) {
- QVarLengthArray<XPoint> xpoints(clippedCount);
- for (int i = 0; i < clippedCount; ++i) {
- xpoints[i].x = qFloor(clippedPoints[i].x);
- xpoints[i].y = qFloor(clippedPoints[i].y);
- }
- if (mode == QPaintEngine::WindingMode)
- XSetFillRule(dpy, fill_gc, WindingRule);
- setupAdaptedOrigin(QPoint(xpoints[0].x, xpoints[0].y));
- XFillPolygon(dpy, hd, fill_gc,
- xpoints.data(), clippedCount,
- mode == QPaintEngine::ConvexMode ? Convex : Complex, CoordModeOrigin);
- resetAdaptedOrigin();
- if (mode == QPaintEngine::WindingMode)
- XSetFillRule(dpy, fill_gc, EvenOddRule);
- }
- }
-}
-
-void QX11PaintEnginePrivate::strokePolygon_translated(const QPointF *polygonPoints, int pointCount, bool close)
-{
- QVarLengthArray<QPointF> translated_points(pointCount);
- QPointF offset(matrix.dx(), matrix.dy());
- for (int i = 0; i < pointCount; ++i)
- translated_points[i] = polygonPoints[i] + offset;
- strokePolygon_dev(translated_points.data(), pointCount, close);
-}
-
-void QX11PaintEnginePrivate::strokePolygon_dev(const QPointF *polygonPoints, int pointCount, bool close)
-{
- int clippedCount = 0;
- qt_float_point *clippedPoints = 0;
- polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount,
- &clippedPoints, &clippedCount, close);
-
- if (clippedCount > 0) {
- QVarLengthArray<XPoint> xpoints(clippedCount);
- for (int i = 0; i < clippedCount; ++i) {
- xpoints[i].x = qRound(clippedPoints[i].x + aliasedCoordinateDelta);
- xpoints[i].y = qRound(clippedPoints[i].y + aliasedCoordinateDelta);
- }
- uint numberPoints = qMin(clippedCount, xlibMaxLinePoints);
- XPoint *pts = xpoints.data();
- XDrawLines(dpy, hd, gc, pts, numberPoints, CoordModeOrigin);
- pts += numberPoints;
- clippedCount -= numberPoints;
- numberPoints = qMin(clippedCount, xlibMaxLinePoints-1);
- while (clippedCount) {
- XDrawLines(dpy, hd, gc, pts-1, numberPoints+1, CoordModeOrigin);
- pts += numberPoints;
- clippedCount -= numberPoints;
- numberPoints = qMin(clippedCount, xlibMaxLinePoints-1);
- }
- }
-}
-
-void QX11PaintEngine::drawPolygon(const QPointF *polygonPoints, int pointCount, PolygonDrawMode mode)
-{
- Q_D(QX11PaintEngine);
-
- if (d->use_path_fallback) {
- QPainterPath path(polygonPoints[0]);
- for (int i = 1; i < pointCount; ++i)
- path.lineTo(polygonPoints[i]);
- if (mode == PolylineMode) {
- QBrush oldBrush = d->cbrush;
- d->cbrush = QBrush(Qt::NoBrush);
- path.setFillRule(Qt::WindingFill);
- drawPath(path);
- d->cbrush = oldBrush;
- } else {
- path.setFillRule(mode == OddEvenMode ? Qt::OddEvenFill : Qt::WindingFill);
- path.closeSubpath();
- drawPath(path);
- }
- return;
- }
- if (mode != PolylineMode && d->has_brush)
- d->fillPolygon_translated(polygonPoints, pointCount, QX11PaintEnginePrivate::BrushGC, mode);
-
- if (d->has_pen)
- d->strokePolygon_translated(polygonPoints, pointCount, mode != PolylineMode);
-}
-
-
-void QX11PaintEnginePrivate::fillPath(const QPainterPath &path, QX11PaintEnginePrivate::GCMode gc_mode, bool transform)
-{
- qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0;
-
- QPainterPath clippedPath;
- QPainterPath clipPath;
- clipPath.addRect(polygonClipper.boundingRect());
-
- if (transform)
- clippedPath = (path*matrix).intersected(clipPath);
- else
- clippedPath = path.intersected(clipPath);
-
- QList<QPolygonF> polys = clippedPath.toFillPolygons();
- for (int i = 0; i < polys.size(); ++i) {
- QVarLengthArray<QPointF> translated_points(polys.at(i).size());
-
- for (int j = 0; j < polys.at(i).size(); ++j) {
- translated_points[j] = polys.at(i).at(j);
- if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) {
- translated_points[j].rx() = qRound(translated_points[j].rx() + aliasedCoordinateDelta) + offs;
- translated_points[j].ry() = qRound(translated_points[j].ry() + aliasedCoordinateDelta) + offs;
- }
- }
-
- fillPolygon_dev(translated_points.data(), polys.at(i).size(), gc_mode,
- path.fillRule() == Qt::OddEvenFill ? QPaintEngine::OddEvenMode : QPaintEngine::WindingMode);
- }
-}
-
-void QX11PaintEngine::drawPath(const QPainterPath &path)
-{
- Q_D(QX11PaintEngine);
- if (path.isEmpty())
- return;
-
- if (d->has_brush)
- d->fillPath(path, QX11PaintEnginePrivate::BrushGC, true);
- if (d->has_pen
- && ((X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing)))
- || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate
- && !d->has_non_scaling_xform)
- || (d->cpen.style() == Qt::CustomDashLine))) {
- QPainterPathStroker stroker;
- if (d->cpen.style() == Qt::CustomDashLine) {
- stroker.setDashPattern(d->cpen.dashPattern());
- stroker.setDashOffset(d->cpen.dashOffset());
- } else {
- stroker.setDashPattern(d->cpen.style());
- }
- stroker.setCapStyle(d->cpen.capStyle());
- stroker.setJoinStyle(d->cpen.joinStyle());
- QPainterPath stroke;
- qreal width = d->cpen.widthF();
- QPolygonF poly;
- QRectF deviceRect(0, 0, d->pdev->width(), d->pdev->height());
- // necessary to get aliased alphablended primitives to be drawn correctly
- if (d->isCosmeticPen() || d->has_scaling_xform) {
- if (d->isCosmeticPen())
- stroker.setWidth(width == 0 ? 1 : width);
- else
- stroker.setWidth(width * d->xform_scale);
- stroker.d_ptr->stroker.setClipRect(deviceRect);
- stroke = stroker.createStroke(path * d->matrix);
- if (stroke.isEmpty())
- return;
- stroke.setFillRule(Qt::WindingFill);
- d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, false);
- } else {
- stroker.setWidth(width);
- stroker.d_ptr->stroker.setClipRect(d->matrix.inverted().mapRect(deviceRect));
- stroke = stroker.createStroke(path);
- if (stroke.isEmpty())
- return;
- stroke.setFillRule(Qt::WindingFill);
- d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, true);
- }
- } else if (d->has_pen) {
- // if we have a cosmetic pen - use XDrawLine() for speed
- QList<QPolygonF> polys = path.toSubpathPolygons(d->matrix);
- for (int i = 0; i < polys.size(); ++i)
- d->strokePolygon_dev(polys.at(i).data(), polys.at(i).size(), false);
- }
-}
-
-Q_GUI_EXPORT void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image,
- Drawable hd, GC gc, Display *dpy, Visual *visual, int depth)
-{
- Q_ASSERT(image.format() == QImage::Format_RGB32);
- Q_ASSERT(image.depth() == 32);
-
- XImage *xi;
- // Note: this code assumes either RGB or BGR, 8 bpc server layouts
- const uint red_mask = (uint) visual->red_mask;
- bool bgr_layout = (red_mask == 0xff);
-
- const int w = rect.width();
- const int h = rect.height();
-
- QImage im;
- int image_byte_order = ImageByteOrder(QXcbX11Info::display());
- if ((QSysInfo::ByteOrder == QSysInfo::BigEndian && ((image_byte_order == LSBFirst) || bgr_layout))
- || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)
- || (image_byte_order == LSBFirst && bgr_layout))
- {
- im = image.copy(rect);
- const qsizetype iw = im.bytesPerLine() / 4;
- uint *data = (uint *)im.bits();
- for (int i=0; i < h; i++) {
- uint *p = data;
- uint *end = p + w;
- if (bgr_layout && image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
- while (p < end) {
- *p = ((*p << 8) & 0xffffff00) | ((*p >> 24) & 0x000000ff);
- p++;
- }
- } else if ((image_byte_order == LSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian)
- || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) {
- while (p < end) {
- *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000)
- | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff);
- p++;
- }
- } else if ((image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian)
- || (image_byte_order == LSBFirst && bgr_layout))
- {
- while (p < end) {
- *p = ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff)
- | ((*p ) & 0xff00ff00);
- p++;
- }
- }
- data += iw;
- }
- xi = XCreateImage(dpy, visual, depth, ZPixmap,
- 0, (char *) im.bits(), w, h, 32, im.bytesPerLine());
- } else {
- xi = XCreateImage(dpy, visual, depth, ZPixmap,
- 0, (char *) image.scanLine(rect.y())+rect.x()*sizeof(uint), w, h, 32, image.bytesPerLine());
- }
- XPutImage(dpy, hd, gc, xi, 0, 0, pos.x(), pos.y(), w, h);
- xi->data = 0; // QImage owns these bits
- XDestroyImage(xi);
-}
-
-void QX11PaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags)
-{
- Q_D(QX11PaintEngine);
-
- if (image.format() == QImage::Format_RGB32
- && d->pdev_depth >= 24 && image.depth() == 32
- && r.size() == sr.size())
- {
- int sx = qRound(sr.x());
- int sy = qRound(sr.y());
- int x = qRound(r.x());
- int y = qRound(r.y());
- int w = qRound(r.width());
- int h = qRound(r.height());
-
- qt_x11_drawImage(QRect(sx, sy, w, h), QPoint(x, y), image, d->hd, d->gc, d->dpy,
- (Visual *)d->xinfo->visual(), d->pdev_depth);
- } else {
- QPaintEngine::drawImage(r, image, sr, flags);
- }
-}
-
-void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRectF &_sr)
-{
- Q_D(QX11PaintEngine);
- QRectF sr = _sr;
- int x = qRound(r.x());
- int y = qRound(r.y());
- int sx = qRound(sr.x());
- int sy = qRound(sr.y());
- int sw = qRound(sr.width());
- int sh = qRound(sr.height());
-
- QPixmap pixmap = qt_toX11Pixmap(px);
- if (pixmap.isNull())
- return;
-
- if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen())
- || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) {
- qt_x11SetScreen(pixmap, d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display));
- }
-
- qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen());
-
-#if QT_CONFIG(xrender)
- ::Picture src_pict = qt_x11PictureHandle(pixmap);
- if (src_pict && d->picture) {
- const int pDepth = pixmap.depth();
- if (pDepth == 1 && (d->has_alpha_pen)) {
- qt_render_bitmap(d->dpy, d->scrn, src_pict, d->picture,
- sx, sy, x, y, sw, sh, d->cpen);
- return;
- } else if (pDepth != 1 && (pDepth == 32 || pDepth != d->pdev_depth)) {
- XRenderComposite(d->dpy, d->composition_mode,
- src_pict, 0, d->picture, sx, sy, 0, 0, x, y, sw, sh);
- return;
- }
- }
-#endif
-
- bool mono_src = pixmap.depth() == 1;
- bool mono_dst = d->pdev_depth == 1;
- bool restore_clip = false;
-
- if (static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) { // pixmap has a mask
- QBitmap comb(sw, sh);
- GC cgc = XCreateGC(d->dpy, qt_x11PixmapHandle(comb), 0, 0);
- XSetForeground(d->dpy, cgc, 0);
- XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh);
- XSetBackground(d->dpy, cgc, 0);
- XSetForeground(d->dpy, cgc, 1);
- if (!d->crgn.isEmpty()) {
- QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn);
- XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted);
- } else if (d->has_clipping) {
- XSetClipRectangles(d->dpy, cgc, 0, 0, 0, 0, Unsorted);
- }
- XSetFillStyle(d->dpy, cgc, FillOpaqueStippled);
- XSetTSOrigin(d->dpy, cgc, -sx, -sy);
- XSetStipple(d->dpy, cgc,
- static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask);
- XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh);
- XFreeGC(d->dpy, cgc);
-
- XSetClipOrigin(d->dpy, d->gc, x, y);
- XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(comb));
- restore_clip = true;
- }
-
- if (mono_src) {
- if (!d->crgn.isEmpty()) {
- Pixmap comb = XCreatePixmap(d->dpy, d->hd, sw, sh, 1);
- GC cgc = XCreateGC(d->dpy, comb, 0, 0);
- XSetForeground(d->dpy, cgc, 0);
- XFillRectangle(d->dpy, comb, cgc, 0, 0, sw, sh);
- QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn);
- XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted);
- XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), comb, cgc, sx, sy, sw, sh, 0, 0);
- XFreeGC(d->dpy, cgc);
-
- XSetClipMask(d->dpy, d->gc, comb);
- XSetClipOrigin(d->dpy, d->gc, x, y);
- XFreePixmap(d->dpy, comb);
- } else {
- XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(pixmap));
- XSetClipOrigin(d->dpy, d->gc, x - sx, y - sy);
- }
-
- if (mono_dst) {
- XSetForeground(d->dpy, d->gc, qGray(d->cpen.color().rgb()) > 127 ? 0 : 1);
- } else {
- QXcbColormap cmap = QXcbColormap::instance(d->scrn);
- XSetForeground(d->dpy, d->gc, cmap.pixel(d->cpen.color()));
- }
- XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh);
- restore_clip = true;
- } else if (mono_dst && !mono_src) {
- QBitmap bitmap = QBitmap::fromPixmap(pixmap);
- XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
- } else {
- XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
- }
-
- if (d->pdev->devType() == QInternal::Pixmap) {
- const QPixmap *px = static_cast<const QPixmap*>(d->pdev);
- Pixmap src_mask = static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask;
- Pixmap dst_mask = static_cast<QX11PlatformPixmap*>(px->handle())->x11_mask;
- if (dst_mask) {
- GC cgc = XCreateGC(d->dpy, dst_mask, 0, 0);
- XSetClipOrigin(d->dpy, cgc, x, y);
- XSetClipMask(d->dpy, cgc, src_mask);
- if (src_mask) { // copy src mask into dst mask
- XCopyArea(d->dpy, src_mask, dst_mask, cgc, sx, sy, sw, sh, x, y);
- } else { // no src mask, but make sure the area copied is opaque in dest
- XSetBackground(d->dpy, cgc, 0);
- XSetForeground(d->dpy, cgc, 1);
- XFillRectangle(d->dpy, dst_mask, cgc, x, y, sw, sh);
- }
- XFreeGC(d->dpy, cgc);
- }
- }
-
- if (restore_clip) {
- XSetClipOrigin(d->dpy, d->gc, 0, 0);
- QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn);
- if (rects.isEmpty())
- XSetClipMask(d->dpy, d->gc, XNone);
- else
- XSetClipRectangles(d->dpy, d->gc, 0, 0, rects.data(), rects.size(), Unsorted);
- }
-}
-
-void QX11PaintEngine::updateMatrix(const QTransform &mtx)
-{
- Q_D(QX11PaintEngine);
- d->txop = mtx.type();
- d->matrix = mtx;
-
- d->has_complex_xform = (d->txop > QTransform::TxTranslate);
-
- extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale);
- bool scaling = qt_scaleForTransform(d->matrix, &d->xform_scale);
- d->has_scaling_xform = scaling && d->xform_scale != 1.0;
- d->has_non_scaling_xform = scaling && d->xform_scale == 1.0;
-}
-
-/*
- NB! the clip region is expected to be in dev coordinates
-*/
-void QX11PaintEngine::updateClipRegion_dev(const QRegion &clipRegion, Qt::ClipOperation op)
-{
- Q_D(QX11PaintEngine);
- QRegion sysClip = d->use_sysclip ? systemClip() : QRegion();
- if (op == Qt::NoClip) {
- d->has_clipping = false;
- d->crgn = sysClip;
- if (!sysClip.isEmpty()) {
- x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, sysClip);
- } else {
- x11ClearClipRegion(d->dpy, d->gc, d->gc_brush, d->picture);
- }
- return;
- }
-
- switch (op) {
- case Qt::IntersectClip:
- if (d->has_clipping) {
- d->crgn &= clipRegion;
- break;
- }
- // fall through
- case Qt::ReplaceClip:
- if (!sysClip.isEmpty())
- d->crgn = clipRegion.intersected(sysClip);
- else
- d->crgn = clipRegion;
- break;
-// case Qt::UniteClip:
-// d->crgn |= clipRegion;
-// if (!sysClip.isEmpty())
-// d->crgn = d->crgn.intersected(sysClip);
-// break;
- default:
- break;
- }
- d->has_clipping = true;
- x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, d->crgn);
-}
-
-void QX11PaintEngine::updateFont(const QFont &)
-{
-}
-
-Drawable QX11PaintEngine::handle() const
-{
- Q_D(const QX11PaintEngine);
- Q_ASSERT(isActive());
- Q_ASSERT(d->hd);
- return d->hd;
-}
-
-extern void qt_draw_tile(QPaintEngine *, qreal, qreal, qreal, qreal, const QPixmap &,
- qreal, qreal);
-
-void QX11PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p)
-{
- int x = qRound(r.x());
- int y = qRound(r.y());
- int w = qRound(r.width());
- int h = qRound(r.height());
- int sx = qRound(p.x());
- int sy = qRound(p.y());
-
- bool mono_src = pixmap.depth() == 1;
- Q_D(QX11PaintEngine);
-
- if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen())
- || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) {
- QPixmap* p = const_cast<QPixmap *>(&pixmap);
- qt_x11SetScreen(*p, d->xinfo ? d->xinfo->screen() : DefaultScreen(QXcbX11Info::display()));
- }
-
- qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen());
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender && d->picture && qt_x11PictureHandle(pixmap)) {
- const int numTiles = (w / pixmap.width()) * (h / pixmap.height());
- if (numTiles < 100) {
- // this is essentially qt_draw_tile(), inlined for
- // the XRenderComposite call
- int yPos, xPos, drawH, drawW, yOff, xOff;
- yPos = y;
- yOff = sy;
- while (yPos < y + h) {
- drawH = pixmap.height() - yOff; // Cropping first row
- if (yPos + drawH > y + h) // Cropping last row
- drawH = y + h - yPos;
- xPos = x;
- xOff = sx;
- while (xPos < x + w) {
- drawW = pixmap.width() - xOff; // Cropping first column
- if (xPos + drawW > x + w) // Cropping last column
- drawW = x + w - xPos;
- if (mono_src) {
- qt_render_bitmap(d->dpy, d->scrn, qt_x11PictureHandle(pixmap), d->picture,
- xOff, yOff, xPos, yPos, drawW, drawH, d->cpen);
- } else {
- XRenderComposite(d->dpy, d->composition_mode,
- qt_x11PictureHandle(pixmap), XNone, d->picture,
- xOff, yOff, 0, 0, xPos, yPos, drawW, drawH);
- }
- xPos += drawW;
- xOff = 0;
- }
- yPos += drawH;
- yOff = 0;
- }
- } else {
- w = qMin(w, d->pdev->width() - x);
- h = qMin(h, d->pdev->height() - y);
- if (w <= 0 || h <= 0)
- return;
-
- const int pw = w + sx;
- const int ph = h + sy;
- QPixmap pm(pw, ph);
- if (pixmap.hasAlpha() || mono_src)
- pm.fill(Qt::transparent);
-
- const int mode = pixmap.hasAlpha() ? PictOpOver : PictOpSrc;
- const ::Picture pmPicture = qt_x11PictureHandle(pm);
-
- // first tile
- XRenderComposite(d->dpy, mode,
- qt_x11PictureHandle(pixmap), XNone, pmPicture,
- 0, 0, 0, 0, 0, 0, qMin(pw, pixmap.width()), qMin(ph, pixmap.height()));
-
- // first row of tiles
- int xPos = pixmap.width();
- const int sh = qMin(ph, pixmap.height());
- while (xPos < pw) {
- const int sw = qMin(xPos, pw - xPos);
- XRenderComposite(d->dpy, mode,
- pmPicture, XNone, pmPicture,
- 0, 0, 0, 0, xPos, 0, sw, sh);
- xPos *= 2;
- }
-
- // remaining rows
- int yPos = pixmap.height();
- const int sw = pw;
- while (yPos < ph) {
- const int sh = qMin(yPos, ph - yPos);
- XRenderComposite(d->dpy, mode,
- pmPicture, XNone, pmPicture,
- 0, 0, 0, 0, 0, yPos, sw, sh);
- yPos *= 2;
- }
-
- // composite
- if (mono_src)
- qt_render_bitmap(d->dpy, d->scrn, pmPicture, d->picture,
- sx, sy, x, y, w, h, d->cpen);
- else
- XRenderComposite(d->dpy, d->composition_mode,
- pmPicture, XNone, d->picture,
- sx, sy, 0, 0, x, y, w, h);
- }
- } else
-#endif // QT_CONFIG(xrender)
- if (pixmap.depth() > 1 && !static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) {
- XSetTile(d->dpy, d->gc, qt_x11PixmapHandle(pixmap));
- XSetFillStyle(d->dpy, d->gc, FillTiled);
- XSetTSOrigin(d->dpy, d->gc, x-sx, y-sy);
- XFillRectangle(d->dpy, d->hd, d->gc, x, y, w, h);
- XSetTSOrigin(d->dpy, d->gc, 0, 0);
- XSetFillStyle(d->dpy, d->gc, FillSolid);
- } else {
- qt_draw_tile(this, x, y, w, h, pixmap, sx, sy);
- }
-}
-
-bool QX11PaintEngine::drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti)
-{
-#if QT_CONFIG(xrender)
- Q_D(QX11PaintEngine);
- Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype);
-
- if (!X11->use_xrender)
- return false;
-
- QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine);
- QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform);
-
- if (!set || set->outline_drawing)
- return false;
-
- QFontEngine::GlyphFormat glyphFormat = QXRenderGlyphCache::glyphFormatForDepth(ft, d->pdev_depth);
-
- QXRenderGlyphCache *cache = static_cast<QXRenderGlyphCache *>(ft->glyphCache(set, glyphFormat, transform));
- if (!cache) {
- cache = new QXRenderGlyphCache(QXcbX11Info(), glyphFormat, transform);
- ft->setGlyphCache(set, cache);
- }
-
- return cache->draw(X11->getSolidFill(d->scrn, d->cpen.color()), d->picture, transform, ti);
-#else // !QT_CONFIG(xrender)
- return false;
-#endif // QT_CONFIG(xrender)
-}
-
-void QX11PaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem)
-{
- Q_D(QX11PaintEngine);
- const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem);
-
- switch (ti.fontEngine->type()) {
- case QFontEngine::TestFontEngine:
- case QFontEngine::Box:
- d->drawBoxTextItem(p, ti);
- break;
-#if QT_CONFIG(fontconfig)
- case QFontEngine::Freetype:
- drawFreetype(p, ti);
- break;
-#endif
- default:
- Q_ASSERT(false);
- }
-}
-
-#if QT_CONFIG(fontconfig)
-static bool path_for_glyphs(QPainterPath *path,
- const QVarLengthArray<glyph_t> &glyphs,
- const QVarLengthArray<QFixedPoint> &positions,
- const QFontEngineFT *ft)
-{
- bool result = true;
- *path = QPainterPath();
- path->setFillRule(Qt::WindingFill);
- ft->lockFace();
- int i = 0;
- while (i < glyphs.size()) {
- QFontEngineFT::Glyph *glyph = ft->loadGlyph(glyphs[i], QFixedPoint(), QFontEngineFT::Format_Mono);
- // #### fix case where we don't get a glyph
- if (!glyph || glyph->format != QFontEngineFT::Format_Mono) {
- result = false;
- break;
- }
-
- int n = 0;
- int h = glyph->height;
- int xp = qRound(positions[i].x);
- int yp = qRound(positions[i].y);
-
- xp += glyph->x;
- yp += -glyph->y + glyph->height;
- int pitch = ((glyph->width + 31) & ~31) >> 3;
-
- uchar *src = glyph->data;
- while (h--) {
- for (int x = 0; x < glyph->width; ++x) {
- bool set = src[x >> 3] & (0x80 >> (x & 7));
- if (set) {
- QRect r(xp + x, yp - h, 1, 1);
- while (x+1 < glyph->width && src[(x+1) >> 3] & (0x80 >> ((x+1) & 7))) {
- ++x;
- r.setRight(r.right()+1);
- }
-
- path->addRect(r);
- ++n;
- }
- }
- src += pitch;
- }
- ++i;
- }
- ft->unlockFace();
- return result;
-}
-
-void QX11PaintEngine::drawFreetype(const QPointF &p, const QTextItemInt &ti)
-{
- Q_D(QX11PaintEngine);
-
- if (!ti.glyphs.numGlyphs)
- return;
-
- if (!d->cpen.isSolid()) {
- QPaintEngine::drawTextItem(p, ti);
- return;
- }
-
- const bool xrenderPath = (X11->use_xrender
- && !(d->pdev->devType() == QInternal::Pixmap
- && static_cast<const QPixmap *>(d->pdev)->handle()->pixelType() == QPlatformPixmap::BitmapType));
-
- if (xrenderPath) {
- QTransform transform = d->matrix;
- transform.translate(p.x(), p.y());
-
- if (drawCachedGlyphs(transform, ti))
- return;
- }
-
- QTransform transform;
- transform.translate(p.x(), p.y());
-
- QVarLengthArray<QFixedPoint> positions;
- QVarLengthArray<glyph_t> glyphs;
- ti.fontEngine->getGlyphPositions(ti.glyphs, transform, ti.flags, glyphs, positions);
-
- if (glyphs.count() == 0)
- return;
-
- QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine);
- QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform);
- QPainterPath path;
-
- if (!set || set->outline_drawing || !path_for_glyphs(&path, glyphs, positions, ft)) {
- QPaintEngine::drawTextItem(p, ti);
- return;
- }
-
- if (path.elementCount() <= 1)
- return;
-
- Q_ASSERT((path.elementCount() % 5) == 0);
- if (d->txop >= QTransform::TxScale) {
- painter()->save();
- painter()->setBrush(d->cpen.brush());
- painter()->setPen(Qt::NoPen);
- painter()->drawPath(path);
- painter()->restore();
- return;
- }
-
- const int rectcount = 256;
- XRectangle rects[rectcount];
- int num_rects = 0;
-
- QPoint delta(qRound(d->matrix.dx()), qRound(d->matrix.dy()));
- QRect clip(d->polygonClipper.boundingRect());
- for (int i=0; i < path.elementCount(); i+=5) {
- int x = qRound(path.elementAt(i).x);
- int y = qRound(path.elementAt(i).y);
- int w = qRound(path.elementAt(i+1).x) - x;
- int h = qRound(path.elementAt(i+2).y) - y;
-
- QRect rect = QRect(x + delta.x(), y + delta.y(), w, h);
- rect = rect.intersected(clip);
- if (rect.isEmpty())
- continue;
-
- rects[num_rects].x = short(rect.x());
- rects[num_rects].y = short(rect.y());
- rects[num_rects].width = ushort(rect.width());
- rects[num_rects].height = ushort(rect.height());
- ++num_rects;
- if (num_rects == rectcount) {
- XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects);
- num_rects = 0;
- }
- }
- if (num_rects > 0)
- XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects);
-}
-#endif // QT_CONFIG(fontconfig)
-
-#if QT_CONFIG(xrender)
-QXRenderGlyphCache::QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix)
- : QFontEngineGlyphCache(format, matrix)
- , xinfo(x)
- , gset(XNone)
-{}
-
-QXRenderGlyphCache::~QXRenderGlyphCache()
-{
- if (gset != XNone)
- XRenderFreeGlyphSet(xinfo.display(), gset);
-}
-
-bool QXRenderGlyphCache::addGlyphs(const QTextItemInt &ti,
- const QVarLengthArray<glyph_t> &glyphs,
- const QVarLengthArray<QFixedPoint> &positions)
-{
- Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype);
-
- QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine);
- QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform());
-
- XGlyphInfo xglyphinfo;
-
- for (int i = 0; i < glyphs.size(); ++i) {
- const QFixed sppx = ft->subPixelPositionForX(positions[i].x);
- const QFixedPoint spp(sppx, 0);
- QFontEngineFT::Glyph *glyph = set->getGlyph(glyphs[i], spp);
- Glyph xglyphid = qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyphs[i], spp));
-
- if (glyph && glyph->format == glyphFormat()) {
- if (cachedGlyphs.contains(xglyphid)) {
- continue;
- } else {
- set->setGlyph(glyphs[i], spp, nullptr);
- delete glyph;
- glyph = 0;
- }
- }
-
- glyph = ft->loadGlyphFor(glyphs[i], spp, glyphFormat(), transform(), QColor());
-
- if (glyph == 0 || glyph->format != glyphFormat())
- return false;
-
- if (glyph->format == QFontEngine::Format_Mono) {
- // Must convert bitmap from msb to lsb bit order
- QImage img(glyph->data, glyph->width, glyph->height, QImage::Format_Mono);
- img = img.convertToFormat(QImage::Format_MonoLSB);
- memcpy(glyph->data, img.constBits(), static_cast<size_t>(img.sizeInBytes()));
- }
-
- set->setGlyph(glyphs[i], spp, glyph);
- Q_ASSERT(glyph->data || glyph->width == 0 || glyph->height == 0);
-
- xglyphinfo.width = glyph->width;
- xglyphinfo.height = glyph->height;
- xglyphinfo.x = -glyph->x;
- xglyphinfo.y = glyph->y;
- xglyphinfo.xOff = glyph->advance;
- xglyphinfo.yOff = 0;
-
- XRenderAddGlyphs(xinfo.display(), glyphSet(), &xglyphid, &xglyphinfo, 1, (const char *) glyph->data, glyphBufferSize(*glyph));
- cachedGlyphs.insert(xglyphid);
- }
-
- return true;
-}
-
-bool QXRenderGlyphCache::draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti)
-{
- Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype);
-
- if (ti.glyphs.numGlyphs == 0)
- return true;
-
- QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine);
- QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(matrix);
-
- QVarLengthArray<glyph_t> glyphs;
- QVarLengthArray<QFixedPoint> positions;
- ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions);
-
- if (glyphs.isEmpty())
- return true;
-
- if (!addGlyphs(ti, glyphs, positions))
- return false;
-
- QVarLengthArray<unsigned int> chars(glyphs.size());
-
- for (int i = 0; i < glyphs.size(); ++i)
- chars[i] = glyphId(glyphs[i], ft->subPixelPositionForX(positions[i].x));
-
- int i = 0;
- while (i < glyphs.size() && !isValidCoordinate(positions[i]))
- ++i;
-
- if (i >= glyphs.size())
- return true;
-
- QFixed xp = positions[i].x;
- QFixed yp = positions[i].y;
- QFixed offs = QFixed::fromReal(aliasedCoordinateDelta);
-
- XGlyphElt32 elt;
- elt.glyphset = gset;
- elt.chars = &chars[i];
- elt.nchars = 1;
- elt.xOff = qRound(xp + offs);
- elt.yOff = qRound(yp + offs);
-
- ++i;
-
- for (; i < glyphs.size(); ++i) {
- if (!isValidCoordinate(positions[i]))
- break;
-
- const QFixed sppx = ft->subPixelPositionForX(positions[i].x);
- const QFixedPoint spp(sppx, 0);
- QFontEngineFT::Glyph *g = set->getGlyph(glyphs[i], spp);
-
- if (g
- && positions[i].x == xp + g->advance
- && positions[i].y == yp
- && elt.nchars < 253 // don't draw more than 253 characters as some X servers
- // hang with it
- ) {
- elt.nchars++;
- xp += g->advance;
- } else {
- xp = positions[i].x;
- yp = positions[i].y;
-
- XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst,
- renderPictFormat(), 0, 0, 0, 0,
- &elt, 1);
- elt.chars = &chars[i];
- elt.nchars = 1;
- elt.xOff = qRound(xp + offs);
- elt.yOff = qRound(yp + offs);
- }
- }
-
- XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst,
- renderPictFormat(), 0, 0, 0, 0, &elt, 1);
-
- return true;
-}
-
-GlyphSet QXRenderGlyphCache::glyphSet()
-{
- if (gset == XNone)
- gset = XRenderCreateGlyphSet(xinfo.display(), renderPictFormat());
-
- Q_ASSERT(gset != XNone);
- return gset;
-}
-
-int QXRenderGlyphCache::glyphBufferSize(const QFontEngineFT::Glyph &glyph) const
-{
- int pitch = 0;
-
- switch (glyphFormat()) {
- case QFontEngine::Format_Mono:
- pitch = ((glyph.width + 31) & ~31) >> 3;
- break;
- case QFontEngine::Format_A8:
- pitch = (glyph.width + 3) & ~3;
- break;
- default:
- pitch = glyph.width * 4;
- break;
- }
-
- return pitch * glyph.height;
-}
-
-QImage::Format QXRenderGlyphCache::imageFormat() const
-{
- switch (glyphFormat()) {
- case QFontEngine::Format_None:
- Q_UNREACHABLE();
- break;
- case QFontEngine::Format_Mono:
- return QImage::Format_Mono;
- break;
- case QFontEngine::Format_A8:
- return QImage::Format_Alpha8;
- break;
- case QFontEngine::Format_A32:
- case QFontEngine::Format_ARGB:
- return QImage::Format_ARGB32_Premultiplied;
- break;
- }
-
- Q_UNREACHABLE();
-}
-
-const XRenderPictFormat *QXRenderGlyphCache::renderPictFormat() const
-{
- switch (glyphFormat()) {
- case QFontEngine::Format_None:
- Q_UNREACHABLE();
- break;
- case QFontEngine::Format_Mono:
- return XRenderFindStandardFormat(xinfo.display(), PictStandardA1);
- break;
- case QFontEngine::Format_A8:
- return XRenderFindStandardFormat(xinfo.display(), PictStandardA8);
- break;
- case QFontEngine::Format_A32:
- case QFontEngine::Format_ARGB:
- return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32);
- break;
- }
-
- Q_UNREACHABLE();
-}
-
-QFontEngine::GlyphFormat QXRenderGlyphCache::glyphFormatForDepth(QFontEngine *fontEngine, int depth)
-{
- QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat;
-
- if (glyphFormat == QFontEngine::Format_None) {
- switch (depth) {
- case 32:
- glyphFormat = QFontEngine::Format_ARGB;
- break;
- case 24:
- glyphFormat = QFontEngine::Format_A32;
- break;
- case 1:
- glyphFormat = QFontEngine::Format_Mono;
- break;
- default:
- glyphFormat = QFontEngine::Format_A8;
- break;
- }
- }
-
- return glyphFormat;
-}
-
-Glyph QXRenderGlyphCache::glyphId(glyph_t glyph, QFixed subPixelPosition)
-{
- return qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyph, QFixedPoint(subPixelPosition, 0)));
-}
-
-bool QXRenderGlyphCache::isValidCoordinate(const QFixedPoint &fp)
-{
- enum { t_min = SHRT_MIN, t_max = SHRT_MAX };
- return (fp.x < t_min || fp.x > t_max || fp.y < t_min || fp.y > t_max) ? false : true;
-}
-#endif // QT_CONFIG(xrender)
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h
deleted file mode 100644
index bcbf84682c6..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <QtGui/QPaintEngine>
-
-typedef unsigned long XID;
-typedef XID Drawable;
-typedef struct _XGC *GC;
-
-QT_BEGIN_NAMESPACE
-
-extern "C" {
-Drawable qt_x11Handle(const QPaintDevice *pd);
-GC qt_x11_get_pen_gc(QPainter *);
-GC qt_x11_get_brush_gc(QPainter *);
-}
-
-class QX11PaintEnginePrivate;
-class QX11PaintEngine : public QPaintEngine
-{
- Q_DECLARE_PRIVATE(QX11PaintEngine)
-public:
- QX11PaintEngine();
- ~QX11PaintEngine();
-
- bool begin(QPaintDevice *pdev) override;
- bool end() override;
-
- void updateState(const QPaintEngineState &state) override;
-
- void updatePen(const QPen &pen);
- void updateBrush(const QBrush &brush, const QPointF &pt);
- void updateRenderHints(QPainter::RenderHints hints);
- void updateFont(const QFont &font);
- void updateMatrix(const QTransform &matrix);
- void updateClipRegion_dev(const QRegion &region, Qt::ClipOperation op);
-
- void drawLines(const QLine *lines, int lineCount) override;
- void drawLines(const QLineF *lines, int lineCount) override;
-
- void drawRects(const QRect *rects, int rectCount) override;
- void drawRects(const QRectF *rects, int rectCount) override;
-
- void drawPoints(const QPoint *points, int pointCount) override;
- void drawPoints(const QPointF *points, int pointCount) override;
-
- void drawEllipse(const QRect &r) override;
- void drawEllipse(const QRectF &r) override;
-
- virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override;
- inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) override
- { QPaintEngine::drawPolygon(points, pointCount, mode); }
-
- void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override;
- void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) override;
- void drawPath(const QPainterPath &path) override;
- void drawTextItem(const QPointF &p, const QTextItem &textItem) override;
- void drawImage(const QRectF &r, const QImage &img, const QRectF &sr,
- Qt::ImageConversionFlags flags = Qt::AutoColor) override;
-
- virtual Drawable handle() const;
- inline Type type() const override { return QPaintEngine::X11; }
-
- QPainter::RenderHints supportedRenderHints() const;
-
-protected:
- QX11PaintEngine(QX11PaintEnginePrivate &dptr);
-
-#if QT_CONFIG(fontconfig)
- void drawFreetype(const QPointF &p, const QTextItemInt &ti);
- bool drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti);
-#endif // QT_CONFIG(fontconfig)
-
- friend class QPixmap;
- friend class QFontEngineBox;
- friend GC qt_x11_get_pen_gc(QPainter *);
- friend GC qt_x11_get_brush_gc(QPainter *);
-
-private:
- Q_DISABLE_COPY_MOVE(QX11PaintEngine)
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
deleted file mode 100644
index b47bd3f5dcc..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
+++ /dev/null
@@ -1,2087 +0,0 @@
-// Copyright (C) 2018 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
-
-#include <QGuiApplication>
-
-#include <private/qdrawhelper_p.h>
-#include <private/qimage_p.h>
-#include <private/qimagepixmapcleanuphooks_p.h>
-
-#include "qxcbnativepainting.h"
-#include "qpixmap_x11_p.h"
-#include "qcolormap_x11_p.h"
-#include "qpaintengine_x11_p.h"
-
-QT_BEGIN_NAMESPACE
-
-#if QT_POINTER_SIZE == 8 // 64-bit versions
-
-Q_ALWAYS_INLINE uint PREMUL(uint x) {
- uint a = x >> 24;
- quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
- t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8;
- t &= 0x000000ff00ff00ff;
- return (uint(t)) | (uint(t >> 24)) | (a << 24);
-}
-
-#else // 32-bit versions
-
-Q_ALWAYS_INLINE uint PREMUL(uint x) {
- uint a = x >> 24;
- uint t = (x & 0xff00ff) * a;
- t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8;
- t &= 0xff00ff;
-
- x = ((x >> 8) & 0xff) * a;
- x = (x + ((x >> 8) & 0xff) + 0x80);
- x &= 0xff00;
- x |= t | (a << 24);
- return x;
-}
-#endif
-
-
-
-struct QXImageWrapper
-{
- XImage *xi;
-};
-
-QPixmap qt_toX11Pixmap(const QImage &image)
-{
- QPlatformPixmap *data =
- new QX11PlatformPixmap(image.depth() == 1
- ? QPlatformPixmap::BitmapType
- : QPlatformPixmap::PixmapType);
-
- data->fromImage(image, Qt::AutoColor);
-
- return QPixmap(data);
-}
-
-QPixmap qt_toX11Pixmap(const QPixmap &pixmap)
-{
- if (pixmap.isNull())
- return QPixmap();
-
- if (QPixmap(pixmap).data_ptr()->classId() == QPlatformPixmap::X11Class)
- return pixmap;
-
- return qt_toX11Pixmap(pixmap.toImage());
-}
-
-// For thread-safety:
-// image->data does not belong to X11, so we must free it ourselves.
-
-inline static void qSafeXDestroyImage(XImage *x)
-{
- if (x->data) {
- free(x->data);
- x->data = 0;
- }
- XDestroyImage(x);
-}
-
-QBitmap QX11PlatformPixmap::mask_to_bitmap(int screen) const
-{
- if (!x11_mask)
- return QBitmap();
- qt_x11SetDefaultScreen(screen);
- QBitmap bm(w, h);
- QX11PlatformPixmap *that = qt_x11Pixmap(bm);
- const QXcbX11Info *x = that->x11_info();
- GC gc = XCreateGC(x->display(), that->handle(), 0, 0);
- XCopyArea(x->display(), x11_mask, that->handle(), gc, 0, 0,
- that->width(), that->height(), 0, 0);
- XFreeGC(x->display(), gc);
- return bm;
-}
-
-void QX11PlatformPixmap::bitmapFromImage(const QImage &image)
-{
- w = image.width();
- h = image.height();
- d = 1;
- is_null = (w <= 0 || h <= 0);
- hd = createBitmapFromImage(image);
-#if QT_CONFIG(xrender)
- if (X11->use_xrender)
- picture = XRenderCreatePicture(xinfo.display(), hd,
- XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0);
-#endif // QT_CONFIG(xrender)
-}
-
-bool QX11PlatformPixmap::canTakeQImageFromXImage(const QXImageWrapper &xiWrapper) const
-{
- XImage *xi = xiWrapper.xi;
-
- if (xi->format != ZPixmap)
- return false;
-
- // ARGB32_Premultiplied
- if (picture && depth() == 32)
- return true;
-
- // RGB32
- if (depth() == 24 && xi->bits_per_pixel == 32 && xi->red_mask == 0xff0000
- && xi->green_mask == 0xff00 && xi->blue_mask == 0xff)
- return true;
-
- // RGB16
- if (depth() == 16 && xi->bits_per_pixel == 16 && xi->red_mask == 0xf800
- && xi->green_mask == 0x7e0 && xi->blue_mask == 0x1f)
- return true;
-
- return false;
-}
-
-QImage QX11PlatformPixmap::takeQImageFromXImage(const QXImageWrapper &xiWrapper) const
-{
- XImage *xi = xiWrapper.xi;
-
- QImage::Format format = QImage::Format_ARGB32_Premultiplied;
- if (depth() == 24)
- format = QImage::Format_RGB32;
- else if (depth() == 16)
- format = QImage::Format_RGB16;
-
- QImage image((uchar *)xi->data, xi->width, xi->height, xi->bytes_per_line, format);
- image.setDevicePixelRatio(devicePixelRatio());
- // take ownership
- image.data_ptr()->own_data = true;
- xi->data = 0;
-
- // we may have to swap the byte order
- if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst)
- || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst))
- {
- for (int i=0; i < image.height(); i++) {
- if (depth() == 16) {
- ushort *p = (ushort*)image.scanLine(i);
- ushort *end = p + image.width();
- while (p < end) {
- *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff);
- p++;
- }
- } else {
- uint *p = (uint*)image.scanLine(i);
- uint *end = p + image.width();
- while (p < end) {
- *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000)
- | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff);
- p++;
- }
- }
- }
- }
-
- // fix-up alpha channel
- if (format == QImage::Format_RGB32) {
- QRgb *p = (QRgb *)image.bits();
- for (int y = 0; y < xi->height; ++y) {
- for (int x = 0; x < xi->width; ++x)
- p[x] |= 0xff000000;
- p += xi->bytes_per_line / 4;
- }
- }
-
- XDestroyImage(xi);
- return image;
-}
-
-XID QX11PlatformPixmap::bitmap_to_mask(const QBitmap &bitmap, int screen)
-{
- if (bitmap.isNull())
- return 0;
- QBitmap bm = bitmap;
- qt_x11SetScreen(bm, screen);
-
- QX11PlatformPixmap *that = qt_x11Pixmap(bm);
- const QXcbX11Info *x = that->x11_info();
- Pixmap mask = XCreatePixmap(x->display(), RootWindow(x->display(), screen),
- that->width(), that->height(), 1);
- GC gc = XCreateGC(x->display(), mask, 0, 0);
- XCopyArea(x->display(), that->handle(), mask, gc, 0, 0,
- that->width(), that->height(), 0, 0);
- XFreeGC(x->display(), gc);
- return mask;
-}
-
-Drawable qt_x11Handle(const QPixmap &pixmap)
-{
- if (pixmap.isNull())
- return XNone;
-
- if (pixmap.handle()->classId() != QPlatformPixmap::X11Class)
- return XNone;
-
- return static_cast<const QX11PlatformPixmap *>(pixmap.handle())->handle();
-}
-
-
-/*****************************************************************************
- Internal functions
- *****************************************************************************/
-
-//extern const uchar *qt_get_bitflip_array(); // defined in qimage.cpp
-
-// Returns position of highest bit set or -1 if none
-static int highest_bit(uint v)
-{
- int i;
- uint b = (uint)1 << 31;
- for (i=31; ((b & v) == 0) && i>=0; i--)
- b >>= 1;
- return i;
-}
-
-// Counts the number of bits set in 'v'
-static uint n_bits(uint v)
-{
- int i = 0;
- while (v) {
- v = v & (v - 1);
- i++;
- }
- return i;
-}
-
-static uint *red_scale_table = nullptr;
-static uint *green_scale_table = nullptr;
-static uint *blue_scale_table = nullptr;
-
-static void cleanup_scale_tables()
-{
- delete[] red_scale_table;
- delete[] green_scale_table;
- delete[] blue_scale_table;
-}
-
-/*
- Could do smart bitshifting, but the "obvious" algorithm only works for
- nBits >= 4. This is more robust.
-*/
-static void build_scale_table(uint **table, uint nBits)
-{
- if (nBits > 7) {
- qWarning("build_scale_table: internal error, nBits = %i", nBits);
- return;
- }
- if (!*table) {
- static bool firstTable = true;
- if (firstTable) {
- qAddPostRoutine(cleanup_scale_tables);
- firstTable = false;
- }
- *table = new uint[256];
- }
- int maxVal = (1 << nBits) - 1;
- int valShift = 8 - nBits;
- int i;
- for (i = 0 ; i < maxVal + 1 ; i++)
- (*table)[i << valShift] = i*255/maxVal;
-}
-
-static int defaultScreen = -1;
-
-int qt_x11SetDefaultScreen(int screen)
-{
- int old = defaultScreen;
- defaultScreen = screen;
- return old;
-}
-
-void qt_x11SetScreen(QPixmap &pixmap, int screen)
-{
- if (pixmap.paintingActive()) {
- qWarning("qt_x11SetScreen(): Cannot change screens during painting");
- return;
- }
-
- if (pixmap.isNull())
- return;
-
- if (pixmap.handle()->classId() != QPlatformPixmap::X11Class)
- return;
-
- if (screen < 0)
- screen = QXcbX11Info::appScreen();
-
- QX11PlatformPixmap *pm = static_cast<QX11PlatformPixmap *>(pixmap.handle());
- if (screen == pm->xinfo.screen())
- return; // nothing to do
-
- if (pixmap.isNull()) {
- pm->xinfo = QXcbX11Info::fromScreen(screen);
- return;
- }
-
-#if 0
- qDebug("qt_x11SetScreen for %p from %d to %d. Size is %d/%d", pm, pm->xinfo.screen(), screen, pm->width(), pm->height());
-#endif
-
- qt_x11SetDefaultScreen(screen);
- pixmap = qt_toX11Pixmap(pixmap.toImage());
-}
-
-/*****************************************************************************
- QPixmap member functions
- *****************************************************************************/
-
-QBasicAtomicInt qt_pixmap_serial = Q_BASIC_ATOMIC_INITIALIZER(0);
-int Q_GUI_EXPORT qt_x11_preferred_pixmap_depth = 0;
-
-QX11PlatformPixmap::QX11PlatformPixmap(PixelType pixelType)
- : QPlatformPixmap(pixelType, X11Class), hd(0),
- flags(Uninitialized), x11_mask(0), picture(0), mask_picture(0), hd2(0),
- dpr(1.0), pengine(0)
-{}
-
-QX11PlatformPixmap::~QX11PlatformPixmap()
-{
- // Cleanup hooks have to be called before the handles are freed
- if (is_cached) {
- QImagePixmapCleanupHooks::executePlatformPixmapDestructionHooks(this);
- is_cached = false;
- }
-
- release();
-}
-
-QPlatformPixmap *QX11PlatformPixmap::createCompatiblePlatformPixmap() const
-{
- QX11PlatformPixmap *p = new QX11PlatformPixmap(pixelType());
- p->setDevicePixelRatio(devicePixelRatio());
- return p;
-}
-
-void QX11PlatformPixmap::resize(int width, int height)
-{
- setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1));
-
- w = width;
- h = height;
- is_null = (w <= 0 || h <= 0);
-
- if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) {
- xinfo = QXcbX11Info::fromScreen(defaultScreen);
- }
-
- int dd = xinfo.depth();
-
- if (qt_x11_preferred_pixmap_depth)
- dd = qt_x11_preferred_pixmap_depth;
-
- bool make_null = w <= 0 || h <= 0; // create null pixmap
- d = (pixelType() == BitmapType ? 1 : dd);
- if (make_null || d == 0) {
- w = 0;
- h = 0;
- is_null = true;
- hd = 0;
- picture = 0;
- d = 0;
- if (!make_null)
- qWarning("QPixmap: Invalid pixmap parameters");
- return;
- }
- hd = XCreatePixmap(xinfo.display(),
- RootWindow(xinfo.display(), xinfo.screen()),
- w, h, d);
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- XRenderPictFormat *format = d == 1
- ? XRenderFindStandardFormat(xinfo.display(), PictStandardA1)
- : XRenderFindVisualFormat(xinfo.display(), (Visual *) xinfo.visual());
- picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0);
- }
-#endif // QT_CONFIG(xrender)
-}
-
-struct QX11AlphaDetector
-{
- bool hasAlpha() const {
- if (checked)
- return has;
- // Will implicitly also check format and return quickly for opaque types...
- checked = true;
- has = image->isNull() ? false : const_cast<QImage *>(image)->data_ptr()->checkForAlphaPixels();
- return has;
- }
-
- bool hasXRenderAndAlpha() const {
- if (!X11->use_xrender)
- return false;
- return hasAlpha();
- }
-
- QX11AlphaDetector(const QImage *i, Qt::ImageConversionFlags flags)
- : image(i), checked(false), has(false)
- {
- if (flags & Qt::NoOpaqueDetection) {
- checked = true;
- has = image->hasAlphaChannel();
- }
- }
-
- const QImage *image;
- mutable bool checked;
- mutable bool has;
-};
-
-void QX11PlatformPixmap::fromImage(const QImage &img, Qt::ImageConversionFlags flags)
-{
- setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1));
-
- w = img.width();
- h = img.height();
- d = img.depth();
- is_null = (w <= 0 || h <= 0);
- setDevicePixelRatio(img.devicePixelRatio());
-
- if (is_null) {
- w = h = 0;
- return;
- }
-
- if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) {
- xinfo = QXcbX11Info::fromScreen(defaultScreen);
- }
-
- if (pixelType() == BitmapType) {
- bitmapFromImage(img);
- return;
- }
-
- if (uint(w) >= 32768 || uint(h) >= 32768) {
- w = h = 0;
- is_null = true;
- return;
- }
-
- QX11AlphaDetector alphaCheck(&img, flags);
- int dd = alphaCheck.hasXRenderAndAlpha() ? 32 : xinfo.depth();
-
- if (qt_x11_preferred_pixmap_depth)
- dd = qt_x11_preferred_pixmap_depth;
-
- QImage image = img;
-
- // must be monochrome
- if (dd == 1 || (flags & Qt::ColorMode_Mask) == Qt::MonoOnly) {
- if (d != 1) {
- // dither
- image = image.convertToFormat(QImage::Format_MonoLSB, flags);
- d = 1;
- }
- } else { // can be both
- bool conv8 = false;
- if (d > 8 && dd <= 8) { // convert to 8 bit
- if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither)
- flags = (flags & ~Qt::DitherMode_Mask)
- | Qt::PreferDither;
- conv8 = true;
- } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) {
- conv8 = (d == 1); // native depth wanted
- } else if (d == 1) {
- if (image.colorCount() == 2) {
- QRgb c0 = image.color(0); // Auto: convert to best
- QRgb c1 = image.color(1);
- conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255);
- } else {
- // eg. 1-color monochrome images (they do exist).
- conv8 = true;
- }
- }
- if (conv8) {
- image = image.convertToFormat(QImage::Format_Indexed8, flags);
- d = 8;
- }
- }
-
- if (d == 1 || image.format() > QImage::Format_ARGB32_Premultiplied) {
- QImage::Format fmt = QImage::Format_RGB32;
- if (alphaCheck.hasXRenderAndAlpha() && d > 1)
- fmt = QImage::Format_ARGB32_Premultiplied;
- image = image.convertToFormat(fmt, flags);
- fromImage(image, Qt::AutoColor);
- return;
- }
-
- Display *dpy = xinfo.display();
- Visual *visual = (Visual *)xinfo.visual();
- XImage *xi = nullptr;
- bool trucol = (visual->c_class >= TrueColor);
- size_t nbytes = image.sizeInBytes();
- uchar *newbits= nullptr;
-
-#if QT_CONFIG(xrender)
- if (alphaCheck.hasXRenderAndAlpha()) {
- const QImage &cimage = image;
-
- d = 32;
-
- if (QXcbX11Info::appDepth() != d) {
- xinfo.setDepth(d);
- }
-
- hd = XCreatePixmap(dpy, RootWindow(dpy, xinfo.screen()), w, h, d);
- picture = XRenderCreatePicture(dpy, hd,
- XRenderFindStandardFormat(dpy, PictStandardARGB32), 0, 0);
-
- xi = XCreateImage(dpy, visual, d, ZPixmap, 0, 0, w, h, 32, 0);
- Q_CHECK_PTR(xi);
- newbits = (uchar *)malloc(xi->bytes_per_line*h);
- Q_CHECK_PTR(newbits);
- xi->data = (char *)newbits;
-
- switch (cimage.format()) {
- case QImage::Format_Indexed8: {
- QList<QRgb> colorTable = cimage.colorTable();
- uint *xidata = (uint *)xi->data;
- for (int y = 0; y < h; ++y) {
- const uchar *p = cimage.scanLine(y);
- for (int x = 0; x < w; ++x) {
- const QRgb rgb = colorTable[p[x]];
- const int a = qAlpha(rgb);
- if (a == 0xff)
- *xidata = rgb;
- else
- // RENDER expects premultiplied alpha
- *xidata = qRgba(qt_div_255(qRed(rgb) * a),
- qt_div_255(qGreen(rgb) * a),
- qt_div_255(qBlue(rgb) * a),
- a);
- ++xidata;
- }
- }
- }
- break;
- case QImage::Format_RGB32: {
- uint *xidata = (uint *)xi->data;
- for (int y = 0; y < h; ++y) {
- const QRgb *p = (const QRgb *) cimage.scanLine(y);
- for (int x = 0; x < w; ++x)
- *xidata++ = p[x] | 0xff000000;
- }
- }
- break;
- case QImage::Format_ARGB32: {
- uint *xidata = (uint *)xi->data;
- for (int y = 0; y < h; ++y) {
- const QRgb *p = (const QRgb *) cimage.scanLine(y);
- for (int x = 0; x < w; ++x) {
- const QRgb rgb = p[x];
- const int a = qAlpha(rgb);
- if (a == 0xff)
- *xidata = rgb;
- else
- // RENDER expects premultiplied alpha
- *xidata = qRgba(qt_div_255(qRed(rgb) * a),
- qt_div_255(qGreen(rgb) * a),
- qt_div_255(qBlue(rgb) * a),
- a);
- ++xidata;
- }
- }
-
- }
- break;
- case QImage::Format_ARGB32_Premultiplied: {
- uint *xidata = (uint *)xi->data;
- for (int y = 0; y < h; ++y) {
- const QRgb *p = (const QRgb *) cimage.scanLine(y);
- memcpy(xidata, p, w*sizeof(QRgb));
- xidata += w;
- }
- }
- break;
- default:
- Q_ASSERT(false);
- }
-
- if ((xi->byte_order == MSBFirst) != (QSysInfo::ByteOrder == QSysInfo::BigEndian)) {
- uint *xidata = (uint *)xi->data;
- uint *xiend = xidata + w*h;
- while (xidata < xiend) {
- *xidata = (*xidata >> 24)
- | ((*xidata >> 8) & 0xff00)
- | ((*xidata << 8) & 0xff0000)
- | (*xidata << 24);
- ++xidata;
- }
- }
-
- GC gc = XCreateGC(dpy, hd, 0, 0);
- XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h);
- XFreeGC(dpy, gc);
-
- qSafeXDestroyImage(xi);
-
- return;
- }
-#endif // QT_CONFIG(xrender)
-
- if (trucol) { // truecolor display
- if (image.format() == QImage::Format_ARGB32_Premultiplied)
- image = image.convertToFormat(QImage::Format_ARGB32);
-
- const QImage &cimage = image;
- QRgb pix[256]; // pixel translation table
- const bool d8 = (d == 8);
- const uint red_mask = (uint)visual->red_mask;
- const uint green_mask = (uint)visual->green_mask;
- const uint blue_mask = (uint)visual->blue_mask;
- const int red_shift = highest_bit(red_mask) - 7;
- const int green_shift = highest_bit(green_mask) - 7;
- const int blue_shift = highest_bit(blue_mask) - 7;
- const uint rbits = highest_bit(red_mask) - lowest_bit(red_mask) + 1;
- const uint gbits = highest_bit(green_mask) - lowest_bit(green_mask) + 1;
- const uint bbits = highest_bit(blue_mask) - lowest_bit(blue_mask) + 1;
-
- if (d8) { // setup pixel translation
- QList<QRgb> ctable = cimage.colorTable();
- for (int i=0; i < cimage.colorCount(); i++) {
- int r = qRed (ctable[i]);
- int g = qGreen(ctable[i]);
- int b = qBlue (ctable[i]);
- r = red_shift > 0 ? r << red_shift : r >> -red_shift;
- g = green_shift > 0 ? g << green_shift : g >> -green_shift;
- b = blue_shift > 0 ? b << blue_shift : b >> -blue_shift;
- pix[i] = (b & blue_mask) | (g & green_mask) | (r & red_mask)
- | ~(blue_mask | green_mask | red_mask);
- }
- }
-
- xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0);
- Q_CHECK_PTR(xi);
- newbits = (uchar *)malloc(xi->bytes_per_line*h);
- Q_CHECK_PTR(newbits);
- if (!newbits) // no memory
- return;
- int bppc = xi->bits_per_pixel;
-
- bool contig_bits = n_bits(red_mask) == rbits &&
- n_bits(green_mask) == gbits &&
- n_bits(blue_mask) == bbits;
- bool dither_tc =
- // Want it?
- (flags & Qt::Dither_Mask) != Qt::ThresholdDither &&
- (flags & Qt::DitherMode_Mask) != Qt::AvoidDither &&
- // Need it?
- bppc < 24 && !d8 &&
- // Can do it? (Contiguous bits?)
- contig_bits;
-
- static bool init=false;
- static int D[16][16];
- if (dither_tc && !init) {
- // I also contributed this code to XV - WWA.
- /*
- The dither matrix, D, is obtained with this formula:
-
- D2 = [0 2]
- [3 1]
-
-
- D2*n = [4*Dn 4*Dn+2*Un]
- [4*Dn+3*Un 4*Dn+1*Un]
- */
- int n,i,j;
- init=1;
-
- /* Set D2 */
- D[0][0]=0;
- D[1][0]=2;
- D[0][1]=3;
- D[1][1]=1;
-
- /* Expand using recursive definition given above */
- for (n=2; n<16; n*=2) {
- for (i=0; i<n; i++) {
- for (j=0; j<n; j++) {
- D[i][j]*=4;
- D[i+n][j]=D[i][j]+2;
- D[i][j+n]=D[i][j]+3;
- D[i+n][j+n]=D[i][j]+1;
- }
- }
- }
- init=true;
- }
-
- enum { BPP8,
- BPP16_565, BPP16_555,
- BPP16_MSB, BPP16_LSB,
- BPP24_888,
- BPP24_MSB, BPP24_LSB,
- BPP32_8888,
- BPP32_MSB, BPP32_LSB
- } mode = BPP8;
-
- bool same_msb_lsb = (xi->byte_order == MSBFirst) == (QSysInfo::ByteOrder == QSysInfo::BigEndian);
-
- if (bppc == 8) // 8 bit
- mode = BPP8;
- else if (bppc == 16) { // 16 bit MSB/LSB
- if (red_shift == 8 && green_shift == 3 && blue_shift == -3 && !d8 && same_msb_lsb)
- mode = BPP16_565;
- else if (red_shift == 7 && green_shift == 2 && blue_shift == -3 && !d8 && same_msb_lsb)
- mode = BPP16_555;
- else
- mode = (xi->byte_order == LSBFirst) ? BPP16_LSB : BPP16_MSB;
- } else if (bppc == 24) { // 24 bit MSB/LSB
- if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb)
- mode = BPP24_888;
- else
- mode = (xi->byte_order == LSBFirst) ? BPP24_LSB : BPP24_MSB;
- } else if (bppc == 32) { // 32 bit MSB/LSB
- if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb)
- mode = BPP32_8888;
- else
- mode = (xi->byte_order == LSBFirst) ? BPP32_LSB : BPP32_MSB;
- } else
- qFatal("Logic error 3");
-
-#define GET_PIXEL \
- uint pixel; \
- if (d8) pixel = pix[*src++]; \
- else { \
- int r = qRed (*p); \
- int g = qGreen(*p); \
- int b = qBlue (*p++); \
- r = red_shift > 0 \
- ? r << red_shift : r >> -red_shift; \
- g = green_shift > 0 \
- ? g << green_shift : g >> -green_shift; \
- b = blue_shift > 0 \
- ? b << blue_shift : b >> -blue_shift; \
- pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask) \
- | ~(blue_mask | green_mask | red_mask); \
- }
-
-#define GET_PIXEL_DITHER_TC \
- int r = qRed (*p); \
- int g = qGreen(*p); \
- int b = qBlue (*p++); \
- const int thres = D[x%16][y%16]; \
- if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \
- > thres) \
- r += (1<<(8-rbits)); \
- if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \
- > thres) \
- g += (1<<(8-gbits)); \
- if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \
- > thres) \
- b += (1<<(8-bbits)); \
- r = red_shift > 0 \
- ? r << red_shift : r >> -red_shift; \
- g = green_shift > 0 \
- ? g << green_shift : g >> -green_shift; \
- b = blue_shift > 0 \
- ? b << blue_shift : b >> -blue_shift; \
- uint pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask);
-
-// again, optimized case
-// can't be optimized that much :(
-#define GET_PIXEL_DITHER_TC_OPT(red_shift,green_shift,blue_shift,red_mask,green_mask,blue_mask, \
- rbits,gbits,bbits) \
- const int thres = D[x%16][y%16]; \
- int r = qRed (*p); \
- if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \
- > thres) \
- r += (1<<(8-rbits)); \
- int g = qGreen(*p); \
- if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \
- > thres) \
- g += (1<<(8-gbits)); \
- int b = qBlue (*p++); \
- if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \
- > thres) \
- b += (1<<(8-bbits)); \
- uint pixel = ((r red_shift) & red_mask) \
- | ((g green_shift) & green_mask) \
- | ((b blue_shift) & blue_mask);
-
-#define CYCLE(body) \
- for (int y=0; y<h; y++) { \
- const uchar* src = cimage.scanLine(y); \
- uchar* dst = newbits + xi->bytes_per_line*y; \
- const QRgb* p = (const QRgb *)src; \
- body \
- }
-
- if (dither_tc) {
- switch (mode) {
- case BPP16_565:
- CYCLE(
- quint16* dst16 = (quint16*)dst;
- for (int x=0; x<w; x++) {
- GET_PIXEL_DITHER_TC_OPT(<<8,<<3,>>3,0xf800,0x7e0,0x1f,5,6,5)
- *dst16++ = pixel;
- }
- )
- break;
- case BPP16_555:
- CYCLE(
- quint16* dst16 = (quint16*)dst;
- for (int x=0; x<w; x++) {
- GET_PIXEL_DITHER_TC_OPT(<<7,<<2,>>3,0x7c00,0x3e0,0x1f,5,5,5)
- *dst16++ = pixel;
- }
- )
- break;
- case BPP16_MSB: // 16 bit MSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL_DITHER_TC
- *dst++ = (pixel >> 8);
- *dst++ = pixel;
- }
- )
- break;
- case BPP16_LSB: // 16 bit LSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL_DITHER_TC
- *dst++ = pixel;
- *dst++ = pixel >> 8;
- }
- )
- break;
- default:
- qFatal("Logic error");
- }
- } else {
- switch (mode) {
- case BPP8: // 8 bit
- CYCLE(
- Q_UNUSED(p);
- for (int x=0; x<w; x++)
- *dst++ = pix[*src++];
- )
- break;
- case BPP16_565:
- CYCLE(
- quint16* dst16 = (quint16*)dst;
- for (int x = 0; x < w; x++) {
- *dst16++ = ((*p >> 8) & 0xf800)
- | ((*p >> 5) & 0x7e0)
- | ((*p >> 3) & 0x1f);
- ++p;
- }
- )
- break;
- case BPP16_555:
- CYCLE(
- quint16* dst16 = (quint16*)dst;
- for (int x=0; x<w; x++) {
- *dst16++ = ((*p >> 9) & 0x7c00)
- | ((*p >> 6) & 0x3e0)
- | ((*p >> 3) & 0x1f);
- ++p;
- }
- )
- break;
- case BPP16_MSB: // 16 bit MSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = (pixel >> 8);
- *dst++ = pixel;
- }
- )
- break;
- case BPP16_LSB: // 16 bit LSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = pixel;
- *dst++ = pixel >> 8;
- }
- )
- break;
- case BPP24_888:
- CYCLE(
- if (QSysInfo::ByteOrder == QSysInfo::BigEndian) {
- for (int x=0; x<w; x++) {
- *dst++ = qRed (*p);
- *dst++ = qGreen(*p);
- *dst++ = qBlue (*p++);
- }
- } else {
- for (int x=0; x<w; x++) {
- *dst++ = qBlue (*p);
- *dst++ = qGreen(*p);
- *dst++ = qRed (*p++);
- }
- }
- )
- break;
- case BPP24_MSB: // 24 bit MSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = pixel >> 16;
- *dst++ = pixel >> 8;
- *dst++ = pixel;
- }
- )
- break;
- case BPP24_LSB: // 24 bit LSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = pixel;
- *dst++ = pixel >> 8;
- *dst++ = pixel >> 16;
- }
- )
- break;
- case BPP32_8888:
- CYCLE(
- memcpy(dst, p, w * 4);
- )
- break;
- case BPP32_MSB: // 32 bit MSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = pixel >> 24;
- *dst++ = pixel >> 16;
- *dst++ = pixel >> 8;
- *dst++ = pixel;
- }
- )
- break;
- case BPP32_LSB: // 32 bit LSB
- CYCLE(
- for (int x=0; x<w; x++) {
- GET_PIXEL
- *dst++ = pixel;
- *dst++ = pixel >> 8;
- *dst++ = pixel >> 16;
- *dst++ = pixel >> 24;
- }
- )
- break;
- default:
- qFatal("Logic error 2");
- }
- }
- xi->data = (char *)newbits;
- }
-
- if (d == 8 && !trucol) { // 8 bit pixmap
- int pop[256]; // pixel popularity
-
- if (image.colorCount() == 0)
- image.setColorCount(1);
-
- const QImage &cimage = image;
- memset(pop, 0, sizeof(int)*256); // reset popularity array
- for (int i = 0; i < h; i++) { // for each scanline...
- const uchar* p = cimage.scanLine(i);
- const uchar *end = p + w;
- while (p < end) // compute popularity
- pop[*p++]++;
- }
-
- newbits = (uchar *)malloc(nbytes); // copy image into newbits
- Q_CHECK_PTR(newbits);
- if (!newbits) // no memory
- return;
- uchar* p = newbits;
- memcpy(p, cimage.bits(), nbytes); // copy image data into newbits
-
- /*
- * The code below picks the most important colors. It is based on the
- * diversity algorithm, implemented in XV 3.10. XV is (C) by John Bradley.
- */
-
- struct PIX { // pixel sort element
- uchar r,g,b,n; // color + pad
- int use; // popularity
- int index; // index in colormap
- int mindist;
- };
- int ncols = 0;
- for (int i=0; i< cimage.colorCount(); i++) { // compute number of colors
- if (pop[i] > 0)
- ncols++;
- }
- for (int i = cimage.colorCount(); i < 256; i++) // ignore out-of-range pixels
- pop[i] = 0;
-
- // works since we make sure above to have at least
- // one color in the image
- if (ncols == 0)
- ncols = 1;
-
- PIX pixarr[256]; // pixel array
- PIX pixarr_sorted[256]; // pixel array (sorted)
- memset(pixarr, 0, ncols*sizeof(PIX));
- PIX *px = &pixarr[0];
- int maxpop = 0;
- int maxpix = 0;
- uint j = 0;
- QList<QRgb> ctable = cimage.colorTable();
- for (int i = 0; i < 256; i++) { // init pixel array
- if (pop[i] > 0) {
- px->r = qRed (ctable[i]);
- px->g = qGreen(ctable[i]);
- px->b = qBlue (ctable[i]);
- px->n = 0;
- px->use = pop[i];
- if (pop[i] > maxpop) { // select most popular entry
- maxpop = pop[i];
- maxpix = j;
- }
- px->index = i;
- px->mindist = 1000000;
- px++;
- j++;
- }
- }
- pixarr_sorted[0] = pixarr[maxpix];
- pixarr[maxpix].use = 0;
-
- for (int i = 1; i < ncols; i++) { // sort pixels
- int minpix = -1, mindist = -1;
- px = &pixarr_sorted[i-1];
- int r = px->r;
- int g = px->g;
- int b = px->b;
- int dist;
- if ((i & 1) || i<10) { // sort on max distance
- for (int j=0; j<ncols; j++) {
- px = &pixarr[j];
- if (px->use) {
- dist = (px->r - r)*(px->r - r) +
- (px->g - g)*(px->g - g) +
- (px->b - b)*(px->b - b);
- if (px->mindist > dist)
- px->mindist = dist;
- if (px->mindist > mindist) {
- mindist = px->mindist;
- minpix = j;
- }
- }
- }
- } else { // sort on max popularity
- for (int j=0; j<ncols; j++) {
- px = &pixarr[j];
- if (px->use) {
- dist = (px->r - r)*(px->r - r) +
- (px->g - g)*(px->g - g) +
- (px->b - b)*(px->b - b);
- if (px->mindist > dist)
- px->mindist = dist;
- if (px->use > mindist) {
- mindist = px->use;
- minpix = j;
- }
- }
- }
- }
- pixarr_sorted[i] = pixarr[minpix];
- pixarr[minpix].use = 0;
- }
-
- QXcbColormap cmap = QXcbColormap::instance(xinfo.screen());
- uint pix[256]; // pixel translation table
- px = &pixarr_sorted[0];
- for (int i = 0; i < ncols; i++) { // allocate colors
- QColor c(px->r, px->g, px->b);
- pix[px->index] = cmap.pixel(c);
- px++;
- }
-
- p = newbits;
- for (size_t i = 0; i < nbytes; i++) { // translate pixels
- *p = pix[*p];
- p++;
- }
- }
-
- if (!xi) { // X image not created
- xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0);
- if (xi->bits_per_pixel == 16) { // convert 8 bpp ==> 16 bpp
- ushort *p2;
- int p2inc = xi->bytes_per_line/sizeof(ushort);
- ushort *newerbits = (ushort *)malloc(xi->bytes_per_line * h);
- Q_CHECK_PTR(newerbits);
- if (!newerbits) // no memory
- return;
- uchar* p = newbits;
- for (int y = 0; y < h; y++) { // OOPS: Do right byte order!!
- p2 = newerbits + p2inc*y;
- for (int x = 0; x < w; x++)
- *p2++ = *p++;
- }
- free(newbits);
- newbits = (uchar *)newerbits;
- } else if (xi->bits_per_pixel != 8) {
- qWarning("QPixmap::fromImage: Display not supported "
- "(bpp=%d)", xi->bits_per_pixel);
- }
- xi->data = (char *)newbits;
- }
-
- hd = XCreatePixmap(dpy,
- RootWindow(dpy, xinfo.screen()),
- w, h, dd);
-
- GC gc = XCreateGC(dpy, hd, 0, 0);
- XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h);
- XFreeGC(dpy, gc);
-
- qSafeXDestroyImage(xi);
- d = dd;
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- XRenderPictFormat *format = d == 1
- ? XRenderFindStandardFormat(dpy, PictStandardA1)
- : XRenderFindVisualFormat(dpy, (Visual *)xinfo.visual());
- picture = XRenderCreatePicture(dpy, hd, format, 0, 0);
- }
-#endif
-
- if (alphaCheck.hasAlpha()) {
- QBitmap m = QBitmap::fromImage(image.createAlphaMask(flags));
- setMask(m);
- }
-}
-
-void QX11PlatformPixmap::copy(const QPlatformPixmap *data, const QRect &rect)
-{
- if (data->pixelType() == BitmapType) {
- fromImage(data->toImage().copy(rect), Qt::AutoColor);
- return;
- }
-
- const QX11PlatformPixmap *x11Data = static_cast<const QX11PlatformPixmap*>(data);
-
- setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1));
-
- flags &= ~Uninitialized;
- xinfo = x11Data->xinfo;
- d = x11Data->d;
- w = rect.width();
- h = rect.height();
- is_null = (w <= 0 || h <= 0);
- hd = XCreatePixmap(xinfo.display(),
- RootWindow(xinfo.display(), x11Data->xinfo.screen()),
- w, h, d);
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- XRenderPictFormat *format = d == 32
- ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32)
- : XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual());
- picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0);
- }
-#endif // QT_CONFIG(xrender)
- if (x11Data->x11_mask) {
- x11_mask = XCreatePixmap(xinfo.display(), hd, w, h, 1);
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask,
- XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0);
- XRenderPictureAttributes attrs;
- attrs.alpha_map = x11Data->mask_picture;
- XRenderChangePicture(xinfo.display(), x11Data->picture, CPAlphaMap, &attrs);
- }
-#endif
- }
-
-#if QT_CONFIG(xrender)
- if (x11Data->picture && x11Data->d == 32) {
- XRenderComposite(xinfo.display(), PictOpSrc,
- x11Data->picture, 0, picture,
- rect.x(), rect.y(), 0, 0, 0, 0, w, h);
- } else
-#endif
- {
- GC gc = XCreateGC(xinfo.display(), hd, 0, 0);
- XCopyArea(xinfo.display(), x11Data->hd, hd, gc,
- rect.x(), rect.y(), w, h, 0, 0);
- if (x11Data->x11_mask) {
- GC monogc = XCreateGC(xinfo.display(), x11_mask, 0, 0);
- XCopyArea(xinfo.display(), x11Data->x11_mask, x11_mask, monogc,
- rect.x(), rect.y(), w, h, 0, 0);
- XFreeGC(xinfo.display(), monogc);
- }
- XFreeGC(xinfo.display(), gc);
- }
-}
-
-bool QX11PlatformPixmap::scroll(int dx, int dy, const QRect &rect)
-{
- GC gc = XCreateGC(xinfo.display(), hd, 0, 0);
- XCopyArea(xinfo.display(), hd, hd, gc,
- rect.left(), rect.top(), rect.width(), rect.height(),
- rect.left() + dx, rect.top() + dy);
- XFreeGC(xinfo.display(), gc);
- return true;
-}
-
-int QX11PlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const
-{
- switch (metric) {
- case QPaintDevice::PdmDevicePixelRatio:
- return devicePixelRatio();
- break;
- case QPaintDevice::PdmDevicePixelRatioScaled:
- return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale();
- break;
- case QPaintDevice::PdmWidth:
- return w;
- case QPaintDevice::PdmHeight:
- return h;
- case QPaintDevice::PdmNumColors:
- return 1 << d;
- case QPaintDevice::PdmDepth:
- return d;
- case QPaintDevice::PdmWidthMM: {
- const int screen = xinfo.screen();
- const int mm = DisplayWidthMM(xinfo.display(), screen) * w
- / DisplayWidth(xinfo.display(), screen);
- return mm;
- }
- case QPaintDevice::PdmHeightMM: {
- const int screen = xinfo.screen();
- const int mm = (DisplayHeightMM(xinfo.display(), screen) * h)
- / DisplayHeight(xinfo.display(), screen);
- return mm;
- }
- case QPaintDevice::PdmDpiX:
- case QPaintDevice::PdmPhysicalDpiX:
- return QXcbX11Info::appDpiX(xinfo.screen());
- case QPaintDevice::PdmDpiY:
- case QPaintDevice::PdmPhysicalDpiY:
- return QXcbX11Info::appDpiY(xinfo.screen());
- default:
- qWarning("QX11PlatformPixmap::metric(): Invalid metric");
- return 0;
- }
-}
-
-void QX11PlatformPixmap::fill(const QColor &fillColor)
-{
- if (fillColor.alpha() != 255) {
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- if (!picture || d != 32)
- convertToARGB32(/*preserveContents = */false);
-
- ::Picture src = X11->getSolidFill(xinfo.screen(), fillColor);
- XRenderComposite(xinfo.display(), PictOpSrc, src, 0, picture,
- 0, 0, width(), height(),
- 0, 0, width(), height());
- } else
-#endif
- {
- QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied);
- im.fill(PREMUL(fillColor.rgba()));
- release();
- fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither);
- }
- return;
- }
-
- GC gc = XCreateGC(xinfo.display(), hd, 0, 0);
- if (depth() == 1) {
- XSetForeground(xinfo.display(), gc, qGray(fillColor.rgb()) > 127 ? 0 : 1);
- } else if (X11->use_xrender && d >= 24) {
- XSetForeground(xinfo.display(), gc, fillColor.rgba());
- } else {
- XSetForeground(xinfo.display(), gc,
- QXcbColormap::instance(xinfo.screen()).pixel(fillColor));
- }
- XFillRectangle(xinfo.display(), hd, gc, 0, 0, width(), height());
- XFreeGC(xinfo.display(), gc);
-}
-
-QBitmap QX11PlatformPixmap::mask() const
-{
- QBitmap mask;
-#if QT_CONFIG(xrender)
- if (picture && d == 32) {
- // #### slow - there must be a better way..
- mask = QBitmap::fromImage(toImage().createAlphaMask());
- } else
-#endif
- if (d == 1) {
- QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this);
- mask = QBitmap::fromPixmap(QPixmap(that));
- } else {
- mask = mask_to_bitmap(xinfo.screen());
- }
- return mask;
-}
-
-void QX11PlatformPixmap::setMask(const QBitmap &newmask)
-{
- if (newmask.isNull()) { // clear mask
-#if QT_CONFIG(xrender)
- if (picture && d == 32) {
- QX11PlatformPixmap newData(pixelType());
- newData.resize(w, h);
- newData.fill(Qt::black);
- XRenderComposite(xinfo.display(), PictOpOver,
- picture, 0, newData.picture,
- 0, 0, 0, 0, 0, 0, w, h);
- release();
- *this = newData;
- // the new QX11PlatformPixmap object isn't referenced yet, so
- // ref it
- ref.ref();
-
- // the below is to make sure the QX11PlatformPixmap destructor
- // doesn't delete our newly created render picture
- newData.hd = 0;
- newData.x11_mask = 0;
- newData.picture = 0;
- newData.mask_picture = 0;
- newData.hd2 = 0;
- } else
-#endif
- if (x11_mask) {
-#if QT_CONFIG(xrender)
- if (picture) {
- XRenderPictureAttributes attrs;
- attrs.alpha_map = 0;
- XRenderChangePicture(xinfo.display(), picture, CPAlphaMap,
- &attrs);
- }
- if (mask_picture)
- XRenderFreePicture(xinfo.display(), mask_picture);
- mask_picture = 0;
-#endif
- XFreePixmap(xinfo.display(), x11_mask);
- x11_mask = 0;
- }
- return;
- }
-
-#if QT_CONFIG(xrender)
- if (picture && d == 32) {
- XRenderComposite(xinfo.display(), PictOpSrc,
- picture, qt_x11Pixmap(newmask)->x11PictureHandle(),
- picture, 0, 0, 0, 0, 0, 0, w, h);
- } else
-#endif
- if (depth() == 1) {
- XGCValues vals;
- vals.function = GXand;
- GC gc = XCreateGC(xinfo.display(), hd, GCFunction, &vals);
- XCopyArea(xinfo.display(), qt_x11Pixmap(newmask)->handle(), hd, gc, 0, 0,
- width(), height(), 0, 0);
- XFreeGC(xinfo.display(), gc);
- } else {
- // ##### should or the masks together
- if (x11_mask) {
- XFreePixmap(xinfo.display(), x11_mask);
-#if QT_CONFIG(xrender)
- if (mask_picture)
- XRenderFreePicture(xinfo.display(), mask_picture);
-#endif
- }
- x11_mask = QX11PlatformPixmap::bitmap_to_mask(newmask, xinfo.screen());
-#if QT_CONFIG(xrender)
- if (picture) {
- mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask,
- XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0);
- XRenderPictureAttributes attrs;
- attrs.alpha_map = mask_picture;
- XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, &attrs);
- }
-#endif
- }
-}
-
-bool QX11PlatformPixmap::hasAlphaChannel() const
-{
- if (picture && d == 32)
- return true;
-
- if (x11_mask && d == 1)
- return true;
-
- return false;
-}
-
-QPixmap QX11PlatformPixmap::transformed(const QTransform &transform, Qt::TransformationMode mode) const
-{
- if (mode == Qt::SmoothTransformation || transform.type() >= QTransform::TxProject) {
- QImage image = toImage();
- return QPixmap::fromImage(image.transformed(transform, mode));
- }
-
- uint w = 0;
- uint h = 0; // size of target pixmap
- uint ws, hs; // size of source pixmap
- uchar *dptr; // data in target pixmap
- uint dbpl, dbytes; // bytes per line/bytes total
- uchar *sptr; // data in original pixmap
- int sbpl; // bytes per line in original
- int bpp; // bits per pixel
- bool depth1 = depth() == 1;
- Display *dpy = xinfo.display();
-
- ws = width();
- hs = height();
-
- QTransform mat(transform.m11(), transform.m12(), transform.m13(),
- transform.m21(), transform.m22(), transform.m23(),
- 0., 0., 1);
- bool complex_xform = false;
-
- if (mat.type() <= QTransform::TxScale) {
- h = qRound(qAbs(mat.m22()) * hs);
- w = qRound(qAbs(mat.m11()) * ws);
- } else { // rotation or shearing
- QPolygonF a(QRectF(0, 0, ws, hs));
- a = mat.map(a);
- QRect r = a.boundingRect().toAlignedRect();
- w = r.width();
- h = r.height();
- complex_xform = true;
- }
- mat = QPixmap::trueMatrix(mat, ws, hs); // true matrix
-
- bool invertible;
- mat = mat.inverted(&invertible); // invert matrix
-
- if (h == 0 || w == 0 || !invertible
- || qAbs(h) >= 32768 || qAbs(w) >= 32768 )
- // error, return null pixmap
- return QPixmap();
-
- XImage *xi = XGetImage(xinfo.display(), handle(), 0, 0, ws, hs, AllPlanes,
- depth1 ? XYPixmap : ZPixmap);
-
- if (!xi)
- return QPixmap();
-
- sbpl = xi->bytes_per_line;
- sptr = (uchar *)xi->data;
- bpp = xi->bits_per_pixel;
-
- if (depth1)
- dbpl = (w+7)/8;
- else
- dbpl = ((w*bpp+31)/32)*4;
- dbytes = dbpl*h;
-
- dptr = (uchar *)malloc(dbytes); // create buffer for bits
- Q_CHECK_PTR(dptr);
- if (depth1) // fill with zeros
- memset(dptr, 0, dbytes);
- else if (bpp == 8) // fill with background color
- memset(dptr, WhitePixel(xinfo.display(), xinfo.screen()), dbytes);
- else
- memset(dptr, 0, dbytes);
-
- // #define QT_DEBUG_XIMAGE
-#if defined(QT_DEBUG_XIMAGE)
- qDebug("----IMAGE--INFO--------------");
- qDebug("width............. %d", xi->width);
- qDebug("height............ %d", xi->height);
- qDebug("xoffset........... %d", xi->xoffset);
- qDebug("format............ %d", xi->format);
- qDebug("byte order........ %d", xi->byte_order);
- qDebug("bitmap unit....... %d", xi->bitmap_unit);
- qDebug("bitmap bit order.. %d", xi->bitmap_bit_order);
- qDebug("depth............. %d", xi->depth);
- qDebug("bytes per line.... %d", xi->bytes_per_line);
- qDebug("bits per pixel.... %d", xi->bits_per_pixel);
-#endif
-
- int type;
- if (xi->bitmap_bit_order == MSBFirst)
- type = QT_XFORM_TYPE_MSBFIRST;
- else
- type = QT_XFORM_TYPE_LSBFIRST;
- int xbpl, p_inc;
- if (depth1) {
- xbpl = (w+7)/8;
- p_inc = dbpl - xbpl;
- } else {
- xbpl = (w*bpp)/8;
- p_inc = dbpl - xbpl;
- }
-
- if (!qt_xForm_helper(mat, xi->xoffset, type, bpp, dptr, xbpl, p_inc, h, sptr, sbpl, ws, hs)){
- qWarning("QPixmap::transform: display not supported (bpp=%d)",bpp);
- QPixmap pm;
- free(dptr);
- return pm;
- }
-
- qSafeXDestroyImage(xi);
-
- if (depth1) { // mono bitmap
- QBitmap bm = QBitmap::fromData(QSize(w, h), dptr,
- BitmapBitOrder(xinfo.display()) == MSBFirst
- ? QImage::Format_Mono
- : QImage::Format_MonoLSB);
- free(dptr);
- return bm;
- } else { // color pixmap
- QX11PlatformPixmap *x11Data = new QX11PlatformPixmap(QPlatformPixmap::PixmapType);
- QPixmap pm(x11Data);
- x11Data->flags &= ~QX11PlatformPixmap::Uninitialized;
- x11Data->xinfo = xinfo;
- x11Data->d = d;
- x11Data->w = w;
- x11Data->h = h;
- x11Data->is_null = (w <= 0 || h <= 0);
- x11Data->hd = XCreatePixmap(xinfo.display(),
- RootWindow(xinfo.display(), xinfo.screen()),
- w, h, d);
- x11Data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1));
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- XRenderPictFormat *format = x11Data->d == 32
- ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32)
- : XRenderFindVisualFormat(xinfo.display(), (Visual *) x11Data->xinfo.visual());
- x11Data->picture = XRenderCreatePicture(xinfo.display(), x11Data->hd, format, 0, 0);
- }
-#endif // QT_CONFIG(xrender)
-
- GC gc = XCreateGC(xinfo.display(), x11Data->hd, 0, 0);
- xi = XCreateImage(dpy, (Visual*)x11Data->xinfo.visual(),
- x11Data->d,
- ZPixmap, 0, (char *)dptr, w, h, 32, 0);
- XPutImage(dpy, qt_x11Pixmap(pm)->handle(), gc, xi, 0, 0, 0, 0, w, h);
- qSafeXDestroyImage(xi);
- XFreeGC(xinfo.display(), gc);
-
- if (x11_mask) { // xform mask, too
- pm.setMask(mask_to_bitmap(xinfo.screen()).transformed(transform));
- } else if (d != 32 && complex_xform) { // need a mask!
- QBitmap mask(ws, hs);
- mask.fill(Qt::color1);
- pm.setMask(mask.transformed(transform));
- }
- return pm;
- }
-}
-
-QImage QX11PlatformPixmap::toImage() const
-{
- return toImage(QRect(0, 0, w, h));
-}
-
-QImage QX11PlatformPixmap::toImage(const QRect &rect) const
-{
- Window root_return;
- int x_return;
- int y_return;
- unsigned int width_return;
- unsigned int height_return;
- unsigned int border_width_return;
- unsigned int depth_return;
-
- XGetGeometry(xinfo.display(), hd, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return);
-
- QXImageWrapper xiWrapper;
- xiWrapper.xi = XGetImage(xinfo.display(), hd, rect.x(), rect.y(), rect.width(), rect.height(),
- AllPlanes, (depth() == 1) ? XYPixmap : ZPixmap);
-
- Q_CHECK_PTR(xiWrapper.xi);
- if (!xiWrapper.xi)
- return QImage();
-
- if (!x11_mask && canTakeQImageFromXImage(xiWrapper))
- return takeQImageFromXImage(xiWrapper);
-
- QImage image = toImage(xiWrapper, rect);
- qSafeXDestroyImage(xiWrapper.xi);
- return image;
-}
-
-#if QT_CONFIG(xrender)
-static XRenderPictFormat *qt_renderformat_for_depth(const QXcbX11Info &xinfo, int depth)
-{
- if (depth == 1)
- return XRenderFindStandardFormat(xinfo.display(), PictStandardA1);
- else if (depth == 32)
- return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32);
- else
- return XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual());
-}
-#endif
-
-Q_GLOBAL_STATIC(QX11PaintEngine, qt_x11_paintengine)
-
-QPaintEngine *QX11PlatformPixmap::paintEngine() const
-{
- QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this);
-
- if ((flags & Readonly)/* && share_mode == QPixmap::ImplicitlyShared*/) {
- // if someone wants to draw onto us, copy the shared contents
- // and turn it into a fully fledged QPixmap
- ::Pixmap hd_copy = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()),
- w, h, d);
-#if QT_CONFIG(xrender)
- if (picture && d == 32) {
- XRenderPictFormat *format = qt_renderformat_for_depth(xinfo, d);
- ::Picture picture_copy = XRenderCreatePicture(xinfo.display(),
- hd_copy, format,
- 0, 0);
-
- XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, picture_copy,
- 0, 0, 0, 0, 0, 0, w, h);
- XRenderFreePicture(xinfo.display(), picture);
- that->picture = picture_copy;
- } else
-#endif
- {
- GC gc = XCreateGC(xinfo.display(), hd_copy, 0, 0);
- XCopyArea(xinfo.display(), hd, hd_copy, gc, 0, 0, w, h, 0, 0);
- XFreeGC(xinfo.display(), gc);
- }
- that->hd = hd_copy;
- that->flags &= ~QX11PlatformPixmap::Readonly;
- }
-
- if (qt_x11_paintengine->isActive()) {
- if (!that->pengine)
- that->pengine = new QX11PaintEngine;
-
- return that->pengine;
- }
-
- return qt_x11_paintengine();
-}
-
-qreal QX11PlatformPixmap::devicePixelRatio() const
-{
- return dpr;
-}
-
-void QX11PlatformPixmap::setDevicePixelRatio(qreal scaleFactor)
-{
- dpr = scaleFactor;
-}
-
-Pixmap QX11PlatformPixmap::x11ConvertToDefaultDepth()
-{
-#if QT_CONFIG(xrender)
- if (d == xinfo.appDepth() || !X11->use_xrender)
- return hd;
- if (!hd2) {
- hd2 = XCreatePixmap(xinfo.display(), hd, w, h, xinfo.appDepth());
- XRenderPictFormat *format = XRenderFindVisualFormat(xinfo.display(),
- (Visual*) xinfo.visual());
- Picture pic = XRenderCreatePicture(xinfo.display(), hd2, format, 0, 0);
- XRenderComposite(xinfo.display(), PictOpSrc, picture,
- XNone, pic, 0, 0, 0, 0, 0, 0, w, h);
- XRenderFreePicture(xinfo.display(), pic);
- }
- return hd2;
-#else
- return hd;
-#endif
-}
-
-XID QX11PlatformPixmap::createBitmapFromImage(const QImage &image)
-{
- QImage img = image.convertToFormat(QImage::Format_MonoLSB);
- const QRgb c0 = QColor(Qt::black).rgb();
- const QRgb c1 = QColor(Qt::white).rgb();
- if (img.color(0) == c0 && img.color(1) == c1) {
- img.invertPixels();
- img.setColor(0, c1);
- img.setColor(1, c0);
- }
-
- char *bits;
- uchar *tmp_bits;
- int w = img.width();
- int h = img.height();
- int bpl = (w + 7) / 8;
- qsizetype ibpl = img.bytesPerLine();
- if (bpl != ibpl) {
- tmp_bits = new uchar[bpl*h];
- bits = (char *)tmp_bits;
- uchar *p, *b;
- int y;
- b = tmp_bits;
- p = img.scanLine(0);
- for (y = 0; y < h; y++) {
- memcpy(b, p, bpl);
- b += bpl;
- p += ibpl;
- }
- } else {
- bits = (char *)img.bits();
- tmp_bits = 0;
- }
- XID hd = XCreateBitmapFromData(QXcbX11Info::display(),
- QXcbX11Info::appRootWindow(),
- bits, w, h);
- if (tmp_bits) // Avoid purify complaint
- delete [] tmp_bits;
- return hd;
-}
-
-bool QX11PlatformPixmap::isBackingStore() const
-{
- return (flags & IsBackingStore);
-}
-
-void QX11PlatformPixmap::setIsBackingStore(bool on)
-{
- if (on)
- flags |= IsBackingStore;
- else {
- flags &= ~IsBackingStore;
- }
-}
-
-#if QT_CONFIG(xrender)
-void QX11PlatformPixmap::convertToARGB32(bool preserveContents)
-{
- if (!X11->use_xrender)
- return;
-
- // Q_ASSERT(count == 1);
- if ((flags & Readonly)/* && share_mode == QPixmap::ExplicitlyShared*/)
- return;
-
- Pixmap pm = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()),
- w, h, 32);
- Picture p = XRenderCreatePicture(xinfo.display(), pm,
- XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32), 0, 0);
- if (picture) {
- if (preserveContents)
- XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, p, 0, 0, 0, 0, 0, 0, w, h);
- if (!(flags & Readonly))
- XRenderFreePicture(xinfo.display(), picture);
- }
- if (hd && !(flags & Readonly))
- XFreePixmap(xinfo.display(), hd);
- if (x11_mask) {
- XFreePixmap(xinfo.display(), x11_mask);
- if (mask_picture)
- XRenderFreePicture(xinfo.display(), mask_picture);
- x11_mask = 0;
- mask_picture = 0;
- }
- hd = pm;
- picture = p;
-
- d = 32;
- xinfo.setDepth(32);
-
- XVisualInfo visinfo;
- if (XMatchVisualInfo(xinfo.display(), xinfo.screen(), 32, TrueColor, &visinfo))
- xinfo.setVisual(visinfo.visual);
-}
-#endif
-
-void QX11PlatformPixmap::release()
-{
- delete pengine;
- pengine = 0;
-
- if (/*!X11*/ QCoreApplication::closingDown()) {
- // At this point, the X server will already have freed our resources,
- // so there is nothing to do.
- return;
- }
-
- if (x11_mask) {
-#if QT_CONFIG(xrender)
- if (mask_picture)
- XRenderFreePicture(xinfo.display(), mask_picture);
- mask_picture = 0;
-#endif
- XFreePixmap(xinfo.display(), x11_mask);
- x11_mask = 0;
- }
-
- if (hd) {
-#if QT_CONFIG(xrender)
- if (picture) {
- XRenderFreePicture(xinfo.display(), picture);
- picture = 0;
- }
-#endif // QT_CONFIG(xrender)
-
- if (hd2) {
- XFreePixmap(xinfo.display(), hd2);
- hd2 = 0;
- }
- if (!(flags & Readonly))
- XFreePixmap(xinfo.display(), hd);
- hd = 0;
- }
-}
-
-QImage QX11PlatformPixmap::toImage(const QXImageWrapper &xiWrapper, const QRect &rect) const
-{
- XImage *xi = xiWrapper.xi;
-
- int d = depth();
- Visual *visual = (Visual *)xinfo.visual();
- bool trucol = (visual->c_class >= TrueColor) && d > 1;
-
- QImage::Format format = QImage::Format_Mono;
- if (d > 1 && d <= 8) {
- d = 8;
- format = QImage::Format_Indexed8;
- }
- // we could run into the situation where d == 8 AND trucol is true, which can
- // cause problems when converting to and from images. in this case, always treat
- // the depth as 32...
- if (d > 8 || trucol) {
- d = 32;
- format = QImage::Format_RGB32;
- }
-
- if (d == 1 && xi->bitmap_bit_order == LSBFirst)
- format = QImage::Format_MonoLSB;
- if (x11_mask && format == QImage::Format_RGB32)
- format = QImage::Format_ARGB32;
-
- QImage image(xi->width, xi->height, format);
- image.setDevicePixelRatio(devicePixelRatio());
- if (image.isNull()) // could not create image
- return image;
-
- QImage alpha;
- if (x11_mask) {
- if (rect.contains(QRect(0, 0, w, h)))
- alpha = mask().toImage();
- else
- alpha = mask().toImage().copy(rect);
- }
- bool ale = alpha.format() == QImage::Format_MonoLSB;
-
- if (trucol) { // truecolor
- const uint red_mask = (uint)visual->red_mask;
- const uint green_mask = (uint)visual->green_mask;
- const uint blue_mask = (uint)visual->blue_mask;
- const int red_shift = highest_bit(red_mask) - 7;
- const int green_shift = highest_bit(green_mask) - 7;
- const int blue_shift = highest_bit(blue_mask) - 7;
-
- const uint red_bits = n_bits(red_mask);
- const uint green_bits = n_bits(green_mask);
- const uint blue_bits = n_bits(blue_mask);
-
- static uint red_table_bits = 0;
- static uint green_table_bits = 0;
- static uint blue_table_bits = 0;
-
- if (red_bits < 8 && red_table_bits != red_bits) {
- build_scale_table(&red_scale_table, red_bits);
- red_table_bits = red_bits;
- }
- if (blue_bits < 8 && blue_table_bits != blue_bits) {
- build_scale_table(&blue_scale_table, blue_bits);
- blue_table_bits = blue_bits;
- }
- if (green_bits < 8 && green_table_bits != green_bits) {
- build_scale_table(&green_scale_table, green_bits);
- green_table_bits = green_bits;
- }
-
- int r, g, b;
-
- QRgb *dst;
- uchar *src;
- uint pixel;
- int bppc = xi->bits_per_pixel;
-
- if (bppc > 8 && xi->byte_order == LSBFirst)
- bppc++;
-
- for (int y = 0; y < xi->height; ++y) {
- uchar* asrc = x11_mask ? alpha.scanLine(y) : 0;
- dst = (QRgb *)image.scanLine(y);
- src = (uchar *)xi->data + xi->bytes_per_line*y;
- for (int x = 0; x < xi->width; x++) {
- switch (bppc) {
- case 8:
- pixel = *src++;
- break;
- case 16: // 16 bit MSB
- pixel = src[1] | (uint)src[0] << 8;
- src += 2;
- break;
- case 17: // 16 bit LSB
- pixel = src[0] | (uint)src[1] << 8;
- src += 2;
- break;
- case 24: // 24 bit MSB
- pixel = src[2] | (uint)src[1] << 8 | (uint)src[0] << 16;
- src += 3;
- break;
- case 25: // 24 bit LSB
- pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16;
- src += 3;
- break;
- case 32: // 32 bit MSB
- pixel = src[3] | (uint)src[2] << 8 | (uint)src[1] << 16 | (uint)src[0] << 24;
- src += 4;
- break;
- case 33: // 32 bit LSB
- pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16 | (uint)src[3] << 24;
- src += 4;
- break;
- default: // should not really happen
- x = xi->width; // leave loop
- y = xi->height;
- pixel = 0; // eliminate compiler warning
- qWarning("QPixmap::convertToImage: Invalid depth %d", bppc);
- }
- if (red_shift > 0)
- r = (pixel & red_mask) >> red_shift;
- else
- r = (pixel & red_mask) << -red_shift;
- if (green_shift > 0)
- g = (pixel & green_mask) >> green_shift;
- else
- g = (pixel & green_mask) << -green_shift;
- if (blue_shift > 0)
- b = (pixel & blue_mask) >> blue_shift;
- else
- b = (pixel & blue_mask) << -blue_shift;
-
- if (red_bits < 8)
- r = red_scale_table[r];
- if (green_bits < 8)
- g = green_scale_table[g];
- if (blue_bits < 8)
- b = blue_scale_table[b];
-
- if (x11_mask) {
- if (ale) {
- *dst++ = (asrc[x >> 3] & (1 << (x & 7))) ? qRgba(r, g, b, 0xff) : 0;
- } else {
- *dst++ = (asrc[x >> 3] & (0x80 >> (x & 7))) ? qRgba(r, g, b, 0xff) : 0;
- }
- } else {
- *dst++ = qRgb(r, g, b);
- }
- }
- }
- } else if (xi->bits_per_pixel == d) { // compatible depth
- char *xidata = xi->data; // copy each scanline
- qsizetype bpl = qMin(image.bytesPerLine(),xi->bytes_per_line);
- for (int y=0; y<xi->height; y++) {
- memcpy(image.scanLine(y), xidata, bpl);
- xidata += xi->bytes_per_line;
- }
- } else {
- /* Typically 2 or 4 bits display depth */
- qWarning("QPixmap::convertToImage: Display not supported (bpp=%d)",
- xi->bits_per_pixel);
- return QImage();
- }
-
- if (d == 1) { // bitmap
- image.setColorCount(2);
- image.setColor(0, qRgb(255,255,255));
- image.setColor(1, qRgb(0,0,0));
- } else if (!trucol) { // pixmap with colormap
- uchar *p;
- uchar *end;
- uchar use[256]; // pixel-in-use table
- uchar pix[256]; // pixel translation table
- int ncols;
- memset(use, 0, 256);
- memset(pix, 0, 256);
- qsizetype bpl = image.bytesPerLine();
-
- if (x11_mask) { // which pixels are used?
- for (int i = 0; i < xi->height; i++) {
- uchar* asrc = alpha.scanLine(i);
- p = image.scanLine(i);
- if (ale) {
- for (int x = 0; x < xi->width; x++) {
- if (asrc[x >> 3] & (1 << (x & 7)))
- use[*p] = 1;
- ++p;
- }
- } else {
- for (int x = 0; x < xi->width; x++) {
- if (asrc[x >> 3] & (0x80 >> (x & 7)))
- use[*p] = 1;
- ++p;
- }
- }
- }
- } else {
- for (int i = 0; i < xi->height; i++) {
- p = image.scanLine(i);
- end = p + bpl;
- while (p < end)
- use[*p++] = 1;
- }
- }
- ncols = 0;
- for (int i = 0; i < 256; i++) { // build translation table
- if (use[i])
- pix[i] = ncols++;
- }
- for (int i = 0; i < xi->height; i++) { // translate pixels
- p = image.scanLine(i);
- end = p + bpl;
- while (p < end) {
- *p = pix[*p];
- p++;
- }
- }
- if (x11_mask) {
- int trans;
- if (ncols < 256) {
- trans = ncols++;
- image.setColorCount(ncols); // create color table
- image.setColor(trans, 0x00000000);
- } else {
- image.setColorCount(ncols); // create color table
- // oh dear... no spare "transparent" pixel.
- // use first pixel in image (as good as any).
- trans = image.scanLine(0)[0];
- }
- for (int i = 0; i < xi->height; i++) {
- uchar* asrc = alpha.scanLine(i);
- p = image.scanLine(i);
- if (ale) {
- for (int x = 0; x < xi->width; x++) {
- if (!(asrc[x >> 3] & (1 << (x & 7))))
- *p = trans;
- ++p;
- }
- } else {
- for (int x = 0; x < xi->width; x++) {
- if (!(asrc[x >> 3] & (1 << (7 -(x & 7)))))
- *p = trans;
- ++p;
- }
- }
- }
- } else {
- image.setColorCount(ncols); // create color table
- }
- QList<QColor> colors = QXcbColormap::instance(xinfo.screen()).colormap();
- int j = 0;
- for (int i=0; i<colors.size(); i++) { // translate pixels
- if (use[i])
- image.setColor(j++, 0xff000000 | colors.at(i).rgb());
- }
- }
-
- return image;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h
deleted file mode 100644
index 0755a34b4a8..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <QBitmap>
-#include <QPixmap>
-
-#include <qpa/qplatformpixmap.h>
-#include "qxcbnativepainting.h"
-
-typedef unsigned long XID;
-typedef XID Drawable;
-typedef XID Picture;
-typedef XID Pixmap;
-
-QT_BEGIN_NAMESPACE
-
-class QX11PaintEngine;
-struct QXImageWrapper;
-
-class QX11PlatformPixmap : public QPlatformPixmap
-{
-public:
- QX11PlatformPixmap(PixelType pixelType);
- ~QX11PlatformPixmap();
-
- QPlatformPixmap *createCompatiblePlatformPixmap() const override;
- void resize(int width, int height) override;
- void fromImage(const QImage &img, Qt::ImageConversionFlags flags) override;
- void copy(const QPlatformPixmap *data, const QRect &rect) override;
- bool scroll(int dx, int dy, const QRect &rect) override;
- int metric(QPaintDevice::PaintDeviceMetric metric) const override;
- void fill(const QColor &fillColor) override;
- QBitmap mask() const override;
- void setMask(const QBitmap &mask) override;
- bool hasAlphaChannel() const override;
- QPixmap transformed(const QTransform &matrix, Qt::TransformationMode mode) const override;
- QImage toImage() const override;
- QImage toImage(const QRect &rect) const override;
- QPaintEngine *paintEngine() const override;
- qreal devicePixelRatio() const override;
- void setDevicePixelRatio(qreal scaleFactor) override;
-
- inline Drawable handle() const { return hd; }
- inline Picture x11PictureHandle() const { return picture; }
- inline const QXcbX11Info *x11_info() const { return &xinfo; }
-
- Pixmap x11ConvertToDefaultDepth();
- static XID createBitmapFromImage(const QImage &image);
-
-#if QT_CONFIG(xrender)
- void convertToARGB32(bool preserveContents = true);
-#endif
-
- bool isBackingStore() const;
- void setIsBackingStore(bool on);
-private:
- friend class QX11PaintEngine;
- friend const QXcbX11Info &qt_x11Info(const QPixmap &pixmap);
- friend void qt_x11SetScreen(QPixmap &pixmap, int screen);
-
- void release();
- QImage toImage(const QXImageWrapper &xi, const QRect &rect) const;
- QBitmap mask_to_bitmap(int screen) const;
- static Pixmap bitmap_to_mask(const QBitmap &, int screen);
- void bitmapFromImage(const QImage &image);
- bool canTakeQImageFromXImage(const QXImageWrapper &xi) const;
- QImage takeQImageFromXImage(const QXImageWrapper &xi) const;
-
- Pixmap hd = 0;
-
- enum Flag {
- NoFlags = 0x0,
- Uninitialized = 0x1,
- Readonly = 0x2,
- InvertedWhenBoundToTexture = 0x4,
- GlSurfaceCreatedWithAlpha = 0x8,
- IsBackingStore = 0x10
- };
- uint flags;
-
- QXcbX11Info xinfo;
- Pixmap x11_mask;
- Picture picture;
- Picture mask_picture;
- Pixmap hd2; // sorted in the default display depth
- //QPixmap::ShareMode share_mode;
- qreal dpr;
-
- QX11PaintEngine *pengine;
-};
-
-inline QX11PlatformPixmap *qt_x11Pixmap(const QPixmap &pixmap)
-{
- return (pixmap.handle() && pixmap.handle()->classId() == QPlatformPixmap::X11Class)
- ? static_cast<QX11PlatformPixmap *>(pixmap.handle())
- : nullptr;
-}
-
-inline Picture qt_x11PictureHandle(const QPixmap &pixmap)
-{
- if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap))
- return pm->x11PictureHandle();
-
- return 0;
-}
-
-inline Pixmap qt_x11PixmapHandle(const QPixmap &pixmap)
-{
- if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap))
- return pm->handle();
-
- return 0;
-}
-
-inline const QXcbX11Info &qt_x11Info(const QPixmap &pixmap)
-{
- if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) {
- return pm->xinfo;
- } else {
- static QXcbX11Info nullX11Info;
- return nullX11Info;
- }
-}
-
-int qt_x11SetDefaultScreen(int screen);
-void qt_x11SetScreen(QPixmap &pixmap, int screen);
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h b/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h
deleted file mode 100644
index e1e31722d76..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h
+++ /dev/null
@@ -1,278 +0,0 @@
-// 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
-
-#pragma once
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of other Qt classes. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtCore/qrect.h>
-#include <QtGui/private/qdatabuffer_p.h>
-
-QT_BEGIN_NAMESPACE
-
-/* based on sutherland-hodgman line-by-line clipping, as described in
- Computer Graphics and Principles */
-template <typename InType, typename OutType, typename CastType> class QPolygonClipper
-{
-public:
- QPolygonClipper() :
- buffer1(0), buffer2(0)
- {
- x1 = y1 = x2 = y2 = 0;
- }
-
- ~QPolygonClipper()
- {
- }
-
- void setBoundingRect(const QRect bounds)
- {
- x1 = bounds.x();
- x2 = bounds.x() + bounds.width();
- y1 = bounds.y();
- y2 = bounds.y() + bounds.height();
- }
-
- QRect boundingRect()
- {
- return QRect(QPoint(x1, y1), QPoint(x2, y2));
- }
-
- inline OutType intersectLeft(const OutType &p1, const OutType &p2)
- {
- OutType t;
- qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x);
- t.x = x1;
- t.y = static_cast<CastType>(p2.y + (x1 - p2.x) * dy);
- return t;
- }
-
-
- inline OutType intersectRight(const OutType &p1, const OutType &p2)
- {
- OutType t;
- qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x);
- t.x = x2;
- t.y = static_cast<CastType>(p2.y + (x2 - p2.x) * dy);
- return t;
- }
-
-
- inline OutType intersectTop(const OutType &p1, const OutType &p2)
- {
- OutType t;
- qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y);
- t.x = static_cast<CastType>(p2.x + (y1 - p2.y) * dx);
- t.y = y1;
- return t;
- }
-
-
- inline OutType intersectBottom(const OutType &p1, const OutType &p2)
- {
- OutType t;
- qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y);
- t.x = static_cast<CastType>(p2.x + (y2 - p2.y) * dx);
- t.y = y2;
- return t;
- }
-
-
- void clipPolygon(const InType *inPoints, int inCount, OutType **outPoints, int *outCount,
- bool closePolygon = true)
- {
- Q_ASSERT(outPoints);
- Q_ASSERT(outCount);
-
- if (inCount < 2) {
- *outCount = 0;
- return;
- }
-
- buffer1.reset();
- buffer2.reset();
-
- QDataBuffer<OutType> *source = &buffer1;
- QDataBuffer<OutType> *clipped = &buffer2;
-
- // Gather some info since we are iterating through the points anyway..
- bool doLeft = false, doRight = false, doTop = false, doBottom = false;
- OutType ot;
- for (int i=0; i<inCount; ++i) {
- ot = inPoints[i];
- clipped->add(ot);
-
- if (ot.x < x1)
- doLeft = true;
- else if (ot.x > x2)
- doRight = true;
- if (ot.y < y1)
- doTop = true;
- else if (ot.y > y2)
- doBottom = true;
- }
-
- if (doLeft && clipped->size() > 1) {
- QDataBuffer<OutType> *tmp = source;
- source = clipped;
- clipped = tmp;
- clipped->reset();
- int lastPos, start;
- if (closePolygon) {
- lastPos = source->size() - 1;
- start = 0;
- } else {
- lastPos = 0;
- start = 1;
- if (source->at(0).x >= x1)
- clipped->add(source->at(0));
- }
- for (int i=start; i<inCount; ++i) {
- const OutType &cpt = source->at(i);
- const OutType &ppt = source->at(lastPos);
-
- if (cpt.x >= x1) {
- if (ppt.x >= x1) {
- clipped->add(cpt);
- } else {
- clipped->add(intersectLeft(cpt, ppt));
- clipped->add(cpt);
- }
- } else if (ppt.x >= x1) {
- clipped->add(intersectLeft(cpt, ppt));
- }
- lastPos = i;
- }
- }
-
- if (doRight && clipped->size() > 1) {
- QDataBuffer<OutType> *tmp = source;
- source = clipped;
- clipped = tmp;
- clipped->reset();
- int lastPos, start;
- if (closePolygon) {
- lastPos = source->size() - 1;
- start = 0;
- } else {
- lastPos = 0;
- start = 1;
- if (source->at(0).x <= x2)
- clipped->add(source->at(0));
- }
- for (int i=start; i<source->size(); ++i) {
- const OutType &cpt = source->at(i);
- const OutType &ppt = source->at(lastPos);
-
- if (cpt.x <= x2) {
- if (ppt.x <= x2) {
- clipped->add(cpt);
- } else {
- clipped->add(intersectRight(cpt, ppt));
- clipped->add(cpt);
- }
- } else if (ppt.x <= x2) {
- clipped->add(intersectRight(cpt, ppt));
- }
-
- lastPos = i;
- }
-
- }
-
- if (doTop && clipped->size() > 1) {
- QDataBuffer<OutType> *tmp = source;
- source = clipped;
- clipped = tmp;
- clipped->reset();
- int lastPos, start;
- if (closePolygon) {
- lastPos = source->size() - 1;
- start = 0;
- } else {
- lastPos = 0;
- start = 1;
- if (source->at(0).y >= y1)
- clipped->add(source->at(0));
- }
- for (int i=start; i<source->size(); ++i) {
- const OutType &cpt = source->at(i);
- const OutType &ppt = source->at(lastPos);
-
- if (cpt.y >= y1) {
- if (ppt.y >= y1) {
- clipped->add(cpt);
- } else {
- clipped->add(intersectTop(cpt, ppt));
- clipped->add(cpt);
- }
- } else if (ppt.y >= y1) {
- clipped->add(intersectTop(cpt, ppt));
- }
-
- lastPos = i;
- }
- }
-
- if (doBottom && clipped->size() > 1) {
- QDataBuffer<OutType> *tmp = source;
- source = clipped;
- clipped = tmp;
- clipped->reset();
- int lastPos, start;
- if (closePolygon) {
- lastPos = source->size() - 1;
- start = 0;
- } else {
- lastPos = 0;
- start = 1;
- if (source->at(0).y <= y2)
- clipped->add(source->at(0));
- }
- for (int i=start; i<source->size(); ++i) {
- const OutType &cpt = source->at(i);
- const OutType &ppt = source->at(lastPos);
-
- if (cpt.y <= y2) {
- if (ppt.y <= y2) {
- clipped->add(cpt);
- } else {
- clipped->add(intersectBottom(cpt, ppt));
- clipped->add(cpt);
- }
- } else if (ppt.y <= y2) {
- clipped->add(intersectBottom(cpt, ppt));
- }
- lastPos = i;
- }
- }
-
- if (closePolygon && clipped->size() > 0) {
- // close clipped polygon
- if (clipped->at(0).x != clipped->at(clipped->size()-1).x ||
- clipped->at(0).y != clipped->at(clipped->size()-1).y) {
- OutType ot = clipped->at(0);
- clipped->add(ot);
- }
- }
- *outCount = clipped->size();
- *outPoints = clipped->data();
- }
-
-private:
- int x1, x2, y1, y2;
- QDataBuffer<OutType> buffer1;
- QDataBuffer<OutType> buffer2;
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h
deleted file mode 100644
index 2986b8f1453..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h
+++ /dev/null
@@ -1,163 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#define register /* C++17 deprecated register */
-#include <X11/Xlib.h>
-#include <X11/Xatom.h>
-#undef register
-
-#if QT_CONFIG(xrender)
-# include "qtessellator_p.h"
-# include <X11/extensions/Xrender.h>
-#endif
-
-#if QT_CONFIG(fontconfig)
-#include <fontconfig/fontconfig.h>
-#endif
-
-#if defined(FT_LCD_FILTER_H)
-#include FT_LCD_FILTER_H
-#endif
-
-#if defined(FC_LCD_FILTER)
-
-#ifndef FC_LCD_FILTER_NONE
-#define FC_LCD_FILTER_NONE FC_LCD_NONE
-#endif
-
-#ifndef FC_LCD_FILTER_DEFAULT
-#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT
-#endif
-
-#ifndef FC_LCD_FILTER_LIGHT
-#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT
-#endif
-
-#ifndef FC_LCD_FILTER_LEGACY
-#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY
-#endif
-
-#endif
-
-QT_BEGIN_NAMESPACE
-
-// rename a couple of X defines to get rid of name clashes
-// resolve the conflict between X11's FocusIn and QEvent::FocusIn
-enum {
- XFocusOut = FocusOut,
- XFocusIn = FocusIn,
- XKeyPress = KeyPress,
- XKeyRelease = KeyRelease,
- XNone = None,
- XRevertToParent = RevertToParent,
- XGrayScale = GrayScale,
- XCursorShape = CursorShape,
-};
-#undef FocusOut
-#undef FocusIn
-#undef KeyPress
-#undef KeyRelease
-#undef None
-#undef RevertToParent
-#undef GrayScale
-#undef CursorShape
-
-#ifdef FontChange
-#undef FontChange
-#endif
-
-Q_DECLARE_TYPEINFO(XPoint, Q_PRIMITIVE_TYPE);
-Q_DECLARE_TYPEINFO(XRectangle, Q_PRIMITIVE_TYPE);
-Q_DECLARE_TYPEINFO(XChar2b, Q_PRIMITIVE_TYPE);
-#if QT_CONFIG(xrender)
-Q_DECLARE_TYPEINFO(XGlyphElt32, Q_PRIMITIVE_TYPE);
-#endif
-
-struct QX11InfoData;
-
-enum DesktopEnvironment {
- DE_UNKNOWN,
- DE_KDE,
- DE_GNOME,
- DE_CDE,
- DE_MEEGO_COMPOSITOR,
- DE_4DWM
-};
-
-struct QXcbX11Data {
- Display *display = nullptr;
-
- // true if Qt is compiled w/ RENDER support and RENDER is supported on the connected Display
- bool use_xrender = false;
- int xrender_major = 0;
- int xrender_version = 0;
-
- QX11InfoData *screens = nullptr;
- Visual **argbVisuals = nullptr;
- Colormap *argbColormaps = nullptr;
- int screenCount = 0;
- int defaultScreen = 0;
-
- // options
- int visual_class = 0;
- int visual_id = 0;
- int color_count = 0;
- bool custom_cmap = false;
-
- // outside visual/colormap
- Visual *visual = nullptr;
- Colormap colormap = 0;
-
-#if QT_CONFIG(xrender)
- enum { solid_fill_count = 16 };
- struct SolidFills {
- XRenderColor color;
- int screen;
- Picture picture;
- } solid_fills[solid_fill_count];
- enum { pattern_fill_count = 16 };
- struct PatternFills {
- XRenderColor color;
- XRenderColor bg_color;
- int screen;
- int style;
- bool opaque;
- Picture picture;
- } pattern_fills[pattern_fill_count];
- Picture getSolidFill(int screen, const QColor &c);
- XRenderColor preMultiply(const QColor &c);
-#endif
-
- bool fc_antialias = true;
- int fc_hint_style = 0;
-
- DesktopEnvironment desktopEnvironment = DE_GNOME;
-};
-
-extern QXcbX11Data *qt_x11Data;
-#define X11 qt_x11Data
-
-struct QX11InfoData {
- int screen;
- int dpiX;
- int dpiY;
- int depth;
- int cells;
- Colormap colormap;
- Visual *visual;
- bool defaultColormap;
- bool defaultVisual;
- int subpixel = 0;
-};
-
-template <class T>
-constexpr inline int lowest_bit(T v) noexcept
-{
- int result = qCountTrailingZeroBits(v);
- return ((result >> 3) == sizeof(T)) ? -1 : result;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
deleted file mode 100644
index dd83f8852b7..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
+++ /dev/null
@@ -1,1466 +0,0 @@
-// Copyright (C) 2018 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
-
-#include "qtessellator_p.h"
-
-#include <QRect>
-#include <QList>
-#include <QMap>
-#include <QDebug>
-
-#include <qmath.h>
-#include <limits.h>
-#include <algorithm>
-
-QT_BEGIN_NAMESPACE
-
-//#define DEBUG
-#ifdef DEBUG
-#define QDEBUG qDebug
-#else
-#define QDEBUG if (1){} else qDebug
-#endif
-
-static const bool emit_clever = true;
-static const bool mark_clever = false;
-
-enum VertexFlags {
- LineBeforeStarts = 0x1,
- LineBeforeEnds = 0x2,
- LineBeforeHorizontal = 0x4,
- LineAfterStarts = 0x8,
- LineAfterEnds = 0x10,
- LineAfterHorizontal = 0x20
-};
-
-
-
-class QTessellatorPrivate {
-public:
- struct Vertices;
-
- QTessellatorPrivate() {}
-
- QRectF collectAndSortVertices(const QPointF *points, int *maxActiveEdges);
- void cancelCoincidingEdges();
-
- void emitEdges(QTessellator *tessellator);
- void processIntersections();
- void removeEdges();
- void addEdges();
- void addIntersections();
-
- struct Vertex : public QTessellator::Vertex
- {
- int flags;
- };
-
- struct Intersection
- {
- Q27Dot5 y;
- int edge;
- bool operator <(const Intersection &other) const {
- if (y != other.y)
- return y < other.y;
- return edge < other.edge;
- }
- };
- struct IntersectionLink
- {
- int next;
- int prev;
- };
- typedef QMap<Intersection, IntersectionLink> Intersections;
-
- struct Edge {
- Edge(const Vertices &v, int _edge);
- int edge;
- const Vertex *v0;
- const Vertex *v1;
- Q27Dot5 y_left;
- Q27Dot5 y_right;
- signed int winding : 8;
- bool mark;
- bool free;
- bool intersect_left;
- bool intersect_right;
- bool isLeftOf(const Edge &other, Q27Dot5 y) const;
- Q27Dot5 positionAt(Q27Dot5 y) const;
- bool intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const;
-
- };
-
- class EdgeSorter
- {
- public:
- EdgeSorter(int _y) : y(_y) {}
- bool operator() (const Edge *e1, const Edge *e2);
- int y;
- };
-
- class Scanline {
- public:
- Scanline();
- ~Scanline();
-
- void init(int maxActiveEdges);
- void done();
-
- int findEdgePosition(Q27Dot5 x, Q27Dot5 y) const;
- int findEdgePosition(const Edge &e) const;
- int findEdge(int edge) const;
- void clearMarks();
-
- void swap(int p1, int p2) {
- Edge *tmp = edges[p1];
- edges[p1] = edges[p2];
- edges[p2] = tmp;
- }
- void insert(int pos, const Edge &e);
- void removeAt(int pos);
- void markEdges(int pos1, int pos2);
-
- void prepareLine();
- void lineDone();
-
- Edge **old;
- int old_size;
-
- Edge **edges;
- int size;
-
- private:
- Edge *edge_table;
- int first_unused;
- int max_edges;
- enum { default_alloc = 32 };
- };
-
- struct Vertices {
- enum { default_alloc = 128 };
- Vertices();
- ~Vertices();
- void init(int maxVertices);
- void done();
- Vertex *storage;
- Vertex **sorted;
-
- Vertex *operator[] (int i) { return storage + i; }
- const Vertex *operator[] (int i) const { return storage + i; }
- int position(const Vertex *v) const {
- return v - storage;
- }
- Vertex *next(Vertex *v) {
- ++v;
- if (v == storage + nPoints)
- v = storage;
- return v;
- }
- const Vertex *next(const Vertex *v) const {
- ++v;
- if (v == storage + nPoints)
- v = storage;
- return v;
- }
- int nextPos(const Vertex *v) const {
- ++v;
- if (v == storage + nPoints)
- return 0;
- return v - storage;
- }
- Vertex *prev(Vertex *v) {
- if (v == storage)
- v = storage + nPoints;
- --v;
- return v;
- }
- const Vertex *prev(const Vertex *v) const {
- if (v == storage)
- v = storage + nPoints;
- --v;
- return v;
- }
- int prevPos(const Vertex *v) const {
- if (v == storage)
- v = storage + nPoints;
- --v;
- return v - storage;
- }
- int nPoints;
- int allocated;
- };
- Vertices vertices;
- Intersections intersections;
- Scanline scanline;
- bool winding;
- Q27Dot5 y;
- int currentVertex;
-
-private:
- void addIntersection(const Edge *e1, const Edge *e2);
- bool edgeInChain(Intersection i, int edge);
-};
-
-
-QTessellatorPrivate::Edge::Edge(const QTessellatorPrivate::Vertices &vertices, int edge)
-{
- this->edge = edge;
- intersect_left = intersect_right = true;
- mark = false;
- free = false;
-
- v0 = vertices[edge];
- v1 = vertices.next(v0);
-
- Q_ASSERT(v0->y != v1->y);
-
- if (v0->y > v1->y) {
- qSwap(v0, v1);
- winding = -1;
- } else {
- winding = 1;
- }
- y_left = y_right = v0->y;
-}
-
-// This is basically the algorithm from graphics gems. The algorithm
-// is cubic in the coordinates at one place. Since we use 64bit
-// integers, this implies, that the allowed range for our coordinates
-// is limited to 21 bits. With 5 bits behind the decimal, this
-// implies that differences in coordaintes can range from 2*SHORT_MIN
-// to 2*SHORT_MAX, giving us efficiently a coordinate system from
-// SHORT_MIN to SHORT_MAX.
-//
-
-// WARNING: It's absolutely critical that the intersect() and isLeftOf() methods use
-// exactly the same algorithm to calculate yi. It's also important to be sure the algorithms
-// are transitive (ie. the conditions below are true for all input data):
-//
-// a.intersect(b) == b.intersect(a)
-// a.isLeftOf(b) != b.isLeftOf(a)
-//
-// This is tricky to get right, so be very careful when changing anything in here!
-
-static inline bool sameSign(qint64 a, qint64 b) {
- return (((qint64) ((quint64) a ^ (quint64) b)) >= 0 );
-}
-
-bool QTessellatorPrivate::Edge::intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const
-{
- qint64 a1 = v1->y - v0->y;
- qint64 b1 = v0->x - v1->x;
-
- qint64 a2 = other.v1->y - other.v0->y;
- qint64 b2 = other.v0->x - other.v1->x;
-
- qint64 det = a1 * b2 - a2 * b1;
- if (det == 0)
- return false;
-
- qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y;
-
- qint64 r3 = a1 * other.v0->x + b1 * other.v0->y + c1;
- qint64 r4 = a1 * other.v1->x + b1 * other.v1->y + c1;
-
- // Check signs of r3 and r4. If both point 3 and point 4 lie on
- // same side of line 1, the line segments do not intersect.
- QDEBUG() << " " << r3 << r4;
- if (r3 != 0 && r4 != 0 && sameSign( r3, r4 ))
- return false;
-
- qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y;
-
- qint64 r1 = a2 * v0->x + b2 * v0->y + c2;
- qint64 r2 = a2 * v1->x + b2 * v1->y + c2;
-
- // Check signs of r1 and r2. If both point 1 and point 2 lie
- // on same side of second line segment, the line segments do not intersect.
- QDEBUG() << " " << r1 << r2;
- if (r1 != 0 && r2 != 0 && sameSign( r1, r2 ))
- return false;
-
- // The det/2 is to get rounding instead of truncating. It
- // is added or subtracted to the numerator, depending upon the
- // sign of the numerator.
- qint64 offset = det < 0 ? -det : det;
- offset >>= 1;
-
- qint64 num = a2 * c1 - a1 * c2;
- *y = ( num < 0 ? num - offset : num + offset ) / det;
-
- *det_positive = (det > 0);
-
- return true;
-}
-
-#undef SAME_SIGNS
-
-bool QTessellatorPrivate::Edge::isLeftOf(const Edge &other, Q27Dot5 y) const
-{
-// QDEBUG() << "isLeftOf" << edge << other.edge << y;
- qint64 a1 = v1->y - v0->y;
- qint64 b1 = v0->x - v1->x;
- qint64 a2 = other.v1->y - other.v0->y;
- qint64 b2 = other.v0->x - other.v1->x;
-
- qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y;
-
- qint64 det = a1 * b2 - a2 * b1;
- if (det == 0) {
- // lines are parallel. Only need to check side of one point
- // fixed ordering for coincident edges
- qint64 r1 = a2 * v0->x + b2 * v0->y + c2;
-// QDEBUG() << "det = 0" << r1;
- if (r1 == 0)
- return edge < other.edge;
- return (r1 < 0);
- }
-
- // not parallel, need to find the y coordinate of the intersection point
- qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y;
-
- qint64 offset = det < 0 ? -det : det;
- offset >>= 1;
-
- qint64 num = a2 * c1 - a1 * c2;
- qint64 yi = ( num < 0 ? num - offset : num + offset ) / det;
-// QDEBUG() << " num=" << num << "offset=" << offset << "det=" << det;
-
- return ((yi > y) ^ (det < 0));
-}
-
-static inline bool compareVertex(const QTessellatorPrivate::Vertex *p1,
- const QTessellatorPrivate::Vertex *p2)
-{
- if (p1->y == p2->y) {
- if (p1->x == p2->x)
- return p1 < p2;
- return p1->x < p2->x;
- }
- return p1->y < p2->y;
-}
-
-Q27Dot5 QTessellatorPrivate::Edge::positionAt(Q27Dot5 y) const
-{
- if (y == v0->y)
- return v0->x;
- else if (y == v1->y)
- return v1->x;
-
- qint64 d = v1->x - v0->x;
- return (v0->x + d*(y - v0->y)/(v1->y-v0->y));
-}
-
-bool QTessellatorPrivate::EdgeSorter::operator() (const Edge *e1, const Edge *e2)
-{
- return e1->isLeftOf(*e2, y);
-}
-
-
-QTessellatorPrivate::Scanline::Scanline()
-{
- edges = 0;
- edge_table = 0;
- old = 0;
-}
-
-void QTessellatorPrivate::Scanline::init(int maxActiveEdges)
-{
- maxActiveEdges *= 2;
- if (!edges || maxActiveEdges > default_alloc) {
- max_edges = maxActiveEdges;
- int s = qMax(maxActiveEdges + 1, default_alloc + 1);
- edges = q_check_ptr((Edge **)realloc(edges, s*sizeof(Edge *)));
- edge_table = q_check_ptr((Edge *)realloc(edge_table, s*sizeof(Edge)));
- old = q_check_ptr((Edge **)realloc(old, s*sizeof(Edge *)));
- }
- size = 0;
- old_size = 0;
- first_unused = 0;
- for (int i = 0; i < maxActiveEdges; ++i)
- edge_table[i].edge = i+1;
- edge_table[maxActiveEdges].edge = -1;
-}
-
-void QTessellatorPrivate::Scanline::done()
-{
- if (max_edges > default_alloc) {
- free(edges);
- free(old);
- free(edge_table);
- edges = 0;
- old = 0;
- edge_table = 0;
- }
-}
-
-QTessellatorPrivate::Scanline::~Scanline()
-{
- free(edges);
- free(old);
- free(edge_table);
-}
-
-int QTessellatorPrivate::Scanline::findEdgePosition(Q27Dot5 x, Q27Dot5 y) const
-{
- int min = 0;
- int max = size - 1;
- while (min < max) {
- int pos = min + ((max - min + 1) >> 1);
- Q27Dot5 ax = edges[pos]->positionAt(y);
- if (ax > x) {
- max = pos - 1;
- } else {
- min = pos;
- }
- }
- return min;
-}
-
-int QTessellatorPrivate::Scanline::findEdgePosition(const Edge &e) const
-{
-// qDebug() << ">> findEdgePosition";
- int min = 0;
- int max = size;
- while (min < max) {
- int pos = min + ((max - min) >> 1);
-// qDebug() << " " << min << max << pos << edges[pos]->isLeftOf(e, e.y0);
- if (edges[pos]->isLeftOf(e, e.v0->y)) {
- min = pos + 1;
- } else {
- max = pos;
- }
- }
-// qDebug() << "<< findEdgePosition got" << min;
- return min;
-}
-
-int QTessellatorPrivate::Scanline::findEdge(int edge) const
-{
- for (int i = 0; i < size; ++i) {
- int item_edge = edges[i]->edge;
- if (item_edge == edge)
- return i;
- }
- //Q_ASSERT(false);
- return -1;
-}
-
-void QTessellatorPrivate::Scanline::clearMarks()
-{
- for (int i = 0; i < size; ++i) {
- edges[i]->mark = false;
- edges[i]->intersect_left = false;
- edges[i]->intersect_right = false;
- }
-}
-
-void QTessellatorPrivate::Scanline::prepareLine()
-{
- Edge **end = edges + size;
- Edge **e = edges;
- Edge **o = old;
- while (e < end) {
- *o = *e;
- ++o;
- ++e;
- }
- old_size = size;
-}
-
-void QTessellatorPrivate::Scanline::lineDone()
-{
- Edge **end = old + old_size;
- Edge **e = old;
- while (e < end) {
- if ((*e)->free) {
- (*e)->edge = first_unused;
- first_unused = (*e - edge_table);
- }
- ++e;
- }
-}
-
-void QTessellatorPrivate::Scanline::insert(int pos, const Edge &e)
-{
- Edge *edge = edge_table + first_unused;
- first_unused = edge->edge;
- Q_ASSERT(first_unused != -1);
- *edge = e;
- memmove(edges + pos + 1, edges + pos, (size - pos)*sizeof(Edge *));
- edges[pos] = edge;
- ++size;
-}
-
-void QTessellatorPrivate::Scanline::removeAt(int pos)
-{
- Edge *e = edges[pos];
- e->free = true;
- --size;
- memmove(edges + pos, edges + pos + 1, (size - pos)*sizeof(Edge *));
-}
-
-void QTessellatorPrivate::Scanline::markEdges(int pos1, int pos2)
-{
- if (pos2 < pos1)
- return;
-
- for (int i = pos1; i <= pos2; ++i)
- edges[i]->mark = true;
-}
-
-
-QTessellatorPrivate::Vertices::Vertices()
-{
- storage = 0;
- sorted = 0;
- allocated = 0;
- nPoints = 0;
-}
-
-QTessellatorPrivate::Vertices::~Vertices()
-{
- if (storage) {
- free(storage);
- free(sorted);
- }
-}
-
-void QTessellatorPrivate::Vertices::init(int maxVertices)
-{
- if (!storage || maxVertices > allocated) {
- int size = qMax((int)default_alloc, maxVertices);
- storage = q_check_ptr((Vertex *)realloc(storage, size*sizeof(Vertex)));
- sorted = q_check_ptr((Vertex **)realloc(sorted, size*sizeof(Vertex *)));
- allocated = maxVertices;
- }
-}
-
-void QTessellatorPrivate::Vertices::done()
-{
- if (allocated > default_alloc) {
- free(storage);
- free(sorted);
- storage = 0;
- sorted = 0;
- allocated = 0;
- }
-}
-
-
-
-static inline void fillTrapezoid(Q27Dot5 y1, Q27Dot5 y2, int left, int right,
- const QTessellatorPrivate::Vertices &vertices,
- QTessellator::Trapezoid *trap)
-{
- trap->top = y1;
- trap->bottom = y2;
- const QTessellatorPrivate::Vertex *v = vertices[left];
- trap->topLeft = v;
- trap->bottomLeft = vertices.next(v);
- if (trap->topLeft->y > trap->bottomLeft->y)
- qSwap(trap->topLeft,trap->bottomLeft);
- v = vertices[right];
- trap->topRight = v;
- trap->bottomRight = vertices.next(v);
- if (trap->topRight->y > trap->bottomRight->y)
- qSwap(trap->topRight, trap->bottomRight);
-}
-
-QRectF QTessellatorPrivate::collectAndSortVertices(const QPointF *points, int *maxActiveEdges)
-{
- *maxActiveEdges = 0;
- Vertex *v = vertices.storage;
- Vertex **vv = vertices.sorted;
-
- qreal xmin(points[0].x());
- qreal xmax(points[0].x());
- qreal ymin(points[0].y());
- qreal ymax(points[0].y());
-
- // collect vertex data
- Q27Dot5 y_prev = FloatToQ27Dot5(points[vertices.nPoints-1].y());
- Q27Dot5 x_next = FloatToQ27Dot5(points[0].x());
- Q27Dot5 y_next = FloatToQ27Dot5(points[0].y());
- int j = 0;
- int i = 0;
- while (i < vertices.nPoints) {
- Q27Dot5 y_curr = y_next;
-
- *vv = v;
-
- v->x = x_next;
- v->y = y_next;
- v->flags = 0;
-
- next_point:
-
- xmin = qMin(xmin, points[i+1].x());
- xmax = qMax(xmax, points[i+1].x());
- ymin = qMin(ymin, points[i+1].y());
- ymax = qMax(ymax, points[i+1].y());
-
- y_next = FloatToQ27Dot5(points[i+1].y());
- x_next = FloatToQ27Dot5(points[i+1].x());
-
- // skip vertices on top of each other
- if (v->x == x_next && v->y == y_next) {
- ++i;
- if (i < vertices.nPoints)
- goto next_point;
- Vertex *v0 = vertices.storage;
- v0->flags &= ~(LineBeforeStarts|LineBeforeEnds|LineBeforeHorizontal);
- if (y_prev < y_curr)
- v0->flags |= LineBeforeEnds;
- else if (y_prev > y_curr)
- v0->flags |= LineBeforeStarts;
- else
- v0->flags |= LineBeforeHorizontal;
- if ((v0->flags & (LineBeforeStarts|LineAfterStarts))
- && !(v0->flags & (LineAfterEnds|LineBeforeEnds)))
- *maxActiveEdges += 2;
- break;
- }
-
- if (y_prev < y_curr)
- v->flags |= LineBeforeEnds;
- else if (y_prev > y_curr)
- v->flags |= LineBeforeStarts;
- else
- v->flags |= LineBeforeHorizontal;
-
-
- if (y_curr < y_next)
- v->flags |= LineAfterStarts;
- else if (y_curr > y_next)
- v->flags |= LineAfterEnds;
- else
- v->flags |= LineAfterHorizontal;
- // ### could probably get better limit by looping over sorted list and counting down on ending edges
- if ((v->flags & (LineBeforeStarts|LineAfterStarts))
- && !(v->flags & (LineAfterEnds|LineBeforeEnds)))
- *maxActiveEdges += 2;
- y_prev = y_curr;
- ++v;
- ++vv;
- ++j;
- ++i;
- }
- vertices.nPoints = j;
-
- QDEBUG() << "maxActiveEdges=" << *maxActiveEdges;
- vv = vertices.sorted;
- std::sort(vv, vv + vertices.nPoints, compareVertex);
-
- return QRectF(xmin, ymin, xmax-xmin, ymax-ymin);
-}
-
-struct QCoincidingEdge {
- QTessellatorPrivate::Vertex *start;
- QTessellatorPrivate::Vertex *end;
- bool used;
- bool before;
-
- inline bool operator<(const QCoincidingEdge &e2) const
- {
- return end->y == e2.end->y ? end->x < e2.end->x : end->y < e2.end->y;
- }
-};
-
-static void cancelEdges(QCoincidingEdge &e1, QCoincidingEdge &e2)
-{
- if (e1.before) {
- e1.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal);
- e1.end->flags &= ~(LineAfterEnds|LineAfterHorizontal);
- } else {
- e1.start->flags &= ~(LineAfterStarts|LineAfterHorizontal);
- e1.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal);
- }
- if (e2.before) {
- e2.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal);
- e2.end->flags &= ~(LineAfterEnds|LineAfterHorizontal);
- } else {
- e2.start->flags &= ~(LineAfterStarts|LineAfterHorizontal);
- e2.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal);
- }
- e1.used = e2.used = true;
-}
-
-void QTessellatorPrivate::cancelCoincidingEdges()
-{
- Vertex **vv = vertices.sorted;
-
- QCoincidingEdge *tl = nullptr;
- int tlSize = 0;
-
- for (int i = 0; i < vertices.nPoints - 1; ++i) {
- Vertex *v = vv[i];
- int testListSize = 0;
- while (i < vertices.nPoints - 1) {
- Vertex *n = vv[i];
- if (v->x != n->x || v->y != n->y)
- break;
-
- if (testListSize > tlSize - 2) {
- tlSize = qMax(tlSize*2, 16);
- tl = q_check_ptr((QCoincidingEdge *)realloc(tl, tlSize*sizeof(QCoincidingEdge)));
- }
- if (n->flags & (LineBeforeStarts|LineBeforeHorizontal)) {
- tl[testListSize].start = n;
- tl[testListSize].end = vertices.prev(n);
- tl[testListSize].used = false;
- tl[testListSize].before = true;
- ++testListSize;
- }
- if (n->flags & (LineAfterStarts|LineAfterHorizontal)) {
- tl[testListSize].start = n;
- tl[testListSize].end = vertices.next(n);
- tl[testListSize].used = false;
- tl[testListSize].before = false;
- ++testListSize;
- }
- ++i;
- }
- if (!testListSize)
- continue;
-
- std::sort(tl, tl + testListSize);
-
-QT_WARNING_PUSH
-QT_WARNING_DISABLE_CLANG("-Wfor-loop-analysis")
- for (int j = 0; j < testListSize; ++j) {
- if (tl[j].used)
- continue;
-
- for (int k = j + 1; k < testListSize; ++k) {
- if (tl[j].end->x != tl[k].end->x
- || tl[j].end->y != tl[k].end->y
- || tl[k].used)
- break;
-
- if (!winding || tl[j].before != tl[k].before) {
- cancelEdges(tl[j], tl[k]);
- break;
- }
- ++k;
- }
- ++j;
- }
-QT_WARNING_POP
- }
- free(tl);
-}
-
-
-void QTessellatorPrivate::emitEdges(QTessellator *tessellator)
-{
- //QDEBUG() << "TRAPS:";
- if (!scanline.old_size)
- return;
-
- // emit edges
- if (winding) {
- // winding fill rule
- int w = 0;
-
- scanline.old[0]->y_left = y;
-
- for (int i = 0; i < scanline.old_size - 1; ++i) {
- Edge *left = scanline.old[i];
- Edge *right = scanline.old[i+1];
- w += left->winding;
-// qDebug() << "i=" << i << "edge->winding=" << left->winding << "winding=" << winding;
- if (w == 0) {
- left->y_right = y;
- right->y_left = y;
- } else if (!emit_clever || left->mark || right->mark) {
- Q27Dot5 top = qMax(left->y_right, right->y_left);
- if (top != y) {
- QTessellator::Trapezoid trap;
- fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap);
- tessellator->addTrap(trap);
-// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge;
- }
- right->y_left = y;
- left->y_right = y;
- }
- left->mark = false;
- }
- if (scanline.old[scanline.old_size - 1]->mark) {
- scanline.old[scanline.old_size - 1]->y_right = y;
- scanline.old[scanline.old_size - 1]->mark = false;
- }
- } else {
- // odd-even fill rule
- for (int i = 0; i < scanline.old_size; i += 2) {
- Edge *left = scanline.old[i];
- Edge *right = scanline.old[i+1];
- if (!emit_clever || left->mark || right->mark) {
- Q27Dot5 top = qMax(left->y_right, right->y_left);
- if (top != y) {
- QTessellator::Trapezoid trap;
- fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap);
- tessellator->addTrap(trap);
- }
-// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge;
- left->y_left = y;
- left->y_right = y;
- right->y_left = y;
- right->y_right = y;
- left->mark = right->mark = false;
- }
- }
- }
-}
-
-
-void QTessellatorPrivate::processIntersections()
-{
- QDEBUG() << "PROCESS INTERSECTIONS";
- // process intersections
- while (!intersections.isEmpty()) {
- Intersections::iterator it = intersections.begin();
- if (it.key().y != y)
- break;
-
- // swap edges
- QDEBUG() << " swapping intersecting edges ";
- int min = scanline.size;
- int max = 0;
- Q27Dot5 xmin = INT_MAX;
- Q27Dot5 xmax = INT_MIN;
- int num = 0;
- while (1) {
- const Intersection i = it.key();
- int next = it->next;
-
- int edgePos = scanline.findEdge(i.edge);
- if (edgePos >= 0) {
- ++num;
- min = qMin(edgePos, min);
- max = qMax(edgePos, max);
- Edge *edge = scanline.edges[edgePos];
- xmin = qMin(xmin, edge->positionAt(y));
- xmax = qMax(xmax, edge->positionAt(y));
- }
- Intersection key;
- key.y = y;
- key.edge = next;
- it = intersections.find(key);
- intersections.remove(i);
- if (it == intersections.end())
- break;
- }
- if (num < 2)
- continue;
-
- Q_ASSERT(min != max);
- QDEBUG() << "sorting between" << min << "and" << max << "xpos=" << xmin << xmax;
- while (min > 0 && scanline.edges[min - 1]->positionAt(y) >= xmin) {
- QDEBUG() << " adding edge on left";
- --min;
- }
- while (max < scanline.size - 1 && scanline.edges[max + 1]->positionAt(y) <= xmax) {
- QDEBUG() << " adding edge on right";
- ++max;
- }
-
- std::sort(scanline.edges + min, scanline.edges + max + 1, EdgeSorter(y));
-#ifdef DEBUG
- for (int i = min; i <= max; ++i)
- QDEBUG() << " " << scanline.edges[i]->edge << "at pos" << i;
-#endif
- for (int i = min; i <= max; ++i) {
- Edge *edge = scanline.edges[i];
- edge->intersect_left = true;
- edge->intersect_right = true;
- edge->mark = true;
- }
- }
-}
-
-void QTessellatorPrivate::removeEdges()
-{
- int cv = currentVertex;
- while (cv < vertices.nPoints) {
- const Vertex *v = vertices.sorted[cv];
- if (v->y > y)
- break;
- if (v->flags & LineBeforeEnds) {
- QDEBUG() << " removing edge" << vertices.prevPos(v);
- int pos = scanline.findEdge(vertices.prevPos(v));
- if (pos == -1)
- continue;
- scanline.edges[pos]->mark = true;
- if (pos > 0)
- scanline.edges[pos - 1]->intersect_right = true;
- if (pos < scanline.size - 1)
- scanline.edges[pos + 1]->intersect_left = true;
- scanline.removeAt(pos);
- }
- if (v->flags & LineAfterEnds) {
- QDEBUG() << " removing edge" << vertices.position(v);
- int pos = scanline.findEdge(vertices.position(v));
- if (pos == -1)
- continue;
- scanline.edges[pos]->mark = true;
- if (pos > 0)
- scanline.edges[pos - 1]->intersect_right = true;
- if (pos < scanline.size - 1)
- scanline.edges[pos + 1]->intersect_left = true;
- scanline.removeAt(pos);
- }
- ++cv;
- }
-}
-
-void QTessellatorPrivate::addEdges()
-{
- while (currentVertex < vertices.nPoints) {
- const Vertex *v = vertices.sorted[currentVertex];
- if (v->y > y)
- break;
- if (v->flags & LineBeforeStarts) {
- // add new edge
- int start = vertices.prevPos(v);
- Edge e(vertices, start);
- int pos = scanline.findEdgePosition(e);
- QDEBUG() << " adding edge" << start << "at position" << pos;
- scanline.insert(pos, e);
- if (!mark_clever || !(v->flags & LineAfterEnds)) {
- if (pos > 0)
- scanline.edges[pos - 1]->mark = true;
- if (pos < scanline.size - 1)
- scanline.edges[pos + 1]->mark = true;
- }
- }
- if (v->flags & LineAfterStarts) {
- Edge e(vertices, vertices.position(v));
- int pos = scanline.findEdgePosition(e);
- QDEBUG() << " adding edge" << vertices.position(v) << "at position" << pos;
- scanline.insert(pos, e);
- if (!mark_clever || !(v->flags & LineBeforeEnds)) {
- if (pos > 0)
- scanline.edges[pos - 1]->mark = true;
- if (pos < scanline.size - 1)
- scanline.edges[pos + 1]->mark = true;
- }
- }
- if (v->flags & LineAfterHorizontal) {
- int pos1 = scanline.findEdgePosition(v->x, v->y);
- const Vertex *next = vertices.next(v);
- Q_ASSERT(v->y == next->y);
- int pos2 = scanline.findEdgePosition(next->x, next->y);
- if (pos2 < pos1)
- qSwap(pos1, pos2);
- if (pos1 > 0)
- --pos1;
- if (pos2 == scanline.size)
- --pos2;
- //QDEBUG() << "marking horizontal edge from " << pos1 << "to" << pos2;
- scanline.markEdges(pos1, pos2);
- }
- ++currentVertex;
- }
-}
-
-#ifdef DEBUG
-static void checkLinkChain(const QTessellatorPrivate::Intersections &intersections,
- QTessellatorPrivate::Intersection i)
-{
-// qDebug() << " Link chain: ";
- int end = i.edge;
- while (1) {
- QTessellatorPrivate::IntersectionLink l = intersections.value(i);
-// qDebug() << " " << i.edge << "next=" << l.next << "prev=" << l.prev;
- if (l.next == end)
- break;
- Q_ASSERT(l.next != -1);
- Q_ASSERT(l.prev != -1);
-
- QTessellatorPrivate::Intersection i2 = i;
- i2.edge = l.next;
- QTessellatorPrivate::IntersectionLink l2 = intersections.value(i2);
-
- Q_ASSERT(l2.next != -1);
- Q_ASSERT(l2.prev != -1);
- Q_ASSERT(l.next == i2.edge);
- Q_ASSERT(l2.prev == i.edge);
- i = i2;
- }
-}
-#endif
-
-bool QTessellatorPrivate::edgeInChain(Intersection i, int edge)
-{
- int end = i.edge;
- while (1) {
- if (i.edge == edge)
- return true;
- IntersectionLink l = intersections.value(i);
- if (l.next == end)
- break;
- Q_ASSERT(l.next != -1);
- Q_ASSERT(l.prev != -1);
-
- Intersection i2 = i;
- i2.edge = l.next;
-
-#ifndef QT_NO_DEBUG
- IntersectionLink l2 = intersections.value(i2);
- Q_ASSERT(l2.next != -1);
- Q_ASSERT(l2.prev != -1);
- Q_ASSERT(l.next == i2.edge);
- Q_ASSERT(l2.prev == i.edge);
-#endif
- i = i2;
- }
- return false;
-}
-
-
-void QTessellatorPrivate::addIntersection(const Edge *e1, const Edge *e2)
-{
- const IntersectionLink emptyLink = {-1, -1};
-
- int next = vertices.nextPos(vertices[e1->edge]);
- if (e2->edge == next)
- return;
- int prev = vertices.prevPos(vertices[e1->edge]);
- if (e2->edge == prev)
- return;
-
- Q27Dot5 yi;
- bool det_positive;
- bool isect = e1->intersect(*e2, &yi, &det_positive);
- QDEBUG("checking edges %d and %d", e1->edge, e2->edge);
- if (!isect) {
- QDEBUG() << " no intersection";
- return;
- }
-
- // don't emit an intersection if it's at the start of a line segment or above us
- if (yi <= y) {
- if (!det_positive)
- return;
- QDEBUG() << " ----->>>>>> WRONG ORDER!";
- yi = y;
- }
- QDEBUG() << " between edges " << e1->edge << "and" << e2->edge << "at point ("
- << Q27Dot5ToDouble(yi) << ')';
-
- Intersection i1;
- i1.y = yi;
- i1.edge = e1->edge;
- IntersectionLink link1 = intersections.value(i1, emptyLink);
- Intersection i2;
- i2.y = yi;
- i2.edge = e2->edge;
- IntersectionLink link2 = intersections.value(i2, emptyLink);
-
- // new pair of edges
- if (link1.next == -1 && link2.next == -1) {
- link1.next = link1.prev = i2.edge;
- link2.next = link2.prev = i1.edge;
- } else if (link1.next == i2.edge || link1.prev == i2.edge
- || link2.next == i1.edge || link2.prev == i1.edge) {
-#ifdef DEBUG
- checkLinkChain(intersections, i1);
- checkLinkChain(intersections, i2);
- Q_ASSERT(edgeInChain(i1, i2.edge));
-#endif
- return;
- } else if (link1.next == -1 || link2.next == -1) {
- if (link2.next == -1) {
- qSwap(i1, i2);
- qSwap(link1, link2);
- }
- Q_ASSERT(link1.next == -1);
-#ifdef DEBUG
- checkLinkChain(intersections, i2);
-#endif
- // only i2 in list
- link1.next = i2.edge;
- link1.prev = link2.prev;
- link2.prev = i1.edge;
- Intersection other;
- other.y = yi;
- other.edge = link1.prev;
- IntersectionLink link = intersections.value(other, emptyLink);
- Q_ASSERT(link.next == i2.edge);
- Q_ASSERT(link.prev != -1);
- link.next = i1.edge;
- intersections.insert(other, link);
- } else {
- bool connected = edgeInChain(i1, i2.edge);
- if (connected)
- return;
-#ifdef DEBUG
- checkLinkChain(intersections, i1);
- checkLinkChain(intersections, i2);
-#endif
- // both already in some list. Have to make sure they are connected
- // this can be done by cutting open the ring(s) after the two eges and
- // connecting them again
- Intersection other1;
- other1.y = yi;
- other1.edge = link1.next;
- IntersectionLink linko1 = intersections.value(other1, emptyLink);
- Intersection other2;
- other2.y = yi;
- other2.edge = link2.next;
- IntersectionLink linko2 = intersections.value(other2, emptyLink);
-
- linko1.prev = i2.edge;
- link2.next = other1.edge;
-
- linko2.prev = i1.edge;
- link1.next = other2.edge;
- intersections.insert(other1, linko1);
- intersections.insert(other2, linko2);
- }
- intersections.insert(i1, link1);
- intersections.insert(i2, link2);
-#ifdef DEBUG
- checkLinkChain(intersections, i1);
- checkLinkChain(intersections, i2);
- Q_ASSERT(edgeInChain(i1, i2.edge));
-#endif
- return;
-
-}
-
-
-void QTessellatorPrivate::addIntersections()
-{
- if (scanline.size) {
- QDEBUG() << "INTERSECTIONS";
- // check marked edges for intersections
-#ifdef DEBUG
- for (int i = 0; i < scanline.size; ++i) {
- Edge *e = scanline.edges[i];
- QDEBUG() << " " << i << e->edge << "isect=(" << e->intersect_left << e->intersect_right
- << ')';
- }
-#endif
-
- for (int i = 0; i < scanline.size - 1; ++i) {
- Edge *e1 = scanline.edges[i];
- Edge *e2 = scanline.edges[i + 1];
- // check for intersection
- if (e1->intersect_right || e2->intersect_left)
- addIntersection(e1, e2);
- }
- }
-#if 0
- if (intersections.constBegin().key().y == y) {
- QDEBUG() << "----------------> intersection on same line";
- scanline.clearMarks();
- scanline.processIntersections(y, &intersections);
- goto redo;
- }
-#endif
-}
-
-
-QTessellator::QTessellator()
-{
- d = new QTessellatorPrivate;
-}
-
-QTessellator::~QTessellator()
-{
- delete d;
-}
-
-void QTessellator::setWinding(bool w)
-{
- d->winding = w;
-}
-
-
-QRectF QTessellator::tessellate(const QPointF *points, int nPoints)
-{
- Q_ASSERT(points[0] == points[nPoints-1]);
- --nPoints;
-
-#ifdef DEBUG
- QDEBUG()<< "POINTS:";
- for (int i = 0; i < nPoints; ++i) {
- QDEBUG() << points[i];
- }
-#endif
-
- // collect edges and calculate bounds
- d->vertices.nPoints = nPoints;
- d->vertices.init(nPoints);
-
- int maxActiveEdges = 0;
- QRectF br = d->collectAndSortVertices(points, &maxActiveEdges);
- d->cancelCoincidingEdges();
-
-#ifdef DEBUG
- QDEBUG() << "nPoints = " << nPoints << "using " << d->vertices.nPoints;
- QDEBUG()<< "VERTICES:";
- for (int i = 0; i < d->vertices.nPoints; ++i) {
- QDEBUG() << " " << i << ": "
- << "point=" << d->vertices.position(d->vertices.sorted[i])
- << "flags=" << d->vertices.sorted[i]->flags
- << "pos=(" << Q27Dot5ToDouble(d->vertices.sorted[i]->x) << '/'
- << Q27Dot5ToDouble(d->vertices.sorted[i]->y) << ')';
- }
-#endif
-
- d->scanline.init(maxActiveEdges);
- d->y = INT_MIN/256;
- d->currentVertex = 0;
-
- while (d->currentVertex < d->vertices.nPoints) {
- d->scanline.clearMarks();
-
- d->y = d->vertices.sorted[d->currentVertex]->y;
- if (!d->intersections.isEmpty())
- d->y = qMin(d->y, d->intersections.constBegin().key().y);
-
- QDEBUG()<< "===== SCANLINE: y =" << Q27Dot5ToDouble(d->y) << " =====";
-
- d->scanline.prepareLine();
- d->processIntersections();
- d->removeEdges();
- d->addEdges();
- d->addIntersections();
- d->emitEdges(this);
- d->scanline.lineDone();
-
-#ifdef DEBUG
- QDEBUG()<< "===== edges:";
- for (int i = 0; i < d->scanline.size; ++i) {
- QDEBUG() << " " << d->scanline.edges[i]->edge
- << "p0= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v0->x)
- << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v0->y)
- << ") p1= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v1->x)
- << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v1->y) << ')'
- << "x=" << Q27Dot5ToDouble(d->scanline.edges[i]->positionAt(d->y))
- << "isLeftOfNext="
- << ((i < d->scanline.size - 1)
- ? d->scanline.edges[i]->isLeftOf(*d->scanline.edges[i+1], d->y)
- : true);
- }
-#endif
-}
-
- d->scanline.done();
- d->intersections.clear();
- return br;
-}
-
-// tessellates the given convex polygon
-void QTessellator::tessellateConvex(const QPointF *points, int nPoints)
-{
- Q_ASSERT(points[0] == points[nPoints-1]);
- --nPoints;
-
- d->vertices.nPoints = nPoints;
- d->vertices.init(nPoints);
-
- for (int i = 0; i < nPoints; ++i) {
- d->vertices[i]->x = FloatToQ27Dot5(points[i].x());
- d->vertices[i]->y = FloatToQ27Dot5(points[i].y());
- }
-
- int left = 0, right = 0;
-
- int top = 0;
- for (int i = 1; i < nPoints; ++i) {
- if (d->vertices[i]->y < d->vertices[top]->y)
- top = i;
- }
-
- left = (top + nPoints - 1) % nPoints;
- right = (top + 1) % nPoints;
-
- while (d->vertices[left]->x == d->vertices[top]->x && d->vertices[left]->y == d->vertices[top]->y && left != right)
- left = (left + nPoints - 1) % nPoints;
-
- while (d->vertices[right]->x == d->vertices[top]->x && d->vertices[right]->y == d->vertices[top]->y && left != right)
- right = (right + 1) % nPoints;
-
- if (left == right)
- return;
-
- int dir = 1;
-
- Vertex dLeft = { d->vertices[top]->x - d->vertices[left]->x,
- d->vertices[top]->y - d->vertices[left]->y };
-
- Vertex dRight = { d->vertices[right]->x - d->vertices[top]->x,
- d->vertices[right]->y - d->vertices[top]->y };
-
- Q27Dot5 cross = dLeft.x * dRight.y - dLeft.y * dRight.x;
-
- // flip direction if polygon is clockwise
- if (cross < 0 || (cross == 0 && dLeft.x > 0)) {
- qSwap(left, right);
- dir = -1;
- }
-
- Vertex *lastLeft = d->vertices[top];
- Vertex *lastRight = d->vertices[top];
-
- QTessellator::Trapezoid trap;
-
- while (lastLeft->y == d->vertices[left]->y && left != right) {
- lastLeft = d->vertices[left];
- left = (left + nPoints - dir) % nPoints;
- }
-
- while (lastRight->y == d->vertices[right]->y && left != right) {
- lastRight = d->vertices[right];
- right = (right + nPoints + dir) % nPoints;
- }
-
- while (true) {
- trap.top = qMax(lastRight->y, lastLeft->y);
- trap.bottom = qMin(d->vertices[left]->y, d->vertices[right]->y);
- trap.topLeft = lastLeft;
- trap.topRight = lastRight;
- trap.bottomLeft = d->vertices[left];
- trap.bottomRight = d->vertices[right];
-
- if (trap.bottom > trap.top)
- addTrap(trap);
-
- if (left == right)
- break;
-
- if (d->vertices[right]->y < d->vertices[left]->y) {
- do {
- lastRight = d->vertices[right];
- right = (right + nPoints + dir) % nPoints;
- }
- while (lastRight->y == d->vertices[right]->y && left != right);
- } else {
- do {
- lastLeft = d->vertices[left];
- left = (left + nPoints - dir) % nPoints;
- }
- while (lastLeft->y == d->vertices[left]->y && left != right);
- }
- }
-}
-
-// tessellates the stroke of the line from a_ to b_ with the given width and a flat cap
-void QTessellator::tessellateRect(const QPointF &a_, const QPointF &b_, qreal width)
-{
- Vertex a = { FloatToQ27Dot5(a_.x()), FloatToQ27Dot5(a_.y()) };
- Vertex b = { FloatToQ27Dot5(b_.x()), FloatToQ27Dot5(b_.y()) };
-
- QPointF pa = a_, pb = b_;
-
- if (a.y > b.y) {
- qSwap(a, b);
- qSwap(pa, pb);
- }
-
- Vertex delta = { b.x - a.x, b.y - a.y };
-
- if (delta.x == 0 && delta.y == 0)
- return;
-
- qreal hw = 0.5 * width;
-
- if (delta.x == 0) {
- Q27Dot5 halfWidth = FloatToQ27Dot5(hw);
-
- if (halfWidth == 0)
- return;
-
- Vertex topLeft = { a.x - halfWidth, a.y };
- Vertex topRight = { a.x + halfWidth, a.y };
- Vertex bottomLeft = { a.x - halfWidth, b.y };
- Vertex bottomRight = { a.x + halfWidth, b.y };
-
- QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight };
- addTrap(trap);
- } else if (delta.y == 0) {
- Q27Dot5 halfWidth = FloatToQ27Dot5(hw);
-
- if (halfWidth == 0)
- return;
-
- if (a.x > b.x)
- qSwap(a.x, b.x);
-
- Vertex topLeft = { a.x, a.y - halfWidth };
- Vertex topRight = { b.x, a.y - halfWidth };
- Vertex bottomLeft = { a.x, a.y + halfWidth };
- Vertex bottomRight = { b.x, a.y + halfWidth };
-
- QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight };
- addTrap(trap);
- } else {
- QPointF perp(pb.y() - pa.y(), pa.x() - pb.x());
- qreal length = qSqrt(perp.x() * perp.x() + perp.y() * perp.y());
-
- if (qFuzzyIsNull(length))
- return;
-
- // need the half of the width
- perp *= hw / length;
-
- QPointF pta = pa + perp;
- QPointF ptb = pa - perp;
- QPointF ptc = pb - perp;
- QPointF ptd = pb + perp;
-
- Vertex ta = { FloatToQ27Dot5(pta.x()), FloatToQ27Dot5(pta.y()) };
- Vertex tb = { FloatToQ27Dot5(ptb.x()), FloatToQ27Dot5(ptb.y()) };
- Vertex tc = { FloatToQ27Dot5(ptc.x()), FloatToQ27Dot5(ptc.y()) };
- Vertex td = { FloatToQ27Dot5(ptd.x()), FloatToQ27Dot5(ptd.y()) };
-
- if (ta.y < tb.y) {
- if (tb.y < td.y) {
- QTessellator::Trapezoid top = { ta.y, tb.y, &ta, &tb, &ta, &td };
- QTessellator::Trapezoid bottom = { td.y, tc.y, &tb, &tc, &td, &tc };
- addTrap(top);
- addTrap(bottom);
-
- QTessellator::Trapezoid middle = { tb.y, td.y, &tb, &tc, &ta, &td };
- addTrap(middle);
- } else {
- QTessellator::Trapezoid top = { ta.y, td.y, &ta, &tb, &ta, &td };
- QTessellator::Trapezoid bottom = { tb.y, tc.y, &tb, &tc, &td, &tc };
- addTrap(top);
- addTrap(bottom);
-
- if (tb.y != td.y) {
- QTessellator::Trapezoid middle = { td.y, tb.y, &ta, &tb, &td, &tc };
- addTrap(middle);
- }
- }
- } else {
- if (ta.y < tc.y) {
- QTessellator::Trapezoid top = { tb.y, ta.y, &tb, &tc, &tb, &ta };
- QTessellator::Trapezoid bottom = { tc.y, td.y, &tc, &td, &ta, &td };
- addTrap(top);
- addTrap(bottom);
-
- QTessellator::Trapezoid middle = { ta.y, tc.y, &tb, &tc, &ta, &td };
- addTrap(middle);
- } else {
- QTessellator::Trapezoid top = { tb.y, tc.y, &tb, &tc, &tb, &ta };
- QTessellator::Trapezoid bottom = { ta.y, td.y, &tc, &td, &ta, &td };
- addTrap(top);
- addTrap(bottom);
-
- if (ta.y != tc.y) {
- QTessellator::Trapezoid middle = { tc.y, ta.y, &tc, &td, &tb, &ta };
- addTrap(middle);
- }
- }
- }
- }
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h
deleted file mode 100644
index ba1d3a971a3..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <QPoint>
-#include <QRect>
-
-QT_BEGIN_NAMESPACE
-
-class QTessellatorPrivate;
-
-typedef int Q27Dot5;
-#define Q27Dot5ToDouble(i) ((i)/32.)
-#define FloatToQ27Dot5(i) (int)((i) * 32)
-#define IntToQ27Dot5(i) ((i) << 5)
-#define Q27Dot5ToXFixed(i) ((i) << 11)
-#define Q27Dot5Factor 32
-
-class QTessellator {
-public:
- QTessellator();
- virtual ~QTessellator();
-
- QRectF tessellate(const QPointF *points, int nPoints);
- void tessellateConvex(const QPointF *points, int nPoints);
- void tessellateRect(const QPointF &a, const QPointF &b, qreal width);
-
- void setWinding(bool w);
-
- struct Vertex {
- Q27Dot5 x;
- Q27Dot5 y;
- };
- struct Trapezoid {
- Q27Dot5 top;
- Q27Dot5 bottom;
- const Vertex *topLeft;
- const Vertex *bottomLeft;
- const Vertex *topRight;
- const Vertex *bottomRight;
- };
- virtual void addTrap(const Trapezoid &trap) = 0;
-
-private:
- friend class QTessellatorPrivate;
- QTessellatorPrivate *d;
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp
deleted file mode 100644
index 23155ef2e62..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-// Copyright (C) 2018 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
-
-#include <QtCore/qrandom.h>
-
-#include "qxcbconnection.h"
-#include "qcolormap_x11_p.h"
-#include "qxcbnativepainting.h"
-#include "qt_x11_p.h"
-
-QT_BEGIN_NAMESPACE
-
-QXcbX11Data *qt_x11Data = nullptr;
-
-void qt_xcb_native_x11_info_init(QXcbConnection *conn)
-{
- qt_x11Data = new QXcbX11Data;
- X11->display = static_cast<Display *>(conn->xlib_display());
- X11->defaultScreen = DefaultScreen(X11->display);
- X11->screenCount = ScreenCount(X11->display);
-
- X11->screens = new QX11InfoData[X11->screenCount];
- X11->argbVisuals = new Visual *[X11->screenCount];
- X11->argbColormaps = new Colormap[X11->screenCount];
-
- for (int s = 0; s < X11->screenCount; s++) {
- QX11InfoData *screen = X11->screens + s;
- //screen->ref = 1; // ensures it doesn't get deleted
- screen->screen = s;
-
- int widthMM = DisplayWidthMM(X11->display, s);
- if (widthMM != 0) {
- screen->dpiX = (DisplayWidth(X11->display, s) * 254 + widthMM * 5) / (widthMM * 10);
- } else {
- screen->dpiX = 72;
- }
-
- int heightMM = DisplayHeightMM(X11->display, s);
- if (heightMM != 0) {
- screen->dpiY = (DisplayHeight(X11->display, s) * 254 + heightMM * 5) / (heightMM * 10);
- } else {
- screen->dpiY = 72;
- }
-
- X11->argbVisuals[s] = 0;
- X11->argbColormaps[s] = 0;
- }
-
- X11->use_xrender = conn->hasXRender() && !qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING_NO_XRENDER");
-
-#if QT_CONFIG(xrender)
- memset(X11->solid_fills, 0, sizeof(X11->solid_fills));
- for (int i = 0; i < X11->solid_fill_count; ++i)
- X11->solid_fills[i].screen = -1;
- memset(X11->pattern_fills, 0, sizeof(X11->pattern_fills));
- for (int i = 0; i < X11->pattern_fill_count; ++i)
- X11->pattern_fills[i].screen = -1;
-#endif
-
- QXcbColormap::initialize();
-
-#if QT_CONFIG(xrender)
- if (X11->use_xrender) {
- // XRender is supported, let's see if we have a PictFormat for the
- // default visual
- XRenderPictFormat *format =
- XRenderFindVisualFormat(X11->display,
- (Visual *) QXcbX11Info::appVisual(X11->defaultScreen));
-
- if (!format) {
- X11->use_xrender = false;
- }
- }
-#endif // QT_CONFIG(xrender)
-}
-
-QList<XRectangle> qt_region_to_xrectangles(const QRegion &r)
-{
- const int numRects = r.rectCount();
- const auto input = r.begin();
- QList<XRectangle> output(numRects);
- for (int i = 0; i < numRects; ++i) {
- const QRect &in = input[i];
- XRectangle &out = output[i];
- out.x = qMax(SHRT_MIN, in.x());
- out.y = qMax(SHRT_MIN, in.y());
- out.width = qMin((int)USHRT_MAX, in.width());
- out.height = qMin((int)USHRT_MAX, in.height());
- }
- return output;
-}
-
-class QXcbX11InfoData : public QSharedData, public QX11InfoData
-{};
-
-QXcbX11Info::QXcbX11Info()
- : d(nullptr)
-{}
-
-QXcbX11Info::~QXcbX11Info()
-{}
-
-QXcbX11Info::QXcbX11Info(const QXcbX11Info &other)
- : d(other.d)
-{}
-
-QXcbX11Info &QXcbX11Info::operator=(const QXcbX11Info &other)
-{
- d = other.d;
- return *this;
-}
-
-QXcbX11Info QXcbX11Info::fromScreen(int screen)
-{
- QXcbX11InfoData *xd = new QXcbX11InfoData;
- xd->screen = screen;
- xd->depth = QXcbX11Info::appDepth(screen);
- xd->cells = QXcbX11Info::appCells(screen);
- xd->colormap = QXcbX11Info::appColormap(screen);
- xd->defaultColormap = QXcbX11Info::appDefaultColormap(screen);
- xd->visual = (Visual *)QXcbX11Info::appVisual(screen);
- xd->defaultVisual = QXcbX11Info::appDefaultVisual(screen);
-
- QXcbX11Info info;
- info.d = xd;
- return info;
-}
-
-void QXcbX11Info::setDepth(int depth)
-{
- if (!d)
- *this = fromScreen(appScreen());
-
- d->depth = depth;
-}
-
-Display *QXcbX11Info::display()
-{
- return X11 ? X11->display : 0;
-}
-
-int QXcbX11Info::screen() const
-{
- return d ? d->screen : QXcbX11Info::appScreen();
-}
-
-int QXcbX11Info::depth() const
-{
- return d ? d->depth : QXcbX11Info::appDepth();
-}
-
-Colormap QXcbX11Info::colormap() const
-{
- return d ? d->colormap : QXcbX11Info::appColormap();
-}
-
-void *QXcbX11Info::visual() const
-{
- return d ? d->visual : QXcbX11Info::appVisual();
-}
-
-void QXcbX11Info::setVisual(void *visual)
-{
- if (!d)
- *this = fromScreen(appScreen());
-
- d->visual = (Visual *) visual;
-}
-
-int QXcbX11Info::appScreen()
-{
- return X11 ? X11->defaultScreen : 0;
-}
-
-int QXcbX11Info::appDepth(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].depth : 32;
-}
-
-int QXcbX11Info::appCells(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].cells : 0;
-}
-
-Colormap QXcbX11Info::appColormap(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].colormap : 0;
-}
-
-void *QXcbX11Info::appVisual(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].visual : 0;
-}
-
-Window QXcbX11Info::appRootWindow(int screen)
-{
- return X11 ? RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen) : 0;
-}
-
-bool QXcbX11Info::appDefaultColormap(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultColormap : true;
-}
-
-bool QXcbX11Info::appDefaultVisual(int screen)
-{
- return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultVisual : true;
-}
-
-int QXcbX11Info::appDpiX(int screen)
-{
- if (!X11)
- return 75;
- if (screen < 0)
- screen = X11->defaultScreen;
- if (screen > X11->screenCount)
- return 0;
- return X11->screens[screen].dpiX;
-}
-
-int QXcbX11Info::appDpiY(int screen)
-{
- if (!X11)
- return 75;
- if (screen < 0)
- screen = X11->defaultScreen;
- if (screen > X11->screenCount)
- return 0;
- return X11->screens[screen].dpiY;
-}
-
-#if QT_CONFIG(xrender)
-Picture QXcbX11Data::getSolidFill(int screen, const QColor &c)
-{
- if (!X11->use_xrender)
- return XNone;
-
- XRenderColor color = preMultiply(c);
- for (int i = 0; i < X11->solid_fill_count; ++i) {
- if (X11->solid_fills[i].screen == screen
- && X11->solid_fills[i].color.alpha == color.alpha
- && X11->solid_fills[i].color.red == color.red
- && X11->solid_fills[i].color.green == color.green
- && X11->solid_fills[i].color.blue == color.blue)
- return X11->solid_fills[i].picture;
- }
- // none found, replace one
- int i = QRandomGenerator::global()->generate() % 16;
-
- if (X11->solid_fills[i].screen != screen && X11->solid_fills[i].picture) {
- XRenderFreePicture (X11->display, X11->solid_fills[i].picture);
- X11->solid_fills[i].picture = 0;
- }
-
- if (!X11->solid_fills[i].picture) {
- Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 1, 1, 32);
- XRenderPictureAttributes attrs;
- attrs.repeat = True;
- X11->solid_fills[i].picture = XRenderCreatePicture (X11->display, pixmap,
- XRenderFindStandardFormat(X11->display, PictStandardARGB32),
- CPRepeat, &attrs);
- XFreePixmap (X11->display, pixmap);
- }
-
- X11->solid_fills[i].color = color;
- X11->solid_fills[i].screen = screen;
- XRenderFillRectangle (X11->display, PictOpSrc, X11->solid_fills[i].picture, &color, 0, 0, 1, 1);
- return X11->solid_fills[i].picture;
-}
-
-XRenderColor QXcbX11Data::preMultiply(const QColor &c)
-{
- XRenderColor color;
- const uint A = c.alpha(),
- R = c.red(),
- G = c.green(),
- B = c.blue();
- color.alpha = (A | A << 8);
- color.red = (R | R << 8) * color.alpha / 0x10000;
- color.green = (G | G << 8) * color.alpha / 0x10000;
- color.blue = (B | B << 8) * color.alpha / 0x10000;
- return color;
-}
-#endif // QT_CONFIG(xrender)
-
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h
deleted file mode 100644
index dac9b101c43..00000000000
--- a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (C) 2018 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
-
-#pragma once
-
-#include <QSharedDataPointer>
-#include "qt_x11_p.h"
-
-typedef struct _FcPattern FcPattern;
-typedef unsigned long XID;
-typedef XID Colormap;
-typedef XID Window;
-typedef struct _XDisplay Display;
-
-QT_BEGIN_NAMESPACE
-
-class QXcbConnection;
-class QPixmap;
-
-void qt_xcb_native_x11_info_init(QXcbConnection *conn);
-QList<XRectangle> qt_region_to_xrectangles(const QRegion &r);
-
-class QXcbX11InfoData;
-class QXcbX11Info
-{
-public:
- QXcbX11Info();
- ~QXcbX11Info();
- QXcbX11Info(const QXcbX11Info &other);
- QXcbX11Info &operator=(const QXcbX11Info &other);
-
- static QXcbX11Info fromScreen(int screen);
- static Display *display();
-
- int depth() const;
- void setDepth(int depth);
-
- int screen() const;
- Colormap colormap() const;
-
- void *visual() const;
- void setVisual(void *visual);
-
- static int appScreen();
- static int appDepth(int screen = -1);
- static int appCells(int screen = -1);
- static Colormap appColormap(int screen = -1);
- static void *appVisual(int screen = -1);
- static Window appRootWindow(int screen = -1);
- static bool appDefaultColormap(int screen = -1);
- static bool appDefaultVisual(int screen = -1);
- static int appDpiX(int screen = -1);
- static int appDpiY(int screen = -1);
-
-private:
- QSharedDataPointer<QXcbX11InfoData> d;
-
- friend class QX11PaintEngine;
- friend class QX11PlatformPixmap;
- friend void qt_x11SetScreen(QPixmap &pixmap, int screen);
-};
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/qxcbimage.cpp b/src/plugins/platforms/xcb/qxcbimage.cpp
index 5b78c2c08ab..4c978152975 100644
--- a/src/plugins/platforms/xcb/qxcbimage.cpp
+++ b/src/plugins/platforms/xcb/qxcbimage.cpp
@@ -80,12 +80,6 @@ bool qt_xcb_imageFormatForVisual(QXcbConnection *connection, uint8_t depth, cons
*imageFormat = QImage::Format_Grayscale8;
return true;
}
-#if QT_CONFIG(xcb_native_painting)
- if (QXcbIntegration::instance() && QXcbIntegration::instance()->nativePaintingEnabled()) {
- *imageFormat = QImage::Format_Indexed8;
- return true;
- }
-#endif
return false;
}
diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp
index c5c1fb1e638..d3a31dd35c7 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.cpp
+++ b/src/plugins/platforms/xcb/qxcbintegration.cpp
@@ -37,11 +37,6 @@
#include <X11/Xlib.h>
#undef register
#endif
-#if QT_CONFIG(xcb_native_painting)
-#include "qxcbnativepainting.h"
-#include "qpixmap_x11_p.h"
-#include "qbackingstore_x11_p.h"
-#endif
#include <qpa/qplatforminputcontextfactory_p.h>
#include <private/qgenericunixtheme_p.h>
@@ -180,13 +175,6 @@ QXcbIntegration::QXcbIntegration(const QStringList &parameters, int &argc, char
m_services->setConnection(m_connection);
m_fontDatabase.reset(new QGenericUnixFontDatabase());
-
-#if QT_CONFIG(xcb_native_painting)
- if (nativePaintingEnabled()) {
- qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING");
- qt_xcb_native_x11_info_init(connection());
- }
-#endif
}
QXcbIntegration::~QXcbIntegration()
@@ -198,11 +186,6 @@ QXcbIntegration::~QXcbIntegration()
QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const
{
-#if QT_CONFIG(xcb_native_painting)
- if (nativePaintingEnabled())
- return new QX11PlatformPixmap(type);
-#endif
-
return QPlatformIntegration::createPlatformPixmap(type);
}
@@ -281,10 +264,6 @@ QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *wind
const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
if (isTrayIconWindow) {
backingStore = new QXcbSystemTrayBackingStore(window);
-#if QT_CONFIG(xcb_native_painting)
- } else if (nativePaintingEnabled()) {
- backingStore = new QXcbNativeBackingStore(window);
-#endif
} else {
backingStore = new QXcbBackingStore(window);
}
@@ -576,16 +555,6 @@ void QXcbIntegration::beep() const
xcb_flush(connection);
}
-bool QXcbIntegration::nativePaintingEnabled() const
-{
-#if QT_CONFIG(xcb_native_painting)
- static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING");
- return enabled;
-#else
- return false;
-#endif
-}
-
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
{
diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h
index 10cc67af55f..041908e4552 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.h
+++ b/src/plugins/platforms/xcb/qxcbintegration.h
@@ -94,8 +94,6 @@ public:
void beep() const override;
- bool nativePaintingEnabled() const;
-
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override;
#endif
diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp
index 0ed8e2890d9..410750d941d 100644
--- a/src/plugins/styles/modernwindows/qwindows11style.cpp
+++ b/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -109,6 +109,7 @@ inline ControlState calcControlState(const QStyleOption *option)
#define More u"\uE712"_s
#define Help u"\uE897"_s
+#define Clear u"\uE894"_s
template <typename R, typename P, typename B>
static inline void drawRoundedRect(QPainter *p, R &&rect, P &&pen, B &&brush)
@@ -580,21 +581,18 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
if (combobox->frame)
drawLineEditFrame(painter, frameRect, combobox, combobox->editable);
- const bool isMouseOver = state & State_MouseOver;
const bool hasFocus = state & State_HasFocus;
- if (isMouseOver && !hasFocus && !highContrastTheme)
- drawRoundedRect(painter, frameRect, Qt::NoPen, winUI3Color(subtleHighlightColor));
+ QStyleOption opt(*option);
+ opt.state.setFlag(QStyle::State_On, false);
+ drawRoundedRect(painter, frameRect, Qt::NoPen, controlFillBrush(&opt, ControlType::Control));
if (sub & SC_ComboBoxArrow) {
QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget).adjusted(4, 0, -4, 1);
painter->setFont(d->assetFont);
- painter->setPen(combobox->palette.text().color());
+ painter->setPen(controlTextColor(option));
painter->drawText(rect, Qt::AlignCenter, ChevronDownMed);
}
- if (state & State_HasFocus) {
- drawPrimitive(PE_FrameFocusRect, option, painter, widget);
- }
- if (state & State_KeyboardFocusChange && state & State_HasFocus) {
+ if (state & State_KeyboardFocusChange && hasFocus) {
QStyleOptionFocusRect fropt;
fropt.QStyleOption::operator=(*option);
proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
@@ -1048,12 +1046,27 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
if (rect.width() <= 0)
break;
- painter->setPen(Qt::NoPen);
- if (vopt->features & QStyleOptionViewItem::Alternate)
- painter->setBrush(vopt->palette.alternateBase());
- else
- painter->setBrush(vopt->palette.base());
- painter->drawRect(rect);
+ if (vopt->features & QStyleOptionViewItem::Alternate) {
+ QPalette::ColorGroup cg =
+ (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled))
+ ? QPalette::Normal
+ : QPalette::Disabled;
+ if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+ painter->fillRect(rect, option->palette.brush(cg, QPalette::AlternateBase));
+ }
+
+ if (option->state & State_Selected && !highContrastTheme) {
+ // keep in sync with CE_ItemViewItem QListView indicator painting
+ const auto col = option->palette.accent().color();
+ painter->setBrush(col);
+ painter->setPen(col);
+ const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f;
+ const auto yOfs = rect.height() / 4.;
+ QRectF r(QPointF(xPos, rect.y() + yOfs),
+ QPointF(xPos + 1, rect.y() + rect.height() - yOfs));
+ painter->drawRoundedRect(r, 1, 1);
+ }
const bool isTreeDecoration = vopt->features.testFlag(
QStyleOptionViewItem::IsDecorationForRootColumn);
@@ -1182,11 +1195,14 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
painter->setRenderHint(QPainter::Antialiasing);
switch (element) {
case QStyle::CE_ComboBoxLabel:
+#if QT_CONFIG(combobox)
if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
+ painter->setPen(controlTextColor(option));
QStyleOptionComboBox newOption = *cb;
newOption.rect.adjust(4,0,-4,0);
QCommonStyle::drawControl(element, &newOption, painter, widget);
}
+#endif // QT_CONFIG(combobox)
break;
case QStyle::CE_TabBarTabShape:
#if QT_CONFIG(tabbar)
@@ -1705,9 +1721,10 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
}
case CE_ItemViewItem: {
if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
- QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
- QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
- QRect textRect = proxy()->subElementRect(SE_ItemViewItemText, vopt, widget);
+ const auto p = proxy();
+ QRect checkRect = p->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
+ QRect iconRect = p->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
+ QRect textRect = p->subElementRect(SE_ItemViewItemText, vopt, widget);
// draw the background
proxy()->drawPrimitive(PE_PanelItemViewItem, option, painter, widget);
@@ -1808,16 +1825,17 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
d->viewItemDrawText(painter, vopt, textRect);
// paint a vertical marker for QListView
- if (vopt->state & State_Selected) {
+ if (vopt->state & State_Selected && !highContrastTheme) {
if (const QListView *lv = qobject_cast<const QListView *>(widget);
- lv && lv->viewMode() != QListView::IconMode && !highContrastTheme) {
- painter->setPen(vopt->palette.accent().color());
- const auto xPos = isRtl ? rect.right() - 1 : rect.left();
- const QLineF lines[2] = {
- QLineF(xPos, rect.y() + 2, xPos, rect.y() + rect.height() - 2),
- QLineF(xPos + 1, rect.y() + 2, xPos + 1, rect.y() + rect.height() - 2),
- };
- painter->drawLines(lines, 2);
+ lv && lv->viewMode() != QListView::IconMode) {
+ const auto col = vopt->palette.accent().color();
+ painter->setBrush(col);
+ painter->setPen(col);
+ const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f;
+ const auto yOfs = rect.height() / 4.;
+ QRectF r(QPointF(xPos, rect.y() + yOfs),
+ QPointF(xPos + 1, rect.y() + rect.height() - yOfs));
+ painter->drawRoundedRect(r, 1, 1);
}
}
}
@@ -1868,22 +1886,29 @@ QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOp
case QStyle::SE_LineEditContents:
ret = option->rect.adjusted(4,0,-4,0);
break;
- case QStyle::SE_ItemViewItemText:
- if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
- const int decorationOffset = item->features.testFlag(QStyleOptionViewItem::HasDecoration) ? item->decorationSize.width() : 0;
- const int checkboxOffset = item->features.testFlag(QStyleOptionViewItem::HasCheckIndicator) ? 16 : 0;
- if (widget && qobject_cast<QComboBoxPrivateContainer *>(widget->parentWidget())) {
- if (option->direction == Qt::LeftToRight)
- ret = option->rect.adjusted(decorationOffset + checkboxOffset + 5, 0, -5, 0);
- else
- ret = option->rect.adjusted(5, 0, decorationOffset - checkboxOffset - 5, 0);
+ case SE_ItemViewItemCheckIndicator:
+ case SE_ItemViewItemDecoration:
+ case SE_ItemViewItemText: {
+ ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ if (!ret.isValid() || highContrastTheme)
+ return ret;
+
+ if (const QListView *lv = qobject_cast<const QListView *>(widget);
+ lv && lv->viewMode() != QListView::IconMode) {
+ const int xOfs = contentHMargin;
+ const bool isRtl = option->direction == Qt::RightToLeft;
+ if (isRtl) {
+ ret.moveRight(ret.right() - xOfs);
+ if (ret.left() < option->rect.left())
+ ret.setLeft(option->rect.left());
} else {
- ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ ret.moveLeft(ret.left() + xOfs);
+ if (ret.right() > option->rect.right())
+ ret.setRight(option->rect.right());
}
- } else {
- ret = QWindowsVistaStyle::subElementRect(element, option, widget);
}
break;
+ }
#if QT_CONFIG(progressbar)
case SE_ProgressBarGroove:
case SE_ProgressBarContents:
@@ -2062,6 +2087,33 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption
}
break;
}
+ case CC_ComboBox: {
+ if (subControl == SC_ComboBoxArrow) {
+ const auto indicatorWidth =
+ proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget);
+ const int endX = option->rect.right() - contentHMargin - 2;
+ const int startX = endX - indicatorWidth;
+ const QRect rect(QPoint(startX, option->rect.top()),
+ QPoint(endX, option->rect.bottom()));
+ ret = visualRect(option->direction, option->rect, rect);
+ } else {
+ ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
+ }
+ break;
+ }
+#if QT_CONFIG(groupbox)
+ case CC_GroupBox: {
+ ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
+ switch (subControl) {
+ case SC_GroupBoxCheckBox:
+ ret.moveTop(1);
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+#endif // QT_CONFIG(groupbox)
default:
ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
}
@@ -2152,14 +2204,18 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
break;
}
#endif
+#if QT_CONFIG(combobox)
case CT_ComboBox:
if (const auto *comboBoxOpt = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); // don't rely on QWindowsThemeData
contentSize += QSize(4, 4); // default win11 style margins
- if (comboBoxOpt->subControls & SC_ComboBoxArrow)
- contentSize += QSize(8, 0); // arrow margins
+ if (comboBoxOpt->subControls & SC_ComboBoxArrow) {
+ const auto w = proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget);
+ contentSize.rwidth() += w + contentItemHMargin;
+ }
}
break;
+#endif
case CT_HeaderSection:
// windows vista does not honor the indicator (as it was drawn above the text, not on the
// side) so call QWindowsStyle::styleHint directly to get the correct size hint
@@ -2201,6 +2257,25 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
contentSize.rwidth() += 2 * contentHMargin - oldMargin;
break;
}
+ case CT_ItemViewItem: {
+ if (const auto *viewItemOpt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ if (const QListView *lv = qobject_cast<const QListView *>(widget);
+ lv && lv->viewMode() != QListView::IconMode) {
+ QStyleOptionViewItem vOpt(*viewItemOpt);
+ // viewItemSize only takes PM_FocusFrameHMargin into account but no additional
+ // margin, therefore adjust it here for a correct width during layouting when
+ // WrapText is enabled
+ vOpt.rect.setRight(vOpt.rect.right() - contentHMargin);
+ contentSize = QWindowsVistaStyle::sizeFromContents(type, &vOpt, size, widget);
+ contentSize.rwidth() += contentHMargin;
+ contentSize.rheight() += 2 * contentHMargin;
+
+ } else {
+ contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
+ }
+ }
+ break;
+ }
default:
contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
break;
@@ -2290,6 +2365,9 @@ int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option,
case PM_ButtonShiftVertical:
res = 0;
break;
+ case PM_TreeViewIndentation:
+ res = 30;
+ break;
default:
res = QWindowsVistaStyle::pixelMetric(metric, option, widget);
}
@@ -2377,6 +2455,13 @@ void QWindows11Style::unpolish(QWidget *widget)
widget->setProperty("_q_original_menubar_maxheight", QVariant());
}
#endif
+ const auto comboBoxContainer = qobject_cast<const QComboBoxPrivateContainer *>(widget);
+ if (comboBoxContainer) {
+ widget->setAttribute(Qt::WA_OpaquePaintEvent, true);
+ widget->setAttribute(Qt::WA_TranslucentBackground, false);
+ widget->setWindowFlag(Qt::FramelessWindowHint, false);
+ widget->setWindowFlag(Qt::NoDropShadowWindowHint, false);
+ }
if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget);
scrollarea
@@ -2501,6 +2586,7 @@ void QWindows11Style::polish(QPalette& result)
d->m_titleBarCloseIcon = QIcon();
d->m_titleBarNormalIcon = QIcon();
d->m_toolbarExtensionButton = QIcon();
+ d->m_lineEditClearButton = QIcon();
}
QPixmap QWindows11Style::standardPixmap(StandardPixmap standardPixmap,
@@ -2525,6 +2611,13 @@ QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon,
{
auto *d = const_cast<QWindows11StylePrivate*>(d_func());
switch (standardIcon) {
+ case SP_LineEditClearButton: {
+ if (d->m_lineEditClearButton.isNull()) {
+ auto e = new WinFontIconEngine(Clear.at(0), d->assetFont);
+ d->m_lineEditClearButton = QIcon(e);
+ }
+ return d->m_lineEditClearButton;
+ }
case SP_ToolBarHorizontalExtensionButton:
case SP_ToolBarVerticalExtensionButton: {
if (d->m_toolbarExtensionButton.isNull()) {
diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h
index 736caae956c..a51a93ddd9b 100644
--- a/src/plugins/styles/modernwindows/qwindows11style_p.h
+++ b/src/plugins/styles/modernwindows/qwindows11style_p.h
@@ -123,6 +123,7 @@ class QWindows11StylePrivate : public QWindowsVistaStylePrivate {
protected:
QIcon m_toolbarExtensionButton;
+ QIcon m_lineEditClearButton;
};
QT_END_NAMESPACE
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
index abe0bde540f..22ca18b10bf 100644
--- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
@@ -1626,6 +1626,12 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt
break;
case PE_Frame:
+ if (widget && widget->inherits("QComboBoxPrivateContainer")){
+ QStyleOption copy = *option;
+ copy.state |= State_Raised;
+ proxy()->drawPrimitive(PE_PanelMenu, &copy, painter, widget);
+ break;
+ }
#if QT_CONFIG(accessibility)
if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText)
|| QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) ||
@@ -1704,6 +1710,14 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt
return;
}
+ case PE_PanelMenu:
+ if (widget && widget->inherits("QComboBoxPrivateContainer")){
+ //fill combobox popup background
+ QWindowsThemeData popupbackgroundTheme(widget, painter, QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPBACKGROUND, stateId, option->rect);
+ d->drawBackground(popupbackgroundTheme);
+ }
+
case PE_PanelMenuBar:
break;
diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp
index 784e69d2486..a98f5da2389 100644
--- a/src/testlib/qtestcase.cpp
+++ b/src/testlib/qtestcase.cpp
@@ -410,7 +410,7 @@ public:
static QMetaMethod findMethod(const QObject *obj, const char *signature);
private:
- bool invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const;
+ void invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const;
void invokeTestOnData(int index) const;
QMetaMethod m_initTestCaseMethod; // might not exist, check isValid().
@@ -1315,11 +1315,8 @@ static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView t
Call slot_data(), init(), slot(), cleanup(), init(), slot(), cleanup(), ...
If data is set then it is the only test that is performed
-
- If the function was successfully called, true is returned, otherwise
- false.
*/
-bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const
+void TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const
{
QBenchmarkTestMethodData benchmarkData;
QBenchmarkTestMethodData::current = &benchmarkData;
@@ -1422,8 +1419,6 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<Wat
QTestResult::finishedCurrentTestFunction();
QTestResult::setSkipCurrentTest(false);
QTestResult::setBlacklistCurrentTest(false);
-
- return true;
}
void *fetchData(QTestData *data, const char *tagName, int typeId)
@@ -1743,10 +1738,8 @@ void TestMethods::invokeTests(QObject *testObject) const
const char *data = nullptr;
if (i < QTest::testTags.size() && !QTest::testTags.at(i).isEmpty())
data = qstrdup(QTest::testTags.at(i).toLatin1().constData());
- const bool ok = invokeTest(i, QLatin1StringView(data), watchDog);
+ invokeTest(i, QLatin1StringView(data), watchDog);
delete [] data;
- if (!ok)
- break;
}
}
diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp
index e35330ebeb3..6cb935ef47d 100644
--- a/src/tools/windeployqt/main.cpp
+++ b/src/tools/windeployqt/main.cpp
@@ -592,15 +592,17 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse
}
// default to deployment of compiler runtime for windows desktop configurations
- if (options->platform == WindowsDesktopMinGW || options->platform.testFlags(WindowsDesktopMsvc)
- || parser->isSet(compilerRunTimeOption))
+ if (options->platform == WindowsDesktopMinGW || options->platform == WindowsDesktopClangMinGW
+ || options->platform.testFlags(WindowsDesktopMsvc) || parser->isSet(compilerRunTimeOption))
options->compilerRunTime = true;
if (parser->isSet(noCompilerRunTimeOption))
options->compilerRunTime = false;
if (options->compilerRunTime && options->platform != WindowsDesktopMinGW
+ && options->platform != WindowsDesktopClangMinGW
&& !options->platform.testFlags(WindowsDesktopMsvc)) {
- *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only.");
+ *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for "
+ "Desktop MSVC and MinGW (g++ and Clang) only.");
return CommandLineParseError;
}
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index ce30f50616b..c0be3debe49 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -681,6 +681,7 @@ qt_internal_extend_target(Widgets CONDITION MACOS AND (QT_FEATURE_menu OR QT_FEA
qt_internal_extend_target(Widgets CONDITION QT_FEATURE_colordialog
SOURCES
dialogs/qcolordialog.cpp dialogs/qcolordialog.h
+ dialogs/qcolorwell_p.h
)
qt_internal_extend_target(Widgets CONDITION QT_FEATURE_dialog
diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp
index fc969e17380..265c523eae0 100644
--- a/src/widgets/accessible/itemviews.cpp
+++ b/src/widgets/accessible/itemviews.cpp
@@ -60,7 +60,7 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const
}
QAccessibleTable::QAccessibleTable(QWidget *w)
- : QAccessibleObject(w)
+ : QAccessibleWidgetV2(w)
{
Q_ASSERT(view());
@@ -677,7 +677,7 @@ void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t)
return static_cast<QAccessibleSelectionInterface*>(this);
if (t == QAccessible::TableInterface)
return static_cast<QAccessibleTableInterface*>(this);
- return nullptr;
+ return QAccessibleWidgetV2::interface_cast(t);
}
void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event)
diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h
index 9b30f36ced3..79f9a7f2f05 100644
--- a/src/widgets/accessible/itemviews_p.h
+++ b/src/widgets/accessible/itemviews_p.h
@@ -31,7 +31,9 @@ QT_BEGIN_NAMESPACE
class QAccessibleTableCell;
class QAccessibleTableHeaderCell;
-class QAccessibleTable :public QAccessibleTableInterface, public QAccessibleSelectionInterface, public QAccessibleObject
+class QAccessibleTable : public QAccessibleTableInterface,
+ public QAccessibleSelectionInterface,
+ public QAccessibleWidgetV2
{
public:
explicit QAccessibleTable(QWidget *w);
diff --git a/src/widgets/accessible/rangecontrols.cpp b/src/widgets/accessible/rangecontrols.cpp
index c0de5357c9a..1f7b20833dd 100644
--- a/src/widgets/accessible/rangecontrols.cpp
+++ b/src/widgets/accessible/rangecontrols.cpp
@@ -65,6 +65,16 @@ QAccessibleInterface *QAccessibleAbstractSpinBox::lineEditIface() const
#endif
}
+QAccessible::State QAccessibleAbstractSpinBox::state() const
+{
+ QAccessible::State state = QAccessibleWidgetV2::state();
+ if (abstractSpinBox()->isReadOnly())
+ state.readOnly = true;
+ else
+ state.editable = true;
+ return state;
+}
+
QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const
{
if (t == QAccessible::Value)
diff --git a/src/widgets/accessible/rangecontrols_p.h b/src/widgets/accessible/rangecontrols_p.h
index dd5a6a4531c..5a023d2f00b 100644
--- a/src/widgets/accessible/rangecontrols_p.h
+++ b/src/widgets/accessible/rangecontrols_p.h
@@ -42,6 +42,7 @@ public:
explicit QAccessibleAbstractSpinBox(QWidget *w);
virtual ~QAccessibleAbstractSpinBox();
+ QAccessible::State state() const override;
QString text(QAccessible::Text t) const override;
void *interface_cast(QAccessible::InterfaceType t) override;
diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp
index eac5de33d32..f8125204045 100644
--- a/src/widgets/dialogs/qcolordialog.cpp
+++ b/src/widgets/dialogs/qcolordialog.cpp
@@ -39,6 +39,7 @@
#include "qwindow.h"
#include "private/qdialog_p.h"
+#include "private/qcolorwell_p.h"
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformservices.h>
@@ -56,16 +57,12 @@ namespace QtPrivate {
class QColorLuminancePicker;
class QColorPicker;
class QColorShower;
-class QWellArray;
-class QColorWell;
class QColorPickingEventFilter;
} // namespace QtPrivate
using QColorLuminancePicker = QtPrivate::QColorLuminancePicker;
using QColorPicker = QtPrivate::QColorPicker;
using QColorShower = QtPrivate::QColorShower;
-using QWellArray = QtPrivate::QWellArray;
-using QColorWell = QtPrivate::QColorWell;
using QColorPickingEventFilter = QtPrivate::QColorPickingEventFilter;
class QColorDialogPrivate : public QDialogPrivate
@@ -162,95 +159,6 @@ private:
//////////// QWellArray BEGIN
-namespace QtPrivate {
-
-class QWellArray : public QWidget
-{
- Q_OBJECT
- Q_PROPERTY(int selectedColumn READ selectedColumn)
- Q_PROPERTY(int selectedRow READ selectedRow)
-
-public:
- QWellArray(int rows, int cols, QWidget* parent=nullptr);
- ~QWellArray() {}
-
- int selectedColumn() const { return selCol; }
- int selectedRow() const { return selRow; }
-
- virtual void setCurrent(int row, int col);
- virtual void setSelected(int row, int col);
-
- QSize sizeHint() const override;
-
- inline int cellWidth() const
- { return cellw; }
-
- inline int cellHeight() const
- { return cellh; }
-
- inline int rowAt(int y) const
- { return y / cellh; }
-
- inline int columnAt(int x) const
- { if (isRightToLeft()) return ncols - (x / cellw) - 1; return x / cellw; }
-
- inline int rowY(int row) const
- { return cellh * row; }
-
- inline int columnX(int column) const
- { if (isRightToLeft()) return cellw * (ncols - column - 1); return cellw * column; }
-
- inline int numRows() const
- { return nrows; }
-
- inline int numCols() const
- {return ncols; }
-
- inline QRect cellRect() const
- { return QRect(0, 0, cellw, cellh); }
-
- inline QSize gridSize() const
- { return QSize(ncols * cellw, nrows * cellh); }
-
- QRect cellGeometry(int row, int column)
- {
- QRect r;
- if (row >= 0 && row < nrows && column >= 0 && column < ncols)
- r.setRect(columnX(column), rowY(row), cellw, cellh);
- return r;
- }
-
- inline void updateCell(int row, int column) { update(cellGeometry(row, column)); }
-
-signals:
- void selected(int row, int col);
- void currentChanged(int row, int col);
- void colorChanged(int index, QRgb color);
-
-protected:
- virtual void paintCell(QPainter *, int row, int col, const QRect&);
- virtual void paintCellContents(QPainter *, int row, int col, const QRect&);
-
- void mousePressEvent(QMouseEvent*) override;
- void mouseReleaseEvent(QMouseEvent*) override;
- void keyPressEvent(QKeyEvent*) override;
- void focusInEvent(QFocusEvent*) override;
- void focusOutEvent(QFocusEvent*) override;
- void paintEvent(QPaintEvent *) override;
-
-private:
- Q_DISABLE_COPY(QWellArray)
-
- int nrows;
- int ncols;
- int cellw;
- int cellh;
- int curRow;
- int curCol;
- int selRow;
- int selCol;
-};
-
void QWellArray::paintEvent(QPaintEvent *e)
{
QRect r = e->rect();
@@ -475,11 +383,12 @@ void QWellArray::keyPressEvent(QKeyEvent* e)
e->ignore(); // we don't accept the event
return;
}
-
-} // namespace QtPrivate
+}
//////////// QWellArray END
+namespace QtPrivate {
+
// Event filter to be installed on the dialog while in color-picking mode.
class QColorPickingEventFilter : public QObject {
public:
@@ -510,7 +419,7 @@ private:
QColorDialogPrivate *m_dp;
};
-} // unnamed namespace
+} // namespace QtPrivate
/*!
Returns the number of custom colors supported by QColorDialog. All
@@ -570,35 +479,6 @@ static inline void rgb2hsv(QRgb rgb, int &h, int &s, int &v)
c.getHsv(&h, &s, &v);
}
-namespace QtPrivate {
-
-class QColorWell : public QWellArray
-{
-public:
- QColorWell(QWidget *parent, int r, int c, const QRgb *vals)
- :QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1)
- { setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); }
-
-protected:
- void paintCellContents(QPainter *, int row, int col, const QRect&) override;
- void mousePressEvent(QMouseEvent *e) override;
- void mouseMoveEvent(QMouseEvent *e) override;
- void mouseReleaseEvent(QMouseEvent *e) override;
-#if QT_CONFIG(draganddrop)
- void dragEnterEvent(QDragEnterEvent *e) override;
- void dragLeaveEvent(QDragLeaveEvent *e) override;
- void dragMoveEvent(QDragMoveEvent *e) override;
- void dropEvent(QDropEvent *e) override;
-#endif
-
-private:
- const QRgb *values;
- bool mousePressed;
- QPoint pressPos;
- QPoint oldCurrent;
-
-};
-
void QColorWell::paintCellContents(QPainter *p, int row, int col, const QRect &r)
{
int i = row + col*numRows();
@@ -686,6 +566,8 @@ void QColorWell::mouseReleaseEvent(QMouseEvent *e)
mousePressed = false;
}
+namespace QtPrivate {
+
class QColorPicker : public QFrame
{
Q_OBJECT
@@ -703,18 +585,21 @@ signals:
protected:
QSize sizeHint() const override;
void paintEvent(QPaintEvent*) override;
+ void keyPressEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
void resizeEvent(QResizeEvent *) override;
private:
- int hue;
- int sat;
+ QPoint m_pos;
- QPoint colPt();
- int huePt(const QPoint &pt);
- int satPt(const QPoint &pt);
- void setCol(const QPoint &pt);
+ QPixmap createColorsPixmap();
+ QPoint colPt(int hue, int sat);
+ int huePt(const QPoint &pt, const QSize &widgetSize);
+ int huePt(const QPoint &pt) { return huePt(pt, size()); }
+ int satPt(const QPoint &pt, const QSize &widgetSize);
+ int satPt(const QPoint &pt) { return satPt(pt, size()); }
+ void setCol(const QPoint &pt, bool notify = true);
QPixmap pix;
bool crossVisible;
@@ -743,6 +628,7 @@ signals:
protected:
void paintEvent(QPaintEvent*) override;
+ void keyPressEvent(QKeyEvent *event) override;
void mouseMoveEvent(QMouseEvent *) override;
void mousePressEvent(QMouseEvent *) override;
@@ -778,6 +664,7 @@ QColorLuminancePicker::QColorLuminancePicker(QWidget* parent)
hue = 100; val = 100; sat = 100;
pix = nullptr;
// setAttribute(WA_NoErase, true);
+ setFocusPolicy(Qt::StrongFocus);
}
QColorLuminancePicker::~QColorLuminancePicker()
@@ -785,6 +672,21 @@ QColorLuminancePicker::~QColorLuminancePicker()
delete pix;
}
+void QColorLuminancePicker::keyPressEvent(QKeyEvent *event)
+{
+ switch (event->key()) {
+ case Qt::Key_Down:
+ setVal(std::clamp(val - 1, 0, 255));
+ break;
+ case Qt::Key_Up:
+ setVal(std::clamp(val + 1, 0, 255));
+ break;
+ default:
+ QWidget::keyPressEvent(event);
+ break;
+ }
+}
+
void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m)
{
if (m->buttons() == Qt::NoButton) {
@@ -855,38 +757,53 @@ void QColorLuminancePicker::setCol(int h, int s , int v)
repaint();
}
-QPoint QColorPicker::colPt()
+QPoint QColorPicker::colPt(int hue, int sat)
{
QRect r = contentsRect();
return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255);
}
-int QColorPicker::huePt(const QPoint &pt)
+int QColorPicker::huePt(const QPoint &pt, const QSize &widgetSize)
{
- QRect r = contentsRect();
- return 360 - pt.x() * 360 / (r.width() - 1);
+ QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins();
+ return std::clamp(360 - pt.x() * 360 / (r.width() - 1), 0, 359);
}
-int QColorPicker::satPt(const QPoint &pt)
+int QColorPicker::satPt(const QPoint &pt, const QSize &widgetSize)
{
- QRect r = contentsRect();
- return 255 - pt.y() * 255 / (r.height() - 1);
+ QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins();
+ return std::clamp(255 - pt.y() * 255 / (r.height() - 1), 0, 255);
}
-void QColorPicker::setCol(const QPoint &pt)
+void QColorPicker::setCol(const QPoint &pt, bool notify)
{
- setCol(huePt(pt), satPt(pt));
+ if (pt == m_pos)
+ return;
+
+ QRect r(m_pos, QSize(20, 20));
+ m_pos.setX(std::clamp(pt.x(), 0, pix.width() - 1));
+ m_pos.setY(std::clamp(pt.y(), 0, pix.height() - 1));
+ r = r.united(QRect(m_pos, QSize(20, 20)));
+ r.translate(contentsRect().x() - 9, contentsRect().y() - 9);
+ // update(r);
+ repaint(r);
+
+ if (notify)
+ emit newCol(huePt(m_pos), satPt(m_pos));
}
QColorPicker::QColorPicker(QWidget* parent)
: QFrame(parent)
, crossVisible(true)
{
- hue = 0; sat = 0;
- setCol(150, 255);
-
setAttribute(Qt::WA_NoSystemBackground);
+ setFocusPolicy(Qt::StrongFocus);
setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) );
+ adjustSize();
+
+ pix = createColorsPixmap();
+
+ setCol(150, 255);
}
QColorPicker::~QColorPicker()
@@ -910,15 +827,31 @@ void QColorPicker::setCol(int h, int s)
{
int nhue = qMin(qMax(0,h), 359);
int nsat = qMin(qMax(0,s), 255);
- if (nhue == hue && nsat == sat)
+ if (nhue == huePt(m_pos) && nsat == satPt(m_pos))
return;
- QRect r(colPt(), QSize(20,20));
- hue = nhue; sat = nsat;
- r = r.united(QRect(colPt(), QSize(20,20)));
- r.translate(contentsRect().x()-9, contentsRect().y()-9);
- // update(r);
- repaint(r);
+ setCol(colPt(nhue, nsat), false);
+}
+
+void QColorPicker::keyPressEvent(QKeyEvent *event)
+{
+ switch (event->key()) {
+ case Qt::Key_Down:
+ setCol(m_pos + QPoint(0, 1));
+ break;
+ case Qt::Key_Left:
+ setCol(m_pos + QPoint(-1, 0));
+ break;
+ case Qt::Key_Right:
+ setCol(m_pos + QPoint(1, 0));
+ break;
+ case Qt::Key_Up:
+ setCol(m_pos + QPoint(0, -1));
+ break;
+ default:
+ QFrame::keyPressEvent(event);
+ break;
+ }
}
void QColorPicker::mouseMoveEvent(QMouseEvent *m)
@@ -929,14 +862,12 @@ void QColorPicker::mouseMoveEvent(QMouseEvent *m)
return;
}
setCol(p);
- emit newCol(hue, sat);
}
void QColorPicker::mousePressEvent(QMouseEvent *m)
{
QPoint p = m->position().toPoint() - contentsRect().topLeft();
setCol(p);
- emit newCol(hue, sat);
}
void QColorPicker::paintEvent(QPaintEvent* )
@@ -948,7 +879,7 @@ void QColorPicker::paintEvent(QPaintEvent* )
p.drawPixmap(r.topLeft(), pix);
if (crossVisible) {
- QPoint pt = colPt() + r.topLeft();
+ QPoint pt = m_pos + r.topLeft();
p.setPen(Qt::black);
p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black);
p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black);
@@ -959,6 +890,21 @@ void QColorPicker::resizeEvent(QResizeEvent *ev)
{
QFrame::resizeEvent(ev);
+ pix = createColorsPixmap();
+
+ const QSize &oldSize = ev->oldSize();
+ if (!oldSize.isValid())
+ return;
+
+ // calculate hue/saturation based on previous widget size
+ // and update position accordingly
+ const int hue = huePt(m_pos, oldSize);
+ const int sat = satPt(m_pos, oldSize);
+ setCol(hue, sat);
+}
+
+QPixmap QColorPicker::createColorsPixmap()
+{
int w = width() - frameWidth() * 2;
int h = height() - frameWidth() * 2;
QImage img(w, h, QImage::Format_RGB32);
@@ -976,10 +922,9 @@ void QColorPicker::resizeEvent(QResizeEvent *ev)
++x;
}
}
- pix = QPixmap::fromImage(img);
+ return QPixmap::fromImage(img);
}
-
class QColSpinBox : public QSpinBox
{
public:
diff --git a/src/widgets/dialogs/qcolorwell_p.h b/src/widgets/dialogs/qcolorwell_p.h
new file mode 100644
index 00000000000..31d69fabb13
--- /dev/null
+++ b/src/widgets/dialogs/qcolorwell_p.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2025 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
+
+#ifndef QCOLORWELL_P_H
+#define QCOLORWELL_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qrect.h>
+#include <QtWidgets/qwidget.h>
+
+QT_REQUIRE_CONFIG(colordialog);
+
+QT_BEGIN_NAMESPACE
+
+class QWellArray : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(int selectedColumn READ selectedColumn)
+ Q_PROPERTY(int selectedRow READ selectedRow)
+
+public:
+ QWellArray(int rows, int cols, QWidget *parent = nullptr);
+ ~QWellArray() { }
+
+ int selectedColumn() const { return selCol; }
+ int selectedRow() const { return selRow; }
+
+ virtual void setCurrent(int row, int col);
+ virtual void setSelected(int row, int col);
+
+ QSize sizeHint() const override;
+
+ inline int cellWidth() const { return cellw; }
+
+ inline int cellHeight() const { return cellh; }
+
+ inline int rowAt(int y) const { return y / cellh; }
+
+ inline int columnAt(int x) const
+ {
+ if (isRightToLeft())
+ return ncols - (x / cellw) - 1;
+ return x / cellw;
+ }
+
+ inline int rowY(int row) const { return cellh * row; }
+
+ inline int columnX(int column) const
+ {
+ if (isRightToLeft())
+ return cellw * (ncols - column - 1);
+ return cellw * column;
+ }
+
+ inline int numRows() const { return nrows; }
+
+ inline int numCols() const { return ncols; }
+
+ inline QRect cellRect() const { return QRect(0, 0, cellw, cellh); }
+
+ inline QSize gridSize() const { return QSize(ncols * cellw, nrows * cellh); }
+
+ QRect cellGeometry(int row, int column)
+ {
+ QRect r;
+ if (row >= 0 && row < nrows && column >= 0 && column < ncols)
+ r.setRect(columnX(column), rowY(row), cellw, cellh);
+ return r;
+ }
+
+ inline void updateCell(int row, int column) { update(cellGeometry(row, column)); }
+
+signals:
+ void selected(int row, int col);
+ void currentChanged(int row, int col);
+ void colorChanged(int index, QRgb color);
+
+protected:
+ virtual void paintCell(QPainter *, int row, int col, const QRect &);
+ virtual void paintCellContents(QPainter *, int row, int col, const QRect &);
+
+ void mousePressEvent(QMouseEvent *) override;
+ void mouseReleaseEvent(QMouseEvent *) override;
+ void keyPressEvent(QKeyEvent *) override;
+ void focusInEvent(QFocusEvent *) override;
+ void focusOutEvent(QFocusEvent *) override;
+ void paintEvent(QPaintEvent *) override;
+
+private:
+ Q_DISABLE_COPY(QWellArray)
+
+ int nrows;
+ int ncols;
+ int cellw;
+ int cellh;
+ int curRow;
+ int curCol;
+ int selRow;
+ int selCol;
+};
+
+class QColorWell : public QWellArray
+{
+public:
+ QColorWell(QWidget *parent, int r, int c, const QRgb *vals)
+ : QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1)
+ {
+ setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
+ }
+
+protected:
+ void paintCellContents(QPainter *, int row, int col, const QRect &) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+#if QT_CONFIG(draganddrop)
+ void dragEnterEvent(QDragEnterEvent *e) override;
+ void dragLeaveEvent(QDragLeaveEvent *e) override;
+ void dragMoveEvent(QDragMoveEvent *e) override;
+ void dropEvent(QDropEvent *e) override;
+#endif
+
+private:
+ const QRgb *values;
+ bool mousePressed;
+ QPoint pressPos;
+ QPoint oldCurrent;
+};
+
+QT_END_NAMESPACE
+
+#endif // QCOLORWELL_P_H
diff --git a/src/widgets/dialogs/qfiledialog.ui b/src/widgets/dialogs/qfiledialog.ui
index f275e20c633..97f39fa6194 100644
--- a/src/widgets/dialogs/qfiledialog.ui
+++ b/src/widgets/dialogs/qfiledialog.ui
@@ -20,7 +20,10 @@
<item row="0" column="0">
<widget class="QLabel" name="lookInLabel">
<property name="text">
- <string>Look in:</string>
+ <string>&amp;Look in:</string>
+ </property>
+ <property name="buddy">
+ <cstring>lookInCombo</cstring>
</property>
</widget>
</item>
@@ -284,7 +287,10 @@
</sizepolicy>
</property>
<property name="text">
- <string>Files of type:</string>
+ <string>Files of &amp;type:</string>
+ </property>
+ <property name="buddy">
+ <cstring>fileTypeCombo</cstring>
</property>
</widget>
</item>
diff --git a/src/widgets/dialogs/qfontdialog.cpp b/src/widgets/dialogs/qfontdialog.cpp
index 870b0c40faf..c4f8af1a639 100644
--- a/src/widgets/dialogs/qfontdialog.cpp
+++ b/src/widgets/dialogs/qfontdialog.cpp
@@ -172,7 +172,7 @@ void QFontDialogPrivate::init()
sizeAccel = new QLabel(q);
#ifndef QT_NO_SHORTCUT
- sizeAccel->setBuddy(sizeEdit);
+ sizeAccel->setBuddy(sizeList);
#endif
sizeAccel->setIndent(2);
diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp
index 00f9766af29..5ba92714f6c 100644
--- a/src/widgets/kernel/qlayout.cpp
+++ b/src/widgets/kernel/qlayout.cpp
@@ -345,6 +345,17 @@ void QLayout::getContentsMargins(int *left, int *top, int *right, int *bottom) c
}
/*!
+ \property QLayout::contentsMargins
+ \since 4.6
+ \brief the margins used around the layout
+
+ By default, QLayout uses the values provided by the style. On
+ most platforms, the margin is 11 pixels in all directions.
+
+ \sa setContentsMargins(), getContentsMargins()
+*/
+
+/*!
\since 4.6
Returns the margins used around the layout.
diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp
index c4b78539114..bb9f7ab27fc 100644
--- a/src/widgets/styles/qcommonstyle.cpp
+++ b/src/widgets/styles/qcommonstyle.cpp
@@ -5,15 +5,12 @@
#include "qcommonstyle.h"
#include "qcommonstyle_p.h"
-#include <qfile.h>
#if QT_CONFIG(itemviews)
#include <qabstractitemview.h>
#endif
#include <qapplication.h>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformtheme.h>
-#include <qbitmap.h>
-#include <qcache.h>
#if QT_CONFIG(dockwidget)
#include <qdockwidget.h>
#endif
@@ -61,7 +58,6 @@
#endif
#include <private/qcommonstylepixmaps_p.h>
#include <private/qmath_p.h>
-#include <qdebug.h>
#include <qtextformat.h>
#if QT_CONFIG(wizard)
#include <qwizard.h>
@@ -69,11 +65,6 @@
#if QT_CONFIG(filedialog)
#include <qsidebar_p.h>
#endif
-#include <qfileinfo.h>
-#include <qdir.h>
-#if QT_CONFIG(settings)
-#include <qsettings.h>
-#endif
#include <qvariant.h>
#include <qpixmapcache.h>
#if QT_CONFIG(animation)
@@ -6199,17 +6190,17 @@ QPixmap QCommonStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &p
return QPixmap::fromImage(std::move(im));
}
case QIcon::Selected: {
- QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
QColor color = opt->palette.color(QPalette::Normal, QPalette::Highlight);
color.setAlphaF(0.3f);
- QPainter painter(&img);
+ QPixmap ret(pixmap);
+ QPainter painter(&ret);
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
- painter.fillRect(0, 0, img.width(), img.height(), color);
+ painter.fillRect(0, 0, pixmap.width(), pixmap.height(), color);
painter.end();
- return QPixmap::fromImage(std::move(img)); }
+ return ret;
+ }
case QIcon::Active:
- return pixmap;
- default:
+ case QIcon::Normal:
break;
}
return pixmap;
diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp
index 9b06822c218..b9143a59ee7 100644
--- a/src/widgets/styles/qwindowsstyle.cpp
+++ b/src/widgets/styles/qwindowsstyle.cpp
@@ -851,6 +851,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt,
}
#ifndef QT_NO_FRAME
case PE_Frame:
+ if (w && w->inherits("QComboBoxPrivateContainer")){
+ QStyleOption copy = *opt;
+ copy.state |= State_Raised;
+ proxy()->drawPrimitive(PE_PanelMenu, &copy, p, w);
+ break;
+ }
case PE_FrameMenu:
if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) {
if (frame->lineWidth == 2 || pe == PE_Frame) {
@@ -873,6 +879,7 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt,
}
} else {
QPalette popupPal = opt->palette;
+ p->drawRect(opt->rect);
popupPal.setColor(QPalette::Light, opt->palette.window().color());
popupPal.setColor(QPalette::Midlight, opt->palette.light().color());
qDrawWinPanel(p, opt->rect, popupPal, opt->state & State_Sunken);
@@ -899,6 +906,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt,
p->drawRect(opt->rect);
}
break; }
+ case PE_PanelMenu:
+ if (w && w->inherits("QComboBoxPrivateContainer")){
+ const QBrush menuBackground = opt->palette.base().color();
+ QColor borderColor = opt->palette.window().color();
+ qDrawPlainRect(p, opt->rect, borderColor, 1, &menuBackground);
+ }
case PE_FrameWindow: {
QPalette popupPal = opt->palette;
popupPal.setColor(QPalette::Light, opt->palette.window().color());
diff --git a/src/widgets/widgets/qlineedit_p.cpp b/src/widgets/widgets/qlineedit_p.cpp
index 55e6137dba9..ee80cca649c 100644
--- a/src/widgets/widgets/qlineedit_p.cpp
+++ b/src/widgets/widgets/qlineedit_p.cpp
@@ -353,17 +353,16 @@ QLineEditPrivate *QLineEditIconButton::lineEditPrivate() const
void QLineEditIconButton::paintEvent(QPaintEvent *)
{
QPainter painter(this);
- QIcon::Mode state = QIcon::Disabled;
+ QIcon::Mode mode = QIcon::Disabled;
if (isEnabled())
- state = isDown() ? QIcon::Active : QIcon::Normal;
+ mode = isDown() ? QIcon::Active : QIcon::Normal;
const QLineEditPrivate *lep = lineEditPrivate();
const int iconWidth = lep ? lep->sideWidgetParameters().iconSize : 16;
const QSize iconSize(iconWidth, iconWidth);
- const QPixmap iconPixmap = icon().pixmap(iconSize, devicePixelRatio(), state, QIcon::Off);
QRect pixmapRect = QRect(QPoint(0, 0), iconSize);
pixmapRect.moveCenter(rect().center());
painter.setOpacity(m_opacity);
- painter.drawPixmap(pixmapRect, iconPixmap);
+ icon().paint(&painter, pixmapRect, Qt::AlignCenter, mode, QIcon::Off);
}
void QLineEditIconButton::actionEvent(QActionEvent *e)
diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp
index 0b562a47879..8e6f497d7f5 100644
--- a/src/widgets/widgets/qtabbar.cpp
+++ b/src/widgets/widgets/qtabbar.cpp
@@ -1003,7 +1003,7 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text)
++tab->lastTab;
}
- if (tabAt(d->mousePosition) == index) {
+ if (isVisible() && tabAt(d->mousePosition) == index) {
d->hoverIndex = index;
d->hoverRect = tabRect(index);
}
diff --git a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
index d0ef37d817f..e9578d7246a 100644
--- a/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
+++ b/tests/auto/cmake/RunCMake/Sbom/CMakeLists.txt
@@ -1,3 +1,7 @@
cmake_minimum_required(VERSION 3.16)
project(${RunCMake_TEST} LANGUAGES CXX)
-include(${RunCMake_TEST}.cmake)
+
+# To allow including the gen files in other subdirectory projects.
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+include(${SBOM_INCLUDE_FILE}.cmake)
diff --git a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
index 3103e651093..81e83ccead3 100644
--- a/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/RunCMakeTest.cmake
@@ -1,27 +1,68 @@
include(QtRunCMake)
-function(run_cmake_and_build case)
+function(run_cmake_and_build case format_case)
+ set(include_file "${case}")
+ set(case "${format_case}-${case}")
+
# Set common build directory for configure and build
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
set(options
"-DQt6_DIR=${Qt6_DIR}"
"-DCMAKE_INSTALL_PREFIX=${RunCMake_TEST_BINARY_DIR}/installed"
+ "-DSBOM_INCLUDE_FILE=${include_file}"
+ "-DFORMAT_CASE=${format_case}"
)
+ if(format_case STREQUAL "spdx23")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=ON
+ -DQT_SBOM_GENERATE_CYDX_V1_6=OFF
+ )
+ elseif(format_case STREQUAL "cydx16")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=OFF
+ -DQT_SBOM_GENERATE_CYDX_V1_6=ON
+ )
+ elseif(format_case STREQUAL "all")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=ON
+ -DQT_SBOM_GENERATE_SPDX_V2=ON
+ -DQT_SBOM_GENERATE_CYDX_V1_6=ON
+ )
+ elseif(format_case STREQUAL "none")
+ list(APPEND options
+ -DQT_GENERATE_SBOM=OFF
+ )
+ endif()
+
+ # Check CI environment variables for SBOM options to ensure we only enabled checks that
+ # require additional dependencies on machines that actually have them.
+ # Also allow force enabling all checks via QT_SBOM_FORCE_ALL_CHECKS env var.
set(maybe_sbom_env_args "$ENV{SBOM_COMMON_ARGS}")
+ set(force_all_checks "$ENV{QT_SBOM_FORCE_ALL_CHECKS}")
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_DEFAULT_CHECKS=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_DEFAULT_CHECKS=ON")
endif()
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT=ON")
endif()
- if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON")
+ if(maybe_sbom_env_args MATCHES "QT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON"
+ OR force_all_checks)
list(APPEND options "-DQT_INTERNAL_SBOM_AUDIT_NO_ERROR=ON")
endif()
+ if(maybe_sbom_env_args MATCHES "QT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON"
+ OR force_all_checks)
+ list(APPEND options "-DQT_SBOM_REQUIRE_GENERATE_CYDX_V1_6=ON")
+ endif()
+
# Need to pass the python interpreter paths, to avoid sbom2doc not found errors.
# This mirrors what coin/instructions/prepare_building_env.yaml does.
set(maybe_python3_path "$ENV{PYTHON3_PATH}")
@@ -42,9 +83,17 @@ function(run_cmake_and_build case)
# fine.
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
+
+ # Check the sbom files are present after installation.
+ set(RunCMake-check-file "check.cmake")
run_cmake_command(${case}-install ${CMAKE_COMMAND} --install .)
+ unset(RunCMake-check-file)
endfunction()
-run_cmake_and_build(minimal)
-run_cmake_and_build(full)
-run_cmake_and_build(versions)
+set(format_cases spdx23 cydx16 all none)
+foreach(format_case IN LISTS format_cases)
+ run_cmake_and_build(minimal "${format_case}")
+ run_cmake_and_build(full "${format_case}")
+ run_cmake_and_build(versions "${format_case}")
+endforeach()
+
diff --git a/tests/auto/cmake/RunCMake/Sbom/check.cmake b/tests/auto/cmake/RunCMake/Sbom/check.cmake
new file mode 100644
index 00000000000..9e143e35ab2
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/check.cmake
@@ -0,0 +1,63 @@
+function(check_exists file)
+ if(NOT EXISTS "${file}")
+ get_filename_component(file_dir "${file}" DIRECTORY)
+ file(GLOB dir_contents "${file_dir}/*")
+ string(APPEND RunCMake_TEST_FAILED "${file} does not exist\n. "
+ "Contents of directory ${file_dir}:\n ${dir_contents}\n")
+ endif()
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+function(check_not_exists file)
+ if(EXISTS "${file}")
+ get_filename_component(file_dir "${file}" DIRECTORY)
+ file(GLOB dir_contents "${file_dir}/*")
+ string(APPEND RunCMake_TEST_FAILED "${file} exists\n. "
+ "Contents of directory ${file_dir}:\n ${dir_contents}\n")
+ endif()
+ set(RunCMake_TEST_FAILED "${RunCMake_TEST_FAILED}" PARENT_SCOPE)
+endfunction()
+
+# Check that the correct option values are used for the project root sbom.
+set(root_result_file "${RunCMake_TEST_BINARY_DIR}/result.cmake")
+if(EXISTS "${root_result_file}")
+ include("${root_result_file}")
+
+ if(NOT "${ORIGINAL_QT_GENERATE_SBOM}" STREQUAL "${RESULT_QT_GENERATE_SBOM}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_GENERATE_SBOM is ${RESULT_QT_GENERATE_SBOM}, expected ${ORIGINAL_QT_GENERATE_SBOM} \n")
+ endif()
+
+ if(NOT "${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2}" STREQUAL "${RESULT_QT_SBOM_GENERATE_SPDX_V2}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_SBOM_GENERATE_SPDX_V2 is ${RESULT_QT_SBOM_GENERATE_SPDX_V2}, "
+ "expected ${ORIGINAL_QT_SBOM_GENERATE_SPDX_V2} \n")
+ endif()
+
+ if(NOT "${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6}" STREQUAL "${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}")
+ string(APPEND RunCMake_TEST_FAILED
+ "QT_SBOM_GENERATE_CYDX_V1_6 is ${RESULT_QT_SBOM_GENERATE_CYDX_V1_6}, "
+ "expected ${ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6} \n")
+ endif()
+endif()
+
+
+# Glob for all result.cmake files recursively in the root of the test binary dir, and run checks
+# for each of them.
+file(GLOB_RECURSE result_files
+ "${RunCMake_TEST_BINARY_DIR}/**/result.cmake"
+)
+
+# Confirm that the all subproject sbom files are installed, including the root one.
+foreach(result_file IN LISTS result_files)
+ include("${result_file}")
+
+ foreach(sbom_doc IN LISTS SBOM_DOCUMENTS)
+ check_exists("${sbom_doc}")
+ endforeach()
+
+ foreach(sbom_doc IN LISTS NO_SBOM_DOCUMENTS)
+ check_not_exists("${sbom_doc}")
+ endforeach()
+endforeach()
+
diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake
new file mode 100644
index 00000000000..a892fee2082
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGen.cmake
@@ -0,0 +1,67 @@
+if(NOT SBOM_PROJECT_NAME)
+ set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+endif()
+# Convert to lower case, otherwise on case-sensitive filesystems the generated
+# filenames may not match the expected ones.
+string(TOLOWER "${SBOM_PROJECT_NAME}" SBOM_PROJECT_NAME)
+
+if(NOT SBOM_VERSION)
+ set(SBOM_VERSION "1.0.0")
+endif()
+
+if(NOT SBOM_INSTALL_DIR)
+ set(SBOM_INSTALL_DIR "sbom")
+endif()
+
+set(sbom_document_base_name "${SBOM_PROJECT_NAME}-${SBOM_VERSION}")
+set(sbom_install_dir "${CMAKE_BINARY_DIR}/installed/${SBOM_INSTALL_DIR}")
+
+set(spdx_file "${sbom_install_dir}/${sbom_document_base_name}.spdx")
+set(spdx_json_file "${sbom_install_dir}/${sbom_document_base_name}.spdx.json")
+set(cydx_file "${sbom_install_dir}/${sbom_document_base_name}.cdx.json")
+
+set(sbom_documents "")
+set(no_sbom_documents "")
+
+if(FORMAT_CASE STREQUAL "spdx23" OR FORMAT_CASE STREQUAL "all")
+ if(QT_SBOM_GENERATE_SPDX_V2)
+ list(APPEND sbom_documents "${spdx_file}")
+ else()
+ list(APPEND no_sbom_documents "${spdx_file}")
+ endif()
+
+ if(QT_SBOM_GENERATE_SPDX_V2_JSON)
+ list(APPEND sbom_documents "${spdx_json_file}")
+ else()
+ list(APPEND no_sbom_documents "${spdx_json_file}")
+ endif()
+endif()
+
+if(FORMAT_CASE STREQUAL "cydx16" OR FORMAT_CASE STREQUAL "all")
+ if(QT_SBOM_GENERATE_CYDX_V1_6)
+ list(APPEND sbom_documents "${cydx_file}")
+ else()
+ list(APPEND no_sbom_documents "${cydx_file}")
+ endif()
+endif()
+
+if(FORMAT_CASE STREQUAL "none")
+ set(no_sbom_documents ${spdx_file} ${spdx_json_file} ${cydx_file})
+ set(sbom_documents "")
+endif()
+
+# These values will be used by the check.cmake script after installation.
+file(GENERATE
+ OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/result.cmake"
+ CONTENT
+ "
+set(SBOM_DOCUMENTS \"${sbom_documents}\")
+set(NO_SBOM_DOCUMENTS \"${no_sbom_documents}\")
+set(ORIGINAL_QT_GENERATE_SBOM \"${original_QT_GENERATE_SBOM}\")
+set(ORIGINAL_QT_SBOM_GENERATE_SPDX_V2 \"${original_QT_SBOM_GENERATE_SPDX_V2}\")
+set(ORIGINAL_QT_SBOM_GENERATE_CYDX_V1_6 \"${original_QT_SBOM_GENERATE_CYDX_V1_6}\")
+set(RESULT_QT_GENERATE_SBOM \"${QT_GENERATE_SBOM}\")
+set(RESULT_QT_SBOM_GENERATE_SPDX_V2 \"${QT_SBOM_GENERATE_SPDX_V2}\")
+set(RESULT_QT_SBOM_GENERATE_CYDX_V1_6 \"${QT_SBOM_GENERATE_CYDX_V1_6}\")
+"
+)
diff --git a/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake
new file mode 100644
index 00000000000..11ec50d76d6
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/cmake/CommonResultGenIntro.cmake
@@ -0,0 +1,12 @@
+# Record the sbom option values before they might be modified by an sbom_setup call, due to
+# missing python dependencies.
+set(original_QT_GENERATE_SBOM "${QT_GENERATE_SBOM}")
+set(original_QT_SBOM_GENERATE_SPDX_V2 "${QT_SBOM_GENERATE_SPDX_V2}")
+set(original_QT_SBOM_GENERATE_CYDX_V1_6 "${QT_SBOM_GENERATE_CYDX_V1_6}")
+
+# Explicitly set these because none case only has QT_GENERATE_SBOM passed as OFF.
+# In this case, the defaults for the formats is to remain ON.
+if(NOT QT_GENERATE_SBOM)
+ set(original_QT_SBOM_GENERATE_SPDX_V2 ON)
+ set(original_QT_SBOM_GENERATE_CYDX_V1_6 ON)
+endif()
diff --git a/tests/auto/cmake/RunCMake/Sbom/full.cmake b/tests/auto/cmake/RunCMake/Sbom/full.cmake
index 7241b8e77a8..b5ac77c3ccc 100644
--- a/tests/auto/cmake/RunCMake/Sbom/full.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/full.cmake
@@ -1,23 +1,30 @@
# Needed to make the sbom functions available.
find_package(Qt6 REQUIRED Core)
+include(CommonResultGenIntro)
+
_qt_internal_setup_sbom(
GENERATE_SBOM_DEFAULT "TRUE"
)
set(IS_FULL_BUILD "TRUE")
+# These are used by common_result_gen.cmake.
+set(SBOM_VERSION "2.0.0")
+set(SBOM_INSTALL_DIR "sbom_full")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}ProjectFull")
+
_qt_internal_sbom_begin_project(
- SBOM_PROJECT_NAME "${PROJECT_NAME}Project"
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
SUPPLIER "QtProjectTest"
SUPPLIER_URL "https://qt-project.org/SbomTest"
LICENSE_EXPRESSION "LGPL-3.0-only"
COPYRIGHTS "2025 The Qt Company Ltd."
INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
- INSTALL_SBOM_DIR "sbom"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
DOWNLOAD_LOCATION "https://download.qt.io/sbom"
CPE "cpe:2.3:a:qt:qtprojecttest:1.0.0:*:*:*:*:*:*:*"
- VERSION "1.0.0"
+ VERSION "${SBOM_VERSION}"
DOCUMENT_CREATOR_TOOL "Test Build System Tool"
LICENSE_DIR_PATHS "${CMAKE_CURRENT_SOURCE_DIR}/custom_licenses"
)
@@ -26,3 +33,7 @@ include(common_targets.cmake)
_qt_internal_sbom_end_project()
+include(CommonResultGen)
+
+# Also create separate sboms for some sibling projects under this subdir.
+add_subdirectory(subprojects)
diff --git a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
index 4422314b2d5..27dfc0a3b12 100644
--- a/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
+++ b/tests/auto/cmake/RunCMake/Sbom/minimal.cmake
@@ -1,17 +1,26 @@
# Needed to make the sbom functions available.
find_package(Qt6 REQUIRED Core)
+include(CommonResultGenIntro)
+
_qt_internal_setup_sbom(
GENERATE_SBOM_DEFAULT "TRUE"
)
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.0")
+
_qt_internal_sbom_begin_project(
SUPPLIER "QtProjectTest"
SUPPLIER_URL "https://qt-project.org/SbomTest"
- VERSION "1.0.0"
+ VERSION "${SBOM_VERSION}"
)
include(common_targets.cmake)
_qt_internal_sbom_end_project()
+include(CommonResultGen)
+
+# Also create separate sboms for some sibling projects under this subdir.
+add_subdirectory(subprojects)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt
new file mode 100644
index 00000000000..0d9e12534df
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/CMakeLists.txt
@@ -0,0 +1,5 @@
+# These subprojects look up the same system library dependency separately in each subdirectory
+# scope to ensure that we simulate the same scenario as in a top-level qt5.git build, so that
+# we try to reuse the same system library entity in multiple sboms.
+add_subdirectory(subproj1)
+add_subdirectory(subproj2)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt
new file mode 100644
index 00000000000..a59908d8889
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/CMakeLists.txt
@@ -0,0 +1,51 @@
+project(subproj1)
+
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.1")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+
+_qt_internal_sbom_begin_project(
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
+ SUPPLIER "QtProjectTest"
+ SUPPLIER_URL "https://qt-project.org/SbomTest"
+ VERSION "${SBOM_VERSION}"
+)
+
+add_library(subproj1_helper STATIC)
+target_sources(subproj1_helper PRIVATE subproj1_helper.cpp)
+target_link_libraries(subproj1_helper)
+install(TARGETS subproj1_helper
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+)
+_qt_internal_add_sbom(subproj1_helper
+ TYPE "LIBRARY"
+ RUNTIME_PATH bin
+ ARCHIVE_PATH lib
+ LIBRARY_PATH lib
+)
+
+
+# This will actually refer to the root project Threads, that gets brought in via Qt6::Core
+# dependency.
+find_package(Threads)
+if(TARGET Threads::Threads)
+ _qt_internal_add_sbom(Threads::Threads
+ TYPE SYSTEM_LIBRARY
+ )
+ target_link_libraries(subproj1_helper PRIVATE Threads::Threads)
+endif()
+
+# Create another IMPORTED target that is meant to simulate a system library, so we don't refer to
+# a target that exists in the root scope.
+add_library(FancySystemLib IMPORTED INTERFACE)
+_qt_internal_add_sbom(FancySystemLib
+ TYPE SYSTEM_LIBRARY
+)
+target_link_libraries(subproj1_helper PRIVATE FancySystemLib)
+
+_qt_internal_sbom_end_project()
+
+include(CommonResultGen)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp
new file mode 100644
index 00000000000..f6b17d4b9f8
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj1/subproj1_helper.cpp
@@ -0,0 +1,4 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+int func() { return 0; };
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt
new file mode 100644
index 00000000000..0a408ddf47f
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/CMakeLists.txt
@@ -0,0 +1,46 @@
+project(subproj2)
+
+# This is used by common_result_gen.cmake.
+set(SBOM_VERSION "1.0.2")
+set(SBOM_PROJECT_NAME "${PROJECT_NAME}")
+
+_qt_internal_sbom_begin_project(
+ SBOM_PROJECT_NAME "${SBOM_PROJECT_NAME}"
+ INSTALL_SBOM_DIR "${SBOM_INSTALL_DIR}"
+ SUPPLIER "QtProjectTest"
+ SUPPLIER_URL "https://qt-project.org/SbomTest"
+ VERSION "${SBOM_VERSION}"
+)
+
+add_library(subproj2_helper STATIC)
+target_sources(subproj2_helper PRIVATE subproj2_helper.cpp)
+target_link_libraries(subproj2_helper)
+install(TARGETS subproj2_helper
+ RUNTIME DESTINATION bin
+ ARCHIVE DESTINATION lib
+ LIBRARY DESTINATION lib
+)
+_qt_internal_add_sbom(subproj2_helper
+ TYPE "LIBRARY"
+ RUNTIME_PATH bin
+ ARCHIVE_PATH lib
+ LIBRARY_PATH lib
+)
+
+find_package(Threads)
+if(TARGET Threads::Threads)
+ _qt_internal_add_sbom(Threads::Threads
+ TYPE SYSTEM_LIBRARY
+ )
+ target_link_libraries(subproj2_helper PRIVATE Threads::Threads)
+endif()
+
+add_library(FancySystemLib IMPORTED INTERFACE)
+_qt_internal_add_sbom(FancySystemLib
+ TYPE SYSTEM_LIBRARY
+)
+target_link_libraries(subproj2_helper PRIVATE FancySystemLib)
+
+_qt_internal_sbom_end_project()
+
+include(CommonResultGen)
diff --git a/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp
new file mode 100644
index 00000000000..f6b17d4b9f8
--- /dev/null
+++ b/tests/auto/cmake/RunCMake/Sbom/subprojects/subproj2/subproj2_helper.cpp
@@ -0,0 +1,4 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+int func() { return 0; };
diff --git a/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp b/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp
index fe509e558c6..72175f8e04c 100644
--- a/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp
+++ b/tests/auto/corelib/global/qcheckedint/tst_qcheckedint.cpp
@@ -347,7 +347,7 @@ void tst_QCheckedInt::division()
// This causes an internal compiler error on MSVC, so skipping it there
// Integrity's compiler says this code isn't constexpr.
-#if (!defined(Q_CC_MSVC) || Q_CC_MSVC > 1944) && !defined(Q_CC_GHS)
+#if (!defined(Q_CC_MSVC) || Q_CC_MSVC > 1950) && !defined(Q_CC_GHS)
template <typename T>
constexpr bool checkedIntTypeProperties()
diff --git a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp
index bbe355061eb..0474ad974cb 100644
--- a/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp
+++ b/tests/auto/corelib/global/qgetputenv/tst_qgetputenv.cpp
@@ -17,15 +17,26 @@ class tst_QGetPutEnv : public QObject
{
Q_OBJECT
private slots:
+ void init();
+
void getSetCheck();
void encoding();
void intValue_data();
void intValue();
+
+private:
+ QByteArray uniqueEnvVarName;
};
+void tst_QGetPutEnv::init()
+{
+ QUuid uuid = QUuid::createUuid();
+ uniqueEnvVarName = "QT_TEST_ENV_VAR_" + uuid.toByteArray(QUuid::Id128);
+}
+
void tst_QGetPutEnv::getSetCheck()
{
- const char varName[] = "should_not_exist";
+ const char *varName = uniqueEnvVarName.constData();
bool ok;
@@ -117,13 +128,14 @@ void tst_QGetPutEnv::encoding()
// The LATIN SMALL LETTER A WITH ACUTE is NFC for NFD:
// U+0061 U+0301 LATIN SMALL LETTER A + COMBINING ACUTE ACCENT
- const char varName[] = "should_not_exist";
+ const char *varName = uniqueEnvVarName.constData();
+
static const wchar_t rawvalue[] = { 'a', 0x00E1, 0x03B1, 0x0430, 0 };
QString value = QString::fromWCharArray(rawvalue);
#if defined(Q_OS_WIN)
- const wchar_t wvarName[] = L"should_not_exist";
- _wputenv_s(wvarName, rawvalue);
+ std::wstring wvarName = QString::fromUtf8(varName).toStdWString();
+ _wputenv_s(wvarName.data(), rawvalue);
#else
// confirm the locale is UTF-8
if (value.toLocal8Bit() != "a\xc3\xa1\xce\xb1\xd0\xb0")
@@ -203,7 +215,7 @@ void tst_QGetPutEnv::intValue_data()
void tst_QGetPutEnv::intValue()
{
const int maxlen = (sizeof(qint64) * CHAR_BIT + 2) / 3;
- const char varName[] = "should_not_exist";
+ const char *varName = uniqueEnvVarName.constData();
QFETCH(QByteArray, value);
QFETCH(qint64, expected);
diff --git a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp
index 0b65673c393..bbcc8c5e5e1 100644
--- a/tests/auto/corelib/global/qglobal/tst_qglobal.cpp
+++ b/tests/auto/corelib/global/qglobal/tst_qglobal.cpp
@@ -464,7 +464,7 @@ Q_COREAPP_STARTUP_FUNCTION(myStartupFunc)
void tst_QGlobal::qCoreAppStartupFunction()
{
- QCOMPARE(qStartupFunctionValue, 0);
+ qStartupFunctionValue = 0;
int argc = 1;
char *argv[] = { const_cast<char*>(QTest::currentAppName()) };
QCoreApplication app(argc, argv);
diff --git a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
index d2fee5124db..34c0fd11bed 100644
--- a/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
+++ b/tests/auto/corelib/global/qnumeric/tst_qnumeric.cpp
@@ -1011,7 +1011,7 @@ SUB_OVERFLOW_UNSIGNED_TYPE_TEST(ulong, ULONG_MAX)
#if defined(QT_HAS_128_BIT_MULTIPLICATION)
// Compiling this causes an ICE in MSVC, so skipping it
-#if !defined(Q_CC_MSVC) || Q_CC_MSVC > 1944
+#if !defined(Q_CC_MSVC) || Q_CC_MSVC > 1950
SIGNED_TYPE_TEST(qlonglong, LLONG_MIN, LLONG_MAX)
UNSIGNED_TYPE_TEST(qulonglong, ULLONG_MAX)
#endif
diff --git a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp
index e0f677e1511..7603d84623b 100644
--- a/tests/auto/corelib/io/qdebug/tst_qdebug.cpp
+++ b/tests/auto/corelib/io/qdebug/tst_qdebug.cpp
@@ -1623,6 +1623,8 @@ void tst_QDebug::threadSafety() const
#ifdef Q_OS_WASM
QSKIP("threadSafety does not run on wasm");
#else
+ s_messages = {};
+
MessageHandlerSetter mhs(threadSafeMessageHandler);
const int numThreads = 10;
QThreadPool::globalInstance()->setMaxThreadCount(numThreads);
diff --git a/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp b/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp
index 0fd62d40f62..9aa99a1b653 100644
--- a/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp
+++ b/tests/auto/corelib/io/qloggingregistry/tst_qloggingregistry.cpp
@@ -186,6 +186,12 @@ private slots:
Q_ASSERT(!qApp); // Rules should not require an app to resolve
+ static bool calledOnce = false;
+ if (calledOnce)
+ QSKIP("QLoggingRegistry_environment can only run once");
+
+ calledOnce = true;
+
qputenv("QT_LOGGING_RULES", "qt.foo.bar=true");
QLoggingCategory qtEnabledByLoggingRule("qt.foo.bar");
QCOMPARE(qtEnabledByLoggingRule.isDebugEnabled(), true);
diff --git a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp
index 96bbd60ab83..9a5e2a5f89b 100644
--- a/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp
+++ b/tests/auto/corelib/kernel/qchronotimer/tst_qchronotimer.cpp
@@ -326,7 +326,7 @@ void tst_QChronoTimer::remainingTimeDuringActivation()
if (!singleShot) {
// do it again - see QTBUG-46940
- remainingTime = std::chrono::milliseconds::min();
+ remainingTime = std::chrono::nanoseconds::min();
QVERIFY(timeoutSpy.wait());
QCOMPARE_LE(remainingTime, timeout);
QCOMPARE_GT(remainingTime, 0ns);
diff --git a/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp b/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp
index bde70e32233..10769165ec4 100644
--- a/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp
+++ b/tests/auto/corelib/kernel/qmetaproperty/tst_qmetaproperty.cpp
@@ -307,6 +307,13 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(EnumFlagsTester::TestFlags)
void tst_QMetaProperty::readAndWriteWithLazyRegistration()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("lazy registration only runs once per type per process");
+ return;
+ }
+ executedOnce = true;
+
QVERIFY(!QMetaType::fromName("CustomReadObject*").isValid());
QVERIFY(!QMetaType::fromName("CustomWriteObject*").isValid());
diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp
index 683025fd409..3cf16367777 100644
--- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp
+++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp
@@ -528,11 +528,25 @@ void tst_QMetaType::qMetaTypeId()
QCOMPARE(::qMetaTypeId<qint8>(), QMetaType::fromType<qint8>().id());
}
+class QPropObject : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QList<QVariant> prop READ prop WRITE setProp)
+
+public:
+ QPropObject() { propList << 42 << "Hello"; }
+
+ QList<QVariant> prop() const { return propList; }
+ void setProp(const QList<QVariant> &list) { propList = list; }
+ QList<QVariant> propList;
+};
+
void tst_QMetaType::properties()
{
qRegisterMetaType<QList<QVariant> >("QList<QVariant>");
+ QPropObject sut;
- QVariant v = property("prop");
+ QVariant v = sut.property("prop");
QCOMPARE(v.typeName(), "QVariantList");
@@ -542,8 +556,8 @@ void tst_QMetaType::properties()
values << 43 << "world";
- QVERIFY(setProperty("prop", values));
- v = property("prop");
+ QVERIFY(sut.setProperty("prop", values));
+ v = sut.property("prop");
QCOMPARE(v.toList().size(), 4);
}
@@ -1450,6 +1464,11 @@ void tst_QMetaType::defaultConstructTrivial_QTBUG_109594()
void tst_QMetaType::typedConstruct()
{
+ static bool calledOnce = false;
+ if (calledOnce)
+ QSKIP("tst_QMetaType::typedConstruct can only run once");
+ calledOnce = true;
+
auto testMetaObjectWriteOnGadget = [](QVariant &gadget, const QList<GadgetPropertyType> &properties)
{
auto metaObject = QMetaType(gadget.userType()).metaObject();
diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h
index 6f218bb0651..e9a177356e6 100644
--- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h
+++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.h
@@ -25,7 +25,6 @@ struct MessageHandlerCustom : public MessageHandler
class tst_QMetaType: public QObject
{
Q_OBJECT
- Q_PROPERTY(QList<QVariant> prop READ prop WRITE setProp)
public:
struct GadgetPropertyType {
@@ -34,14 +33,8 @@ public:
QVariant testData;
};
- tst_QMetaType() { propList << 42 << "Hello"; }
-
- QList<QVariant> prop() const { return propList; }
- void setProp(const QList<QVariant> &list) { propList = list; }
-
private:
void registerGadget(const char * name, const QList<GadgetPropertyType> &gadgetProperties);
- QList<QVariant> propList;
private slots:
#if QT_CONFIG(thread)
diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp
index 661c1f6072b..93f23259745 100644
--- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp
+++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype2.cpp
@@ -148,6 +148,11 @@ void registerCustomTypeConversions()
void tst_QMetaType::convertCustomType_data()
{
+ static bool calledOnce = false;
+ if (calledOnce)
+ QSKIP("convertCustomType can only run once");
+ calledOnce = true;
+
customTypeNotYetConvertible();
registerCustomTypeConversions();
@@ -363,6 +368,11 @@ void tst_QMetaType::compareCustomEqualOnlyType()
void tst_QMetaType::customDebugStream()
{
+ static bool calledOnce = false;
+ if (calledOnce)
+ QSKIP("customDebugStream can only run once");
+ calledOnce = true;
+
MessageHandlerCustom handler(::qMetaTypeId<CustomDebugStreamableType>());
QVariant v1 = QVariant::fromValue(CustomDebugStreamableType());
handler.expectedMessage = "QVariant(CustomDebugStreamableType, string-content)";
diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp
index afb0bf0169a..6d9f7d12dcb 100644
--- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp
+++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp
@@ -8736,6 +8736,9 @@ QT_END_NAMESPACE
void tst_QObject::emitToDestroyedClass()
{
using namespace EmitToDestroyedClass;
+ assertionCallCount = 0;
+ wouldHaveAssertedCount = 0;
+
std::unique_ptr ptr = std::make_unique<Derived>();
QObject::connect(ptr.get(), &Base::theSignal, ptr.get(), &Derived::doNothing);
QCOMPARE(assertionCallCount, 0);
diff --git a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp
index 81316b061d0..98c184872f9 100644
--- a/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp
+++ b/tests/auto/corelib/kernel/qtimer/tst_qtimer.cpp
@@ -1202,10 +1202,10 @@ void tst_QTimer::singleShotDestructionBeforeEventDispatcher()
// would be deleted by the QObject destructor, which is too late to
// unregister the timer.
- auto thr = QThread::create([] {
+ std::unique_ptr<QThread> thr{QThread::create([] {
QObject o;
QTimer::singleShot(300s, &o, [] {});
- });
+ })};
thr->start();
thr->wait();
}
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
index e713901101c..721d3d3af98 100644
--- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
+++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
@@ -432,6 +432,9 @@ private slots:
void get_QTransform() { get_impl(QTransform{1, 2, 3, 4, 5, 6, 7, 8, 9}); } // too large
void get_NonDefaultConstructible();
+ void reference();
+ void pointer();
+
private:
using StdVariant = std::variant<std::monostate,
// list here all the types with which we instantiate getIf_impl:
@@ -6213,6 +6216,73 @@ void tst_QVariant::get_NonDefaultConstructible()
get_impl(NonDefaultConstructible{42});
}
+struct QVariantWrapper
+{
+public:
+ static constexpr bool canNoexceptConvertToQVariant
+ = std::is_nothrow_copy_constructible_v<QVariant>;
+ static constexpr bool canNoexceptAssignQVariant
+ = std::is_nothrow_copy_assignable_v<QVariant>;
+
+ QVariantWrapper(QVariant *content = nullptr) noexcept : m_content(content) {}
+
+ QVariant content() const noexcept(canNoexceptConvertToQVariant) { return *m_content; }
+ void setContent(const QVariant &content) noexcept(canNoexceptAssignQVariant)
+ {
+ *m_content = content;
+ }
+
+private:
+ QVariant *m_content = nullptr;
+};
+
+QT_BEGIN_NAMESPACE
+template<>
+QVariant::ConstReference<QVariantWrapper>::operator QVariant() const
+ noexcept(QVariantWrapper::canNoexceptConvertToQVariant)
+{
+ return m_referred.content();
+}
+
+template<>
+QVariant::Reference<QVariantWrapper> &QVariant::Reference<QVariantWrapper>::operator=(
+ const QVariant &content) noexcept(QVariantWrapper::canNoexceptAssignQVariant)
+{
+ m_referred.setContent(content);
+ return *this;
+}
+QT_END_NAMESPACE
+
+void tst_QVariant::reference()
+{
+ QVariant content(5);
+
+ QVariant::ConstReference<QVariantWrapper> constRef(&content);
+ QCOMPARE(QVariant(constRef), QVariant(5));
+
+ QVariant::Reference<QVariantWrapper> ref(&content);
+ QCOMPARE(QVariant(ref), QVariant(5));
+
+ ref = QVariant(12);
+ QCOMPARE(QVariant(ref), QVariant(12));
+ QCOMPARE(content, QVariant(12));
+}
+
+void tst_QVariant::pointer()
+{
+ QVariant content(5);
+
+ QVariant::ConstPointer<QVariantWrapper> constPtr(&content);
+ QCOMPARE(*constPtr, QVariant(5));
+
+ QVariant::Pointer<QVariantWrapper> ptr(&content);
+ QCOMPARE(*ptr, QVariant(5));
+
+ *ptr = QVariant(12);
+ QCOMPARE(*ptr, QVariant(12));
+ QCOMPARE(content, QVariant(12));
+}
+
template <typename T>
T mutate(const T &t) { return t + t; }
template <>
diff --git a/tests/auto/corelib/plugin/CMakeLists.txt b/tests/auto/corelib/plugin/CMakeLists.txt
index 5518231ace2..82fa9d18749 100644
--- a/tests/auto/corelib/plugin/CMakeLists.txt
+++ b/tests/auto/corelib/plugin/CMakeLists.txt
@@ -7,7 +7,9 @@ endif()
add_subdirectory(quuid)
if(QT_FEATURE_library)
add_subdirectory(qpluginloader)
- add_subdirectory(qlibrary)
+ if(QT_FEATURE_private_tests)
+ add_subdirectory(qlibrary)
+ endif()
endif()
if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_library)
add_subdirectory(qplugin)
diff --git a/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt b/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt
index fc452f37f53..62dd4a06c17 100644
--- a/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt
+++ b/tests/auto/corelib/plugin/qlibrary/tst/CMakeLists.txt
@@ -13,7 +13,7 @@ qt_internal_add_test(tst_qlibrary
SOURCES
../tst_qlibrary.cpp
TESTDATA ${test_data}
- LIBRARIES mylib mylib2
+ LIBRARIES mylib mylib2 Qt::CorePrivate
)
add_dependencies(tst_qlibrary mylib mylib2)
diff --git a/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp b/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp
index 5ae49ff1707..d708f6def1c 100644
--- a/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp
+++ b/tests/auto/corelib/plugin/qlibrary/tst_qlibrary.cpp
@@ -6,6 +6,7 @@
#include <qdir.h>
#include <qlibrary.h>
#include <QtCore/QRegularExpression>
+#include <QtCore/private/qlibrary_p.h>
// Helper macros to let us know if some suffixes and prefixes are valid
@@ -162,7 +163,12 @@ void tst_QLibrary::cleanup()
};
for (const auto &entry : libs) {
- do {} while (QLibrary(entry.name, entry.version).unload());
+ bool unloaded = false;
+ do {
+ QLibrary lib(entry.name, entry.version);
+ auto libPrivate = QLibraryPrivate::get(&lib);
+ unloaded = libPrivate->unload();
+ } while (unloaded);
}
}
diff --git a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp
index 37e1f744f34..763ec71e277 100644
--- a/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp
+++ b/tests/auto/corelib/thread/qthreadonce/tst_qthreadonce.cpp
@@ -80,6 +80,14 @@ QSingleton<SingletonObject> IncrementThread::singleton;
void tst_QThreadOnce::sameThread_data()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
+
SingletonObject::runCount = 0;
QTest::addColumn<int>("expectedValue");
@@ -105,6 +113,14 @@ void tst_QThreadOnce::sameThread()
void tst_QThreadOnce::multipleThreads()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
+
#if defined(Q_OS_VXWORKS)
const int NumberOfThreads = 20;
#else
@@ -136,6 +152,13 @@ void tst_QThreadOnce::multipleThreads()
void tst_QThreadOnce::nesting()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
int variable = 0;
Q_ONCE {
Q_ONCE {
@@ -148,6 +171,14 @@ void tst_QThreadOnce::nesting()
static void reentrant(int control, int &counter)
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
+
Q_ONCE {
if (counter)
reentrant(--control, counter);
@@ -159,6 +190,13 @@ static void reentrant(int control, int &counter)
void tst_QThreadOnce::reentering()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
const int WantedRecursions = 5;
int count = 0;
SingletonObject::runCount = 0;
@@ -181,6 +219,14 @@ static void exception_helper(int &val)
#ifndef QT_NO_EXCEPTIONS
void tst_QThreadOnce::exception()
{
+ static bool executedOnce = false;
+ if (executedOnce) {
+ QSKIP("tst_QThreadOnce can only run once");
+ return;
+ }
+ executedOnce = true;
+
+
int count = 0;
try {
diff --git a/tests/auto/corelib/time/CMakeLists.txt b/tests/auto/corelib/time/CMakeLists.txt
index 1fb95e9245c..29e882429f4 100644
--- a/tests/auto/corelib/time/CMakeLists.txt
+++ b/tests/auto/corelib/time/CMakeLists.txt
@@ -8,6 +8,7 @@ if(QT_FEATURE_datetimeparser)
add_subdirectory(qdatetimeparser)
endif()
add_subdirectory(qtime)
-if(QT_FEATURE_timezone)
- add_subdirectory(qtimezone)
+add_subdirectory(qtimezone)
+if(QT_FEATURE_timezone AND QT_FEATURE_developer_build)
+ add_subdirectory(qtimezonebackend)
endif()
diff --git a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
index fde06d2edd9..1e9aeeef799 100644
--- a/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
+++ b/tests/auto/corelib/time/qdatetime/tst_qdatetime.cpp
@@ -1262,8 +1262,7 @@ void tst_QDateTime::toString_strformat()
QCOMPARE(testDateTime.toString(u"yyyy-MM-dd hh:mm:ss tt"), u"2013-01-01 01:02:03 +0000"_s);
QCOMPARE(testDateTime.toString(u"yyyy-MM-dd hh:mm:ss ttt"), u"2013-01-01 01:02:03 +00:00"_s);
-#if QT_CONFIG(icu) && !defined(Q_STL_DINKUMWARE)
- // The Dinkum (VxWorks) exception may just be because it has an old ICU.
+#if QT_CONFIG(icu)
// Hopefully other timezone backends shall eventually agree with this:
const QString longForm = u"2013-01-01 01:02:03 Coordinated Universal Time"_s;
#else
diff --git a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
index 66fbac97977..d1d86a4802f 100644
--- a/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
+++ b/tests/auto/corelib/time/qtimezone/tst_qtimezone.cpp
@@ -3,7 +3,9 @@
#include <QTest>
#include <qtimezone.h>
-#include <private/qtimezoneprivate_p.h>
+#if QT_CONFIG(timezone)
+# include <private/qtimezoneprivate_p.h>
+#endif
#include <private/qcomparisontesthelper_p.h>
#include <qlocale.h>
@@ -51,33 +53,21 @@ private Q_SLOTS:
void checkOffset();
void stressTest();
void windowsId();
- void isValidId_data();
- void isValidId();
void serialize();
void malformed();
- // Backend tests
+ // Backend tests (see also ../qtimezonebackend/)
void utcTest();
- void icuTest();
- void tzTest();
- void macTest();
void darwinTypes();
- void winTest();
void localeSpecificDisplayName_data();
void localeSpecificDisplayName();
- void roundtripDisplayNames_data();
- void roundtripDisplayNames();
+ // Compatibility with std::chrono::tzdb:
void stdCompatibility_data();
void stdCompatibility();
-#endif // timezone backends
+#endif // timezone backends exist
private:
- void printTimeZone(const QTimeZone &tz);
#if QT_CONFIG(timezone)
-# if defined(QT_BUILD_INTERNAL)
- // Generic tests of privates, called by implementation-specific private tests:
- void testCetPrivate(const QTimeZonePrivate &tzp);
- void testEpochTranPrivate(const QTimeZonePrivate &tzp);
-# endif // QT_BUILD_INTERNAL
+ void printTimeZone(const QTimeZone &tz);
// Where tzdb contains a link between zones in different territories, CLDR
// doesn't treat those as aliases for one another. For details see "Links in
// the tz database" at:
@@ -102,6 +92,7 @@ private:
static constexpr bool debug = false;
};
+#if QT_CONFIG(timezone)
void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
{
QDateTime now = QDateTime::currentDateTime();
@@ -167,9 +158,11 @@ void tst_QTimeZone::printTimeZone(const QTimeZone &tz)
qDebug() << "Transition before 1 Jun = " << tz.previousTransition(jun).atUtc;
qDebug() << "";
}
+#endif // feature timezone
void tst_QTimeZone::createTest()
{
+#if QT_CONFIG(timezone)
const QTimeZone tz("Pacific/Auckland");
if constexpr (debug)
@@ -265,13 +258,16 @@ void tst_QTimeZone::createTest()
QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
}
}
+#else
+ QSKIP("Test depends on backends, enabled by feature timezone");
+#endif // feature timezone
}
void tst_QTimeZone::nullTest()
{
QTimeZone nullTz1;
QTimeZone nullTz2;
- QTimeZone utc("UTC");
+ QTimeZone utc(QTimeZone::UTC);
// Validity tests
QCOMPARE(nullTz1.isValid(), false);
@@ -290,6 +286,7 @@ void tst_QTimeZone::nullTest()
utc = nullTz1;
QCOMPARE(utc.isValid(), false);
+#if QT_CONFIG(timezone)
QCOMPARE(nullTz1.id(), QByteArray());
QCOMPARE(nullTz1.territory(), QLocale::AnyTerritory);
QCOMPARE(nullTz1.comment(), QString());
@@ -335,6 +332,7 @@ void tst_QTimeZone::nullTest()
QCOMPARE(data.offsetFromUtc, invalidOffset);
QCOMPARE(data.standardTimeOffset, invalidOffset);
QCOMPARE(data.daylightTimeOffset, invalidOffset);
+#endif // feature timezone
}
void tst_QTimeZone::assign()
@@ -487,7 +485,7 @@ void tst_QTimeZone::offset()
void tst_QTimeZone::dataStreamTest()
{
-#ifndef QT_NO_DATASTREAM
+#if QT_CONFIG(timezone) && !defined(QT_NO_DATASTREAM)
// Test the OffsetFromUtc backend serialization. First with a custom timezone:
QTimeZone tz1("QST"_ba, 23456,
u"Qt Standard Time"_s, u"QST"_s, QLocale::Norway, u"Qt Testing"_s);
@@ -546,7 +544,7 @@ void tst_QTimeZone::dataStreamTest()
QCOMPARE(ds.status(), QDataStream::Ok);
}
QCOMPARE(tz2.id(), tz1.id());
-#endif
+#endif // feature timezone and enabled datastream
}
#if QT_CONFIG(timezone)
@@ -791,7 +789,7 @@ void tst_QTimeZone::hasAlternativeName()
void tst_QTimeZone::specificTransition_data()
{
-#if QT_CONFIG(timezone) && QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__)
+#if QT_CONFIG(timezone_tzdb) && defined(__GLIBCXX__)
QSKIP("libstdc++'s C++20 misreads the IANA DB for Moscow's transitions (among others).");
#endif
#if defined Q_OS_ANDROID && !QT_CONFIG(timezone_tzdb)
@@ -1159,120 +1157,6 @@ void tst_QTimeZone::windowsId()
}
}
-void tst_QTimeZone::isValidId_data()
-{
-#ifdef QT_BUILD_INTERNAL
- QTest::addColumn<QByteArray>("input");
- QTest::addColumn<bool>("valid");
-
- // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
- // Can't start with '-'
- // Parts separated by '/', each part min 1 and max of 14 chars
- // (Android has parts with lengths up to 17, so tolerates this as a special case.)
-#define TESTSET(name, section, valid) \
- QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \
- QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \
- QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid
-
- // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
- // Can't start with '-'
- // Parts separated by '/', each part min 1 and max of 14 chars
- TESTSET("empty", "", false);
- TESTSET("minimal", "m", true);
-#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
- TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this
- TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this.
-#else
- TESTSET("maximal", "12345678901234", true);
- TESTSET("maximal twice", "12345678901234/12345678901234", true);
- TESTSET("too long", "123456789012345", false);
- TESTSET("too-long/maximal", "123456789012345/12345678901234", false);
- TESTSET("maximal/too-long", "12345678901234/123456789012345", false);
-#endif
-
- TESTSET("bad hyphen", "-hyphen", false);
- TESTSET("good hyphen", "hy-phen", true);
-
- TESTSET("valid char _", "_", true);
- TESTSET("valid char .", ".", true);
- TESTSET("valid char :", ":", true);
- TESTSET("valid char +", "+", true);
- TESTSET("valid char A", "A", true);
- TESTSET("valid char Z", "Z", true);
- TESTSET("valid char a", "a", true);
- TESTSET("valid char z", "z", true);
- TESTSET("valid char 0", "0", true);
- TESTSET("valid char 9", "9", true);
-
- TESTSET("valid pair az", "az", true);
- TESTSET("valid pair AZ", "AZ", true);
- TESTSET("valid pair 09", "09", true);
- TESTSET("valid pair .z", ".z", true);
- TESTSET("valid pair _z", "_z", true);
- TESTSET("invalid pair -z", "-z", false);
-
- TESTSET("valid triple a/z", "a/z", true);
- TESTSET("valid triple a.z", "a.z", true);
- TESTSET("valid triple a-z", "a-z", true);
- TESTSET("valid triple a_z", "a_z", true);
- TESTSET("invalid triple a z", "a z", false);
- TESTSET("invalid triple a\\z", "a\\z", false);
- TESTSET("invalid triple a,z", "a,z", false);
-
- TESTSET("invalid space", " ", false);
- TESTSET("invalid char ^", "^", false);
- TESTSET("invalid char \"", "\"", false);
- TESTSET("invalid char $", "$", false);
- TESTSET("invalid char %", "%", false);
- TESTSET("invalid char &", "&", false);
- TESTSET("invalid char (", "(", false);
- TESTSET("invalid char )", ")", false);
- TESTSET("invalid char =", "=", false);
- TESTSET("invalid char -", "-", false);
- TESTSET("invalid char ?", "?", false);
- TESTSET("invalid char ß", "ß", false);
- TESTSET("invalid char \\x01", "\x01", false);
- TESTSET("invalid char ' '", " ", false);
-
-#undef TESTSET
-
- QTest::newRow("az alone") << QByteArray("az") << true;
- QTest::newRow("AZ alone") << QByteArray("AZ") << true;
- QTest::newRow("09 alone") << QByteArray("09") << true;
- QTest::newRow("a/z alone") << QByteArray("a/z") << true;
- QTest::newRow("a.z alone") << QByteArray("a.z") << true;
- QTest::newRow("a-z alone") << QByteArray("a-z") << true;
- QTest::newRow("a_z alone") << QByteArray("a_z") << true;
- QTest::newRow(".z alone") << QByteArray(".z") << true;
- QTest::newRow("_z alone") << QByteArray("_z") << true;
- QTest::newRow("a z alone") << QByteArray("a z") << false;
- QTest::newRow("a\\z alone") << QByteArray("a\\z") << false;
- QTest::newRow("a,z alone") << QByteArray("a,z") << false;
- QTest::newRow("/z alone") << QByteArray("/z") << false;
- QTest::newRow("-z alone") << QByteArray("-z") << false;
-#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
- QTest::newRow("long alone") << QByteArray("12345678901234567") << true;
- QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false;
-#else
- QTest::newRow("long alone") << QByteArray("12345678901234") << true;
- QTest::newRow("over-long alone") << QByteArray("123456789012345") << false;
-#endif
-
-#else
- QSKIP("This test requires a Qt -developer-build.");
-#endif // QT_BUILD_INTERNAL
-}
-
-void tst_QTimeZone::isValidId()
-{
-#ifdef QT_BUILD_INTERNAL
- QFETCH(QByteArray, input);
- QFETCH(bool, valid);
-
- QCOMPARE(QTimeZonePrivate::isValidId(input), valid);
-#endif
-}
-
void tst_QTimeZone::serialize()
{
int parts = 0;
@@ -1365,7 +1249,7 @@ void tst_QTimeZone::utcTest()
tz = QTimeZone(15 * 3600); // no IANA ID, so uses minimal id, skipping :00 minutes
QVERIFY(tz.isValid());
- QCOMPARE(tz.id(), "UTC+15");
+ QCOMPARE(tz.id(), "UTC+15:00");
QCOMPARE(tz.offsetFromUtc(now), 15 * 3600);
QCOMPARE(tz.standardTimeOffset(now), 15 * 3600);
QCOMPARE(tz.daylightTimeOffset(now), 0);
@@ -1402,314 +1286,6 @@ void tst_QTimeZone::utcTest()
QCOMPARE(tz.daylightTimeOffset(now), 0);
}
-// Relies on local variable names: zone tzp and locale enUS.
-#define ZONE_DNAME_CHECK(type, name, val) \
- QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val);
-
-void tst_QTimeZone::icuTest()
-{
-#if defined(QT_BUILD_INTERNAL) && QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && !defined(Q_OS_UNIX)
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QIcuTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid is not available:
- QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon"));
- // and construction gives an invalid result:
- QIcuTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QIcuTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by ICU version installed
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS("en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto"));
-#endif // ICU not on Unix, without tzdb
-}
-
-void tst_QTimeZone::tzTest()
-{
-#if defined QT_BUILD_INTERNAL && defined Q_OS_UNIX \
- && !QT_CONFIG(timezone_tzdb) && !defined Q_OS_DARWIN && !defined Q_OS_ANDROID
- const auto UTC = QTimeZone::UTC;
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QTzTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QTzTimeZonePrivate tzpi("Gondwana/Erewhon");
- QVERIFY(!tzpi.isValid());
-
- // Test named constructor
- QTzTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Test POSIX-format value for $TZ:
- QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00");
- QVERIFY(tzposix.isValid());
- QVERIFY(tzposix.hasDaylightTime());
-
- // Cope with stray space at start of value (QTBUG-135109):
- QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3");
- QVERIFY(syd.isValid());
- QVERIFY(syd.hasDaylightTime());
-
- // RHEL has been seen with this as Africa/Casablanca's POSIX rule:
- QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25");
- const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst
- QVERIFY(permaDst.isValid());
- QVERIFY(permaDst.hasDaylightTime());
- QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch()));
- QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch()));
- // Note that the final /25 could be misunderstood as putting a fall-back at
- // 1am on the next year's Jan 1st; check we don't do that:
- QVERIFY(permaDst.isDaylightTime(
- QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch()));
- // It shouldn't have any transitions. QTimeZone::hasTransitions() only says
- // whether the backend supports them, so ask for transitions in a wide
- // enough interval that one would show up, if there are any:
- QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(),
- QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch()
- ).isEmpty());
-
- QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule
- QVERIFY(tzBrazil.isValid());
- QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800);
-
- // Test display names by type, either ICU or abbreviation only
- QLocale enUS(u"en_US");
- // Only test names in debug mode, names used can vary by ICU version installed
- if constexpr (debug) {
-#if QT_CONFIG(icu)
- ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-#else
- ZONE_DNAME_CHECK(StandardTime, LongName, "CET");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "CET");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST");
- ZONE_DNAME_CHECK(GenericTime, LongName, "CET");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "CET");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET");
-#endif // icu
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto"));
- if (QTest::currentTestFailed())
- return;
-
- // Test first and last transition rule
- // Warning: This could vary depending on age of TZ file!
-
- // Test low date uses first rule found
- constexpr qint64 ancient = -Q_INT64_C(9999999999999);
- // Note: Depending on the OS in question, the database may be carrying the
- // Local Mean Time. which for Berlin is 0:53:28
- QTimeZonePrivate::Data dat = tzp.data(ancient);
- QCOMPARE(dat.atMSecsSinceEpoch, ancient);
- QCOMPARE(dat.daylightTimeOffset, 0);
- if (dat.abbreviation == u"LMT") {
- QCOMPARE(dat.standardTimeOffset, 3208);
- } else {
- QCOMPARE(dat.standardTimeOffset, 3600);
-
- constexpr qint64 invalidTime = std::numeric_limits<qint64>::min();
- constexpr int invalidOffset = std::numeric_limits<int>::min();
- // Test previous to low value is invalid
- dat = tzp.previousTransition(ancient);
- QCOMPARE(dat.atMSecsSinceEpoch, invalidTime);
- QCOMPARE(dat.standardTimeOffset, invalidOffset);
- QCOMPARE(dat.daylightTimeOffset, invalidOffset);
- }
-
- dat = tzp.nextTransition(ancient);
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
- QTimeZone::fromSecondsAheadOfUtc(3600)),
- QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32),
- QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- // Date-times late enough to exercise POSIX rules:
- qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch();
- qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch();
- // Relevant last Sundays in October and March:
- QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday);
- QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday);
- QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday);
-
- dat = tzp.data(stdHi);
- QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- dat = tzp.data(dstHi);
- QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.previousTransition(stdHi);
- QCOMPARE(dat.abbreviation, u"CET");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- dat = tzp.previousTransition(dstHi);
- QCOMPARE(dat.abbreviation, u"CEST");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.nextTransition(stdHi);
- QCOMPARE(dat.abbreviation, u"CEST");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
-
- dat = tzp.nextTransition(dstHi);
- QCOMPARE(dat.abbreviation, u"CET");
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
- QTimeZone::fromSecondsAheadOfUtc(3600)),
- QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
-
- // Test TZ timezone vs UTC timezone for non-whole-hour negative offset:
- QTzTimeZonePrivate tztz1("America/Caracas");
- QUtcTimeZonePrivate tzutc1("UTC-04:30");
- QVERIFY(tztz1.isValid());
- QVERIFY(tzutc1.isValid());
- QTzTimeZonePrivate::Data datatz1 = tztz1.data(std);
- QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std);
- QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc);
-
- // Test TZ timezone vs UTC timezone for non-whole-hour positive offset:
- QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name
- QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name
- // Can't assign QtzTZP, so use a reference; prefer new name.
- QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c;
- QUtcTimeZonePrivate tzutc2("UTC+05:30");
- QVERIFY2(tztz2.isValid(), tztz2.id().constData());
- QVERIFY(tzutc2.isValid());
- QTzTimeZonePrivate::Data datatz2 = tztz2.data(std);
- QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std);
- QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc);
-
- // Test a timezone with an abbreviation that isn't all letters:
- QTzTimeZonePrivate tzBarnaul("Asia/Barnaul");
- if (tzBarnaul.isValid()) {
- QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07");
-
- // first full day of the new rule (tzdata2016b)
- QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC);
- QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07");
- }
-#endif // QT_BUILD_INTERNAL && Q_OS_UNIX && !timezone_tzdb && !Q_OS_DARWIN && !Q_OS_ANDROID
-}
-
-void tst_QTimeZone::macTest()
-{
-#if defined(QT_BUILD_INTERNAL) && defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb)
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QMacTimeZonePrivate tzpd;
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QMacTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QMacTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by version
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS(u"en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- // ICU C api does not support Generic Time yet, C++ api does
- ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time");
- ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto"));
-#endif // QT_BUILD_INTERNAL && Q_OS_DARWIN without tzdb
-}
-
void tst_QTimeZone::darwinTypes()
{
#ifndef Q_OS_DARWIN
@@ -1720,59 +1296,6 @@ void tst_QTimeZone::darwinTypes()
#endif
}
-void tst_QTimeZone::winTest()
-{
-#if defined(QT_BUILD_INTERNAL) && defined(USING_WIN_TZ)
- // Known datetimes
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
-
- // Test default constructor
- QWinTimeZonePrivate tzpd;
- if constexpr (debug)
- qDebug() << "System ID = " << tzpd.id()
- << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale())
- << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale());
- QVERIFY(tzpd.isValid());
-
- // Test invalid constructor
- QWinTimeZonePrivate tzpi("Gondwana/Erewhon");
- QCOMPARE(tzpi.isValid(), false);
-
- // Test named constructor
- QWinTimeZonePrivate tzp("Europe/Berlin");
- QVERIFY(tzp.isValid());
-
- // Only test names in debug mode, names used can vary by version
- if constexpr (debug) {
- // Test display names by type
- QLocale enUS(u"en_US");
- ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time");
- ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time");
- ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
- ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time");
- ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time");
- ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
- ZONE_DNAME_CHECK(GenericTime, LongName,
- "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
- ZONE_DNAME_CHECK(GenericTime, ShortName,
- "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
- ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
-
- // Test Abbreviations
- QCOMPARE(tzp.abbreviation(std), u"CET");
- QCOMPARE(tzp.abbreviation(dst), u"CEST");
- }
-
- testCetPrivate(tzp);
- if (QTest::currentTestFailed())
- return;
- testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto"));
-#endif // QT_BUILD_INTERNAL && USING_WIN_TZ
-}
-
-#undef ZONE_DNAME_CHECK
-
void tst_QTimeZone::localeSpecificDisplayName_data()
{
QTest::addColumn<QByteArray>("zoneName");
@@ -1849,285 +1372,6 @@ void tst_QTimeZone::localeSpecificDisplayName()
#endif
}
-void tst_QTimeZone::roundtripDisplayNames_data()
-{
-#ifdef QT_BUILD_INTERNAL
- QTest::addColumn<QTimeZone>("zone");
- QTest::addColumn<QLocale>("locale");
- QTest::addColumn<QTimeZone::TimeType>("type");
-
- constexpr QTimeZone::TimeType types[] = {
- QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
- };
- const auto typeName = [](QTimeZone::TimeType type) {
- switch (type) {
- case QTimeZone::GenericTime: return "Gen";
- case QTimeZone::StandardTime: return "Std";
- case QTimeZone::DaylightTime: return "DST";
- }
- Q_UNREACHABLE_RETURN("Unrecognised");
- };
- const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba);
-#ifdef EXHAUSTIVE_ZONE_DISPLAY
- const QList<QByteArray> idList = allList;
-#else
- const QList<QByteArray> idList = {
- "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba,
- "America/Caracas"_ba, "America/Indiana/Tell_City"_ba, "America/Managua"_ba,
- "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba,
- "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba,
- "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba,
- "Brazil/Acre"_ba, "CST6CDT"_ba, "Canada/Atlantic"_ba,
- "Chile/EasterIsland"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba,
- "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba,
- "Europe/Prague"_ba, "Europe/Vatican"_ba,
- "Indian/Comoro"_ba, "Mexico/BajaSur"_ba,
- "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba,
- "US/Aleutian"_ba,
- "UTC"_ba,
- // Those named overtly in tst_QDateTime - special cases first:
- "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba,
- "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba,
- // ... then ordinary names in alphabetic order:
- "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba,
- "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba,
- "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba,
- "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba,
- "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba,
- "Europe/Rome"_ba,
- "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba,
- "Vulcan/ShiKahr"_ba // Invalid: also worth testing.
- };
- // Some valid zones in that list may be absent from the platform's
- // availableTimeZoneIds(), yet in fact work when used as it's asked to
- // instantiate them (e.g. Etc/Universal on macOS). This can give them a
- // displayName() that we fail to decode, without timezone_locale, due to
- // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we
- // have to filter on membership of allList when creating rows.
-#endif // Exhaustive
- const QLocale fr(QLocale::French, QLocale::France);
- const QLocale hi(QLocale::Hindi, QLocale::India);
- for (const QByteArray &id : idList) {
- if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id))
- continue;
- QTimeZone zone = QTimeZone(id);
- if (!zone.isValid())
- continue;
- for (const auto type : types) {
- QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type))
- << zone << fr << type;
- QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type))
- << zone << hi << type;
- }
- }
-#else
- QSKIP("Test needs access to internal APIs");
-#endif
-}
-
-void tst_QTimeZone::roundtripDisplayNames()
-{
-#ifdef QT_BUILD_INTERNAL
- QFETCH(const QTimeZone, zone);
- QFETCH(const QLocale, locale);
- QFETCH(const QTimeZone::TimeType, type);
- static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC);
- static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC);
- const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan;
-
- // Some zones exercise region format.
- const QString name = zone.displayName(type, QTimeZone::LongName, locale);
- if (!name.isEmpty()) {
- const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type);
- const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs()
- ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch;
- const QString extended = name + "some spurious cruft"_L1;
- auto match =
- QTimeZonePrivate::findLongNamePrefix(extended, locale, when);
- if (!match)
- match = QTimeZonePrivate::findLongNamePrefix(extended, locale);
- if (!match)
- match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale);
- if (!match)
- match = QTimeZonePrivate::findLongUtcPrefix(extended);
- auto report = qScopeGuard([=]() {
- qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
- << "via" << name;
- });
- QCOMPARE(match.nameLength, name.size());
- report.dismiss();
-#if 0
- if (match.ianaId != zone.id()) {
- const QTimeZone found = QTimeZone(match.ianaId);
- if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when)
- != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) {
- // For DST, some zones haven't done it in ages, so tran may be ancient.
- // Meanwhile, match.ianaId is typically the canonical zone for a metazone.
- // That, in turn, may not have been doing DST when zone was.
- // So we can't rely on a match, but can report the mismatches.
- qDebug() << "Long name" << name << "on"
- << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)
- << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
- << "got" << match.ianaId << "on"
- << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when);
- // There are also some absurdly over-generic names, that lead to
- // ambiguities, e.g. "heure : West"
- }
- }
-#endif // Debug code
- } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */
- qDebug("Empty display name");
- }
-#else
- Q_ASSERT(!"Should be skipped when building data table");
-#endif
-}
-
-#ifdef QT_BUILD_INTERNAL
-// Test each private produces the same basic results for CET
-void tst_QTimeZone::testCetPrivate(const QTimeZonePrivate &tzp)
-{
- // Known datetimes
- const auto UTC = QTimeZone::UTC;
- const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600);
- qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
- qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
-
- QCOMPARE(tzp.offsetFromUtc(std), 3600);
- QCOMPARE(tzp.offsetFromUtc(dst), 7200);
-
- QCOMPARE(tzp.standardTimeOffset(std), 3600);
- QCOMPARE(tzp.standardTimeOffset(dst), 3600);
-
- QCOMPARE(tzp.daylightTimeOffset(std), 0);
- QCOMPARE(tzp.daylightTimeOffset(dst), 3600);
-
- QCOMPARE(tzp.hasDaylightTime(), true);
- QCOMPARE(tzp.isDaylightTime(std), false);
- QCOMPARE(tzp.isDaylightTime(dst), true);
-
- QTimeZonePrivate::Data dat = tzp.data(std);
- QCOMPARE(dat.atMSecsSinceEpoch, std);
- QCOMPARE(dat.offsetFromUtc, 3600);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 0);
- QCOMPARE(dat.abbreviation, tzp.abbreviation(std));
-
- dat = tzp.data(dst);
- QCOMPARE(dat.atMSecsSinceEpoch, dst);
- QCOMPARE(dat.offsetFromUtc, 7200);
- QCOMPARE(dat.standardTimeOffset, 3600);
- QCOMPARE(dat.daylightTimeOffset, 3600);
- QCOMPARE(dat.abbreviation, tzp.abbreviation(dst));
-
- // Only test transitions if host system supports them
- if (tzp.hasTransitions()) {
- QTimeZonePrivate::Data tran = tzp.nextTransition(std);
- // 2012-03-25 02:00 CET, +1 -> +2
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
- QCOMPARE(tran.offsetFromUtc, 7200);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- tran = tzp.nextTransition(dst);
- // 2012-10-28 03:00 CEST, +2 -> +1
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 10, 28), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
- QCOMPARE(tran.offsetFromUtc, 3600);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
-
- tran = tzp.previousTransition(std);
- // 2011-10-30 03:00 CEST, +2 -> +1
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2011, 10, 30), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
- QCOMPARE(tran.offsetFromUtc, 3600);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
-
- tran = tzp.previousTransition(dst);
- // 2012-03-25 02:00 CET, +1 -> +2 (again)
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
- QCOMPARE(tran.offsetFromUtc, 7200);
- QCOMPARE(tran.standardTimeOffset, 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- QTimeZonePrivate::DataList expected;
- // 2011-03-27 02:00 CET, +1 -> +2
- tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0),
- eastOneHour).toMSecsSinceEpoch();
- tran.offsetFromUtc = 7200;
- tran.standardTimeOffset = 3600;
- tran.daylightTimeOffset = 3600;
- expected << tran;
- // 2011-10-30 03:00 CEST, +2 -> +1
- tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0),
- QTimeZone::fromSecondsAheadOfUtc(2 * 3600)
- ).toMSecsSinceEpoch();
- tran.offsetFromUtc = 3600;
- tran.standardTimeOffset = 3600;
- tran.daylightTimeOffset = 0;
- expected << tran;
- QTimeZonePrivate::DataList result = tzp.transitions(prev, std);
- QCOMPARE(result.size(), expected.size());
- for (int i = 0; i < expected.size(); ++i) {
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour),
- QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour));
- QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
- QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
- QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
- }
- }
-}
-
-// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT)
-void tst_QTimeZone::testEpochTranPrivate(const QTimeZonePrivate &tzp)
-{
- if (!tzp.hasTransitions())
- return; // test only viable for transitions
-
- const auto UTC = QTimeZone::UTC;
- const auto hour = std::chrono::hours{1};
- QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch
- // 1970-04-26 02:00 EST, -5 -> -4
- const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0),
- QTimeZone::fromDurationAheadOfUtc(-5 * hour));
- const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC);
-#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th.
- QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time());
-#else
- QCOMPARE(found, after);
-#endif
- QCOMPARE(tran.offsetFromUtc, -4 * 3600);
- QCOMPARE(tran.standardTimeOffset, -5 * 3600);
- QCOMPARE(tran.daylightTimeOffset, 3600);
-
- // Pre-epoch time-zones might not be supported at all:
- tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch());
- if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()
- // Toronto *did* have a transition before 1970 (DST since 1918):
- && tran.atMSecsSinceEpoch < 0) {
- // ... but, if they are, we should be able to search back to them:
- tran = tzp.previousTransition(0); // i.e. last before epoch
- // 1969-10-26 02:00 EDT, -4 -> -5
- QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
- QDateTime(QDate(1969, 10, 26), QTime(2, 0),
- QTimeZone::fromDurationAheadOfUtc(-4 * hour)));
- QCOMPARE(tran.offsetFromUtc, -5 * 3600);
- QCOMPARE(tran.standardTimeOffset, -5 * 3600);
- QCOMPARE(tran.daylightTimeOffset, 0);
- } else {
- // Do not use QSKIP(): that would discard the rest of this sub-test's caller.
- qDebug() << "No support for pre-epoch time-zone transitions";
- }
-}
-#endif // QT_BUILD_INTERNAL
-
#if __cpp_lib_chrono >= 201907L
Q_DECLARE_METATYPE(const std::chrono::time_zone *);
#endif
diff --git a/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt
new file mode 100644
index 00000000000..6bead8b6549
--- /dev/null
+++ b/tests/auto/corelib/time/qtimezonebackend/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qtimezonebackend Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qtimezonebackend LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qtimezonebackend
+ SOURCES
+ tst_qtimezonebackend.cpp
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_FOREACH
+ QT_NO_KEYWORDS
+ LIBRARIES
+ Qt::CorePrivate
+)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qtimezonebackend CONDITION QT_FEATURE_icu
+ LIBRARIES
+ ICU::i18n ICU::uc ICU::data
+)
diff --git a/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp
new file mode 100644
index 00000000000..71519ccd9fc
--- /dev/null
+++ b/tests/auto/corelib/time/qtimezonebackend/tst_qtimezonebackend.cpp
@@ -0,0 +1,784 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
+#include <qtimezone.h>
+#include <private/qtimezoneprivate_p.h>
+
+#include <qlocale.h>
+#include <qscopeguard.h>
+
+#if defined(Q_OS_WIN) && !QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb)
+# define USING_WIN_TZ
+#endif
+
+// Enable to test exhaustively - this is slow and expensive.
+// It is also quite likely to trip over problems that we can't necessarily fix.
+// #define EXHAUSTIVE_ZONE_DISPLAY
+
+using namespace Qt::StringLiterals;
+
+class tst_QTimeZoneBackend : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ // Tests of QTZP functionality:
+ void isValidId_data();
+ void isValidId();
+ void roundtripDisplayNames_data();
+ void roundtripDisplayNames();
+ // Tests of specific backends (see also ../qtimezone/):
+ void icuTest();
+ void tzTest();
+ void macTest();
+ void winTest();
+
+private:
+ // Generic tests of privates, called by implementation-specific private tests:
+ void testCetPrivate(const QTimeZonePrivate &tzp);
+ void testEpochTranPrivate(const QTimeZonePrivate &tzp);
+ // Set to true to print debug output, test Display Names and run long stress tests
+ static constexpr bool debug = false;
+};
+
+void tst_QTimeZoneBackend::isValidId_data()
+{
+ QTest::addColumn<QByteArray>("input");
+ QTest::addColumn<bool>("valid");
+
+ // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
+ // Can't start with '-'
+ // Parts separated by '/', each part min 1 and max of 14 chars
+ // (Android has parts with lengths up to 17, so tolerates this as a special case.)
+#define TESTSET(name, section, valid) \
+ QTest::newRow(name " front") << QByteArray(section "/xyz/xyz") << valid; \
+ QTest::newRow(name " middle") << QByteArray("xyz/" section "/xyz") << valid; \
+ QTest::newRow(name " back") << QByteArray("xyz/xyz/" section) << valid
+
+ // a-z, A-Z, 0-9, '.', '-', '_' are valid chars
+ // Can't start with '-'
+ // Parts separated by '/', each part min 1 and max of 14 chars
+ TESTSET("empty", "", false);
+ TESTSET("minimal", "m", true);
+#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
+ TESTSET("maximal", "East-Saskatchewan", true); // Android actually uses this
+ TESTSET("too long", "North-Saskatchewan", false); // ... but thankfully not this.
+#else
+ TESTSET("maximal", "12345678901234", true);
+ TESTSET("maximal twice", "12345678901234/12345678901234", true);
+ TESTSET("too long", "123456789012345", false);
+ TESTSET("too-long/maximal", "123456789012345/12345678901234", false);
+ TESTSET("maximal/too-long", "12345678901234/123456789012345", false);
+#endif
+
+ TESTSET("bad hyphen", "-hyphen", false);
+ TESTSET("good hyphen", "hy-phen", true);
+
+ TESTSET("valid char _", "_", true);
+ TESTSET("valid char .", ".", true);
+ TESTSET("valid char :", ":", true);
+ TESTSET("valid char +", "+", true);
+ TESTSET("valid char A", "A", true);
+ TESTSET("valid char Z", "Z", true);
+ TESTSET("valid char a", "a", true);
+ TESTSET("valid char z", "z", true);
+ TESTSET("valid char 0", "0", true);
+ TESTSET("valid char 9", "9", true);
+
+ TESTSET("valid pair az", "az", true);
+ TESTSET("valid pair AZ", "AZ", true);
+ TESTSET("valid pair 09", "09", true);
+ TESTSET("valid pair .z", ".z", true);
+ TESTSET("valid pair _z", "_z", true);
+ TESTSET("invalid pair -z", "-z", false);
+
+ TESTSET("valid triple a/z", "a/z", true);
+ TESTSET("valid triple a.z", "a.z", true);
+ TESTSET("valid triple a-z", "a-z", true);
+ TESTSET("valid triple a_z", "a_z", true);
+ TESTSET("invalid triple a z", "a z", false);
+ TESTSET("invalid triple a\\z", "a\\z", false);
+ TESTSET("invalid triple a,z", "a,z", false);
+
+ TESTSET("invalid space", " ", false);
+ TESTSET("invalid char ^", "^", false);
+ TESTSET("invalid char \"", "\"", false);
+ TESTSET("invalid char $", "$", false);
+ TESTSET("invalid char %", "%", false);
+ TESTSET("invalid char &", "&", false);
+ TESTSET("invalid char (", "(", false);
+ TESTSET("invalid char )", ")", false);
+ TESTSET("invalid char =", "=", false);
+ TESTSET("invalid char -", "-", false);
+ TESTSET("invalid char ?", "?", false);
+ TESTSET("invalid char ß", "ß", false);
+ TESTSET("invalid char \\x01", "\x01", false);
+ TESTSET("invalid char ' '", " ", false);
+
+#undef TESTSET
+
+ QTest::newRow("az alone") << QByteArray("az") << true;
+ QTest::newRow("AZ alone") << QByteArray("AZ") << true;
+ QTest::newRow("09 alone") << QByteArray("09") << true;
+ QTest::newRow("a/z alone") << QByteArray("a/z") << true;
+ QTest::newRow("a.z alone") << QByteArray("a.z") << true;
+ QTest::newRow("a-z alone") << QByteArray("a-z") << true;
+ QTest::newRow("a_z alone") << QByteArray("a_z") << true;
+ QTest::newRow(".z alone") << QByteArray(".z") << true;
+ QTest::newRow("_z alone") << QByteArray("_z") << true;
+ QTest::newRow("a z alone") << QByteArray("a z") << false;
+ QTest::newRow("a\\z alone") << QByteArray("a\\z") << false;
+ QTest::newRow("a,z alone") << QByteArray("a,z") << false;
+ QTest::newRow("/z alone") << QByteArray("/z") << false;
+ QTest::newRow("-z alone") << QByteArray("-z") << false;
+#if (defined(Q_OS_ANDROID) || QT_CONFIG(icu)) && !QT_CONFIG(timezone_tzdb)
+ QTest::newRow("long alone") << QByteArray("12345678901234567") << true;
+ QTest::newRow("over-long alone") << QByteArray("123456789012345678") << false;
+#else
+ QTest::newRow("long alone") << QByteArray("12345678901234") << true;
+ QTest::newRow("over-long alone") << QByteArray("123456789012345") << false;
+#endif
+}
+
+void tst_QTimeZoneBackend::isValidId()
+{
+ QFETCH(QByteArray, input);
+ QFETCH(bool, valid);
+
+ QCOMPARE(QTimeZonePrivate::isValidId(input), valid);
+}
+
+void tst_QTimeZoneBackend::roundtripDisplayNames_data()
+{
+ QTest::addColumn<QTimeZone>("zone");
+ QTest::addColumn<QLocale>("locale");
+ QTest::addColumn<QTimeZone::TimeType>("type");
+
+ constexpr QTimeZone::TimeType types[] = {
+ QTimeZone::GenericTime, QTimeZone::StandardTime, QTimeZone::DaylightTime
+ };
+ const auto typeName = [](QTimeZone::TimeType type) {
+ switch (type) {
+ case QTimeZone::GenericTime: return "Gen";
+ case QTimeZone::StandardTime: return "Std";
+ case QTimeZone::DaylightTime: return "DST";
+ }
+ Q_UNREACHABLE_RETURN("Unrecognised");
+ };
+ const QList<QByteArray> allList = (QTimeZone::availableTimeZoneIds() << "Vulcan/ShiKahr"_ba);
+#ifdef EXHAUSTIVE_ZONE_DISPLAY
+ const QList<QByteArray> idList = allList;
+#else
+ const QList<QByteArray> idList = {
+ "Africa/Casablanca"_ba, "Africa/Lagos"_ba, "Africa/Tunis"_ba,
+ "America/Caracas"_ba, "America/Coyhaique"_ba,
+ "America/Indiana/Tell_City"_ba, "America/Managua"_ba,
+ "Asia/Bangkok"_ba, "Asia/Colombo"_ba, "Asia/Tokyo"_ba,
+ "Atlantic/Bermuda"_ba, "Atlantic/Faroe"_ba, "Atlantic/Madeira"_ba,
+ "Australia/Broken_Hill"_ba, "Australia/NSW"_ba, "Australia/Tasmania"_ba,
+ "Brazil/Acre"_ba, "Canada/Atlantic"_ba, "Chile/EasterIsland"_ba,
+ "CST6CDT"_ba, "Etc/Greenwich"_ba, "Etc/Universal"_ba,
+ "Europe/Guernsey"_ba, "Europe/Kaliningrad"_ba, "Europe/Kyiv"_ba,
+ "Europe/Prague"_ba, "Europe/Vatican"_ba,
+ "Indian/Comoro"_ba, "Mexico/BajaSur"_ba,
+ "Pacific/Bougainville"_ba, "Pacific/Midway"_ba, "Pacific/Wallis"_ba,
+ "US/Aleutian"_ba,
+ "UTC"_ba,
+ // Those named overtly in tst_QDateTime - special cases first:
+ "UTC-02:00"_ba, "UTC+02:00"_ba, "UTC+12:00"_ba,
+ "Etc/GMT+3"_ba, "GMT-0"_ba, "GMT"_ba,
+ // ... then ordinary names in alphabetic order:
+ "America/Anchorage"_ba, "America/Metlakatla"_ba, "America/New_York"_ba,
+ "America/Sao_Paulo"_ba, "America/Toronto"_ba, "America/Vancouver"_ba,
+ "Asia/Kathmandu"_ba, "Asia/Manila"_ba, "Asia/Singapore"_ba,
+ "Australia/Brisbane"_ba, "Australia/Eucla"_ba, "Australia/Sydney"_ba,
+ "Europe/Berlin"_ba, "Europe/Helsinki"_ba, "Europe/Lisbon"_ba, "Europe/Oslo"_ba,
+ "Europe/Rome"_ba,
+ "Pacific/Apia"_ba, "Pacific/Auckland"_ba, "Pacific/Kiritimati"_ba,
+ "Vulcan/ShiKahr"_ba // Invalid: also worth testing.
+ };
+ // Some valid zones in that list may be absent from the platform's
+ // availableTimeZoneIds(), yet in fact work when used as it's asked to
+ // instantiate them (e.g. Etc/Universal on macOS). This can give them a
+ // displayName() that we fail to decode, without timezone_locale, due to
+ // only trying the availableTimeZoneIds() in findLongNamePrefix(). So we
+ // have to filter on membership of allList when creating rows.
+#endif // Exhaustive
+ const QLocale fr(QLocale::French, QLocale::France);
+ const QLocale hi(QLocale::Hindi, QLocale::India);
+ for (const QByteArray &id : idList) {
+ if (id == "localtime"_ba || id == "posixrules"_ba || !allList.contains(id))
+ continue;
+ QTimeZone zone = QTimeZone(id);
+ if (!zone.isValid())
+ continue;
+ for (const auto type : types) {
+ QTest::addRow("%s@fr_FR/%s", id.constData(), typeName(type))
+ << zone << fr << type;
+ QTest::addRow("%s@hi_IN/%s", id.constData(), typeName(type))
+ << zone << hi << type;
+ }
+ }
+}
+
+void tst_QTimeZoneBackend::roundtripDisplayNames()
+{
+ QFETCH(const QTimeZone, zone);
+ QFETCH(const QLocale, locale);
+ QFETCH(const QTimeZone::TimeType, type);
+ static const QDateTime jan = QDateTime(QDate(2015, 1, 1), QTime(12, 0), QTimeZone::UTC);
+ static const QDateTime jul = QDateTime(QDate(2015, 7, 1), QTime(12, 0), QTimeZone::UTC);
+ const QDateTime dt = zone.isDaylightTime(jul) == (type == QTimeZone::DaylightTime) ? jul : jan;
+
+ // Some zones exercise region format.
+ const QString name = zone.displayName(type, QTimeZone::LongName, locale);
+ if (!name.isEmpty()) {
+ const auto tran = QTimeZonePrivate::extractPrivate(zone)->data(type);
+ const qint64 when = tran.atMSecsSinceEpoch == QTimeZonePrivate::invalidMSecs()
+ ? dt.toMSecsSinceEpoch() : tran.atMSecsSinceEpoch;
+ const QString extended = name + "some spurious cruft"_L1;
+ auto match =
+ QTimeZonePrivate::findLongNamePrefix(extended, locale, when);
+ if (!match)
+ match = QTimeZonePrivate::findLongNamePrefix(extended, locale);
+ if (!match)
+ match = QTimeZonePrivate::findNarrowOffsetPrefix(extended, locale);
+ if (!match)
+ match = QTimeZonePrivate::findLongUtcPrefix(extended);
+ auto report = qScopeGuard([=]() {
+ qDebug() << "At" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
+ << "via" << name;
+ });
+ QCOMPARE(match.nameLength, name.size());
+ report.dismiss();
+#if 0
+ if (match.ianaId != zone.id()) {
+ const QTimeZone found = QTimeZone(match.ianaId);
+ if (QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when)
+ != QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)) {
+ // For DST, some zones haven't done it in ages, so tran may be ancient.
+ // Meanwhile, match.ianaId is typically the canonical zone for a metazone.
+ // That, in turn, may not have been doing DST when zone was.
+ // So we can't rely on a match, but can report the mismatches.
+ qDebug() << "Long name" << name << "on"
+ << QTimeZonePrivate::extractPrivate(zone)->offsetFromUtc(when)
+ << "at" << QDateTime::fromMSecsSinceEpoch(when, QTimeZone::UTC)
+ << "got" << match.ianaId << "on"
+ << QTimeZonePrivate::extractPrivate(found)->offsetFromUtc(when);
+ // There are also some absurdly over-generic names, that lead to
+ // ambiguities, e.g. "heure : West"
+ }
+ }
+#endif // Debug code
+ } else if (type != QTimeZone::DaylightTime) { /* Zones with no DST have no DST-name */
+ qDebug("Empty display name");
+ }
+}
+
+// Relies on local variable names: zone tzp and locale enUS.
+#define ZONE_DNAME_CHECK(type, name, val) \
+ QCOMPARE(tzp.displayName(QTimeZone::type, QTimeZone::name, enUS), val);
+
+void tst_QTimeZoneBackend::icuTest()
+{
+#if QT_CONFIG(icu) && !QT_CONFIG(timezone_tzdb) && (defined(Q_OS_VXWORKS) || !defined(Q_OS_UNIX))
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QIcuTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid is not available:
+ QVERIFY(!tzpd.isTimeZoneIdAvailable("Gondwana/Erewhon"));
+ // and construction gives an invalid result:
+ QIcuTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QIcuTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by ICU version installed
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS("en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, u"Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, u"GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, u"UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, u"Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, u"GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, u"UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, u"Central European Standard Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, u"GMT+01:00");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, u"UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QIcuTimeZonePrivate("America/Toronto"));
+#endif // ICU without tzdb, on VxWorks or not on Unix
+}
+
+void tst_QTimeZoneBackend::tzTest()
+{
+#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \
+ || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS))
+ const auto UTC = QTimeZone::UTC;
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QTzTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QTzTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QVERIFY(!tzpi.isValid());
+
+ // Test named constructor
+ QTzTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Test POSIX-format value for $TZ:
+ QTimeZone tzposix("MET-1METDST-2,M3.5.0/02:00:00,M10.5.0/03:00:00");
+ QVERIFY(tzposix.isValid());
+ QVERIFY(tzposix.hasDaylightTime());
+
+ // Cope with stray space at start of value (QTBUG-135109):
+ QTimeZone syd(" AEST-10AEDT,M10.1.0,M4.1.0/3");
+ QVERIFY(syd.isValid());
+ QVERIFY(syd.hasDaylightTime());
+
+ // RHEL has been seen with this as Africa/Casablanca's POSIX rule:
+ QTzTimeZonePrivate permaDst("<+00>0<+01>,0/0,J365/25");
+ const QTimeZone utcP1("UTC+01:00"); // Should always have same offset as permaDst
+ QVERIFY(permaDst.isValid());
+ QVERIFY(permaDst.hasDaylightTime());
+ QVERIFY(permaDst.isDaylightTime(QDate(2020, 1, 1).startOfDay(utcP1).toMSecsSinceEpoch()));
+ QVERIFY(permaDst.isDaylightTime(QDate(2020, 12, 31).endOfDay(utcP1).toMSecsSinceEpoch()));
+ // Note that the final /25 could be misunderstood as putting a fall-back at
+ // 1am on the next year's Jan 1st; check we don't do that:
+ QVERIFY(permaDst.isDaylightTime(
+ QDateTime(QDate(2020, 1, 1), QTime(1, 30), utcP1).toMSecsSinceEpoch()));
+ // It shouldn't have any transitions. QTimeZone::hasTransitions() only says
+ // whether the backend supports them, so ask for transitions in a wide
+ // enough interval that one would show up, if there are any:
+ QVERIFY(permaDst.transitions(QDate(2015, 1, 1).startOfDay(UTC).toMSecsSinceEpoch(),
+ QDate(2020, 1, 1).startOfDay(UTC).toMSecsSinceEpoch()
+ ).isEmpty());
+
+ QTimeZone tzBrazil("BRT+3"); // parts of Northern Brazil, as a POSIX rule
+ QVERIFY(tzBrazil.isValid());
+ QCOMPARE(tzBrazil.offsetFromUtc(QDateTime(QDate(1111, 11, 11).startOfDay())), -10800);
+
+ // Test display names by type, either ICU or abbreviation only
+ QLocale enUS(u"en_US");
+ // Only test names in debug mode, names used can vary by ICU version installed
+ if constexpr (debug) {
+#if QT_CONFIG(icu)
+ ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+#else
+ ZONE_DNAME_CHECK(StandardTime, LongName, "CET");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "CET");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "CET");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "CEST");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "CEST");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "CEST");
+ ZONE_DNAME_CHECK(GenericTime, LongName, "CET");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "CET");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "CET");
+#endif // icu
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QTzTimeZonePrivate("America/Toronto"));
+ if (QTest::currentTestFailed())
+ return;
+
+ // Test first and last transition rule
+ // Warning: This could vary depending on age of TZ file!
+
+ // Test low date uses first rule found
+ constexpr qint64 ancient = -Q_INT64_C(9999999999999);
+ // Note: Depending on the OS in question, the database may be carrying the
+ // Local Mean Time. which for Berlin is 0:53:28
+ QTimeZonePrivate::Data dat = tzp.data(ancient);
+ QCOMPARE(dat.atMSecsSinceEpoch, ancient);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+ if (dat.abbreviation == u"LMT") {
+ QCOMPARE(dat.standardTimeOffset, 3208);
+ } else {
+ QCOMPARE(dat.standardTimeOffset, 3600);
+
+ constexpr qint64 invalidTime = std::numeric_limits<qint64>::min();
+ constexpr int invalidOffset = std::numeric_limits<int>::min();
+ // Test previous to low value is invalid
+ dat = tzp.previousTransition(ancient);
+ QCOMPARE(dat.atMSecsSinceEpoch, invalidTime);
+ QCOMPARE(dat.standardTimeOffset, invalidOffset);
+ QCOMPARE(dat.daylightTimeOffset, invalidOffset);
+ }
+
+ dat = tzp.nextTransition(ancient);
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
+ QTimeZone::fromSecondsAheadOfUtc(3600)),
+ QDateTime(QDate(1893, 4, 1), QTime(0, 6, 32),
+ QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ // Date-times late enough to exercise POSIX rules:
+ qint64 stdHi = QDate(2100, 1, 1).startOfDay(UTC).toMSecsSinceEpoch();
+ qint64 dstHi = QDate(2100, 6, 1).startOfDay(UTC).toMSecsSinceEpoch();
+ // Relevant last Sundays in October and March:
+ QCOMPARE(Qt::DayOfWeek(QDate(2099, 10, 25).dayOfWeek()), Qt::Sunday);
+ QCOMPARE(Qt::DayOfWeek(QDate(2100, 3, 28).dayOfWeek()), Qt::Sunday);
+ QCOMPARE(Qt::DayOfWeek(QDate(2100, 10, 31).dayOfWeek()), Qt::Sunday);
+
+ dat = tzp.data(stdHi);
+ QCOMPARE(dat.atMSecsSinceEpoch - stdHi, qint64(0));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ dat = tzp.data(dstHi);
+ QCOMPARE(dat.atMSecsSinceEpoch - dstHi, qint64(0));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.previousTransition(stdHi);
+ QCOMPARE(dat.abbreviation, u"CET");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2099, 10, 25), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ dat = tzp.previousTransition(dstHi);
+ QCOMPARE(dat.abbreviation, u"CEST");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.nextTransition(stdHi);
+ QCOMPARE(dat.abbreviation, u"CEST");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2100, 3, 28), QTime(2, 0), QTimeZone::fromSecondsAheadOfUtc(3600)));
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+
+ dat = tzp.nextTransition(dstHi);
+ QCOMPARE(dat.abbreviation, u"CET");
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(dat.atMSecsSinceEpoch,
+ QTimeZone::fromSecondsAheadOfUtc(3600)),
+ QDateTime(QDate(2100, 10, 31), QTime(3, 0), QTimeZone::fromSecondsAheadOfUtc(7200)));
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+
+ // Test TZ timezone vs UTC timezone for non-whole-hour negative offset:
+ QTzTimeZonePrivate tztz1("America/Caracas");
+ QUtcTimeZonePrivate tzutc1("UTC-04:30");
+ QVERIFY(tztz1.isValid());
+ QVERIFY(tzutc1.isValid());
+ QTzTimeZonePrivate::Data datatz1 = tztz1.data(std);
+ QTzTimeZonePrivate::Data datautc1 = tzutc1.data(std);
+ QCOMPARE(datatz1.offsetFromUtc, datautc1.offsetFromUtc);
+
+ // Test TZ timezone vs UTC timezone for non-whole-hour positive offset:
+ QTzTimeZonePrivate tztz2k("Asia/Kolkata"); // New name
+ QTzTimeZonePrivate tztz2c("Asia/Calcutta"); // Legacy name
+ // Can't assign QtzTZP, so use a reference; prefer new name.
+ QTzTimeZonePrivate &tztz2 = tztz2k.isValid() ? tztz2k : tztz2c;
+ QUtcTimeZonePrivate tzutc2("UTC+05:30");
+ QVERIFY2(tztz2.isValid(), tztz2.id().constData());
+ QVERIFY(tzutc2.isValid());
+ QTzTimeZonePrivate::Data datatz2 = tztz2.data(std);
+ QTzTimeZonePrivate::Data datautc2 = tzutc2.data(std);
+ QCOMPARE(datatz2.offsetFromUtc, datautc2.offsetFromUtc);
+
+ // Test a timezone with an abbreviation that isn't all letters:
+ QTzTimeZonePrivate tzBarnaul("Asia/Barnaul");
+ if (tzBarnaul.isValid()) {
+ QCOMPARE(tzBarnaul.data(std).abbreviation, u"+07");
+
+ // first full day of the new rule (tzdata2016b)
+ QDateTime dt(QDate(2016, 3, 28), QTime(0, 0), UTC);
+ QCOMPARE(tzBarnaul.data(dt.toMSecsSinceEpoch()).abbreviation, u"+07");
+ }
+#endif // Unix && !(timezone_tzdb || Darwin || Android || VxWorks)
+}
+
+void tst_QTimeZoneBackend::macTest()
+{
+#if defined(Q_OS_DARWIN) && !QT_CONFIG(timezone_tzdb)
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QMacTimeZonePrivate tzpd;
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QMacTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QMacTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by version
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS(u"en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, "Central European Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "GMT+01:00");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "Central European Summer Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "GMT+02:00");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ // ICU C api does not support Generic Time yet, C++ api does
+ ZONE_DNAME_CHECK(GenericTime, LongName, "Central European Time");
+ ZONE_DNAME_CHECK(GenericTime, ShortName, "Germany Time");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QMacTimeZonePrivate("America/Toronto"));
+#endif // Q_OS_DARWIN without tzdb
+}
+
+void tst_QTimeZoneBackend::winTest()
+{
+#if defined(USING_WIN_TZ)
+ // Known datetimes
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), QTimeZone::UTC).toMSecsSinceEpoch();
+
+ // Test default constructor
+ QWinTimeZonePrivate tzpd;
+ if constexpr (debug)
+ qDebug() << "System ID = " << tzpd.id()
+ << tzpd.displayName(QTimeZone::StandardTime, QTimeZone::LongName, QLocale())
+ << tzpd.displayName(QTimeZone::GenericTime, QTimeZone::LongName, QLocale());
+ QVERIFY(tzpd.isValid());
+
+ // Test invalid constructor
+ QWinTimeZonePrivate tzpi("Gondwana/Erewhon");
+ QCOMPARE(tzpi.isValid(), false);
+
+ // Test named constructor
+ QWinTimeZonePrivate tzp("Europe/Berlin");
+ QVERIFY(tzp.isValid());
+
+ // Only test names in debug mode, names used can vary by version
+ if constexpr (debug) {
+ // Test display names by type
+ QLocale enUS(u"en_US");
+ ZONE_DNAME_CHECK(StandardTime, LongName, "W. Europe Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, ShortName, "W. Europe Standard Time");
+ ZONE_DNAME_CHECK(StandardTime, OffsetName, "UTC+01:00");
+ ZONE_DNAME_CHECK(DaylightTime, LongName, "W. Europe Daylight Time");
+ ZONE_DNAME_CHECK(DaylightTime, ShortName, "W. Europe Daylight Time");
+ ZONE_DNAME_CHECK(DaylightTime, OffsetName, "UTC+02:00");
+ ZONE_DNAME_CHECK(GenericTime, LongName,
+ "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
+ ZONE_DNAME_CHECK(GenericTime, ShortName,
+ "(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna");
+ ZONE_DNAME_CHECK(GenericTime, OffsetName, "UTC+01:00");
+
+ // Test Abbreviations
+ QCOMPARE(tzp.abbreviation(std), u"CET");
+ QCOMPARE(tzp.abbreviation(dst), u"CEST");
+ }
+
+ testCetPrivate(tzp);
+ if (QTest::currentTestFailed())
+ return;
+ testEpochTranPrivate(QWinTimeZonePrivate("America/Toronto"));
+#endif // TZ backend
+}
+
+#undef ZONE_DNAME_CHECK
+
+// Test each private produces the same basic results for CET
+void tst_QTimeZoneBackend::testCetPrivate(const QTimeZonePrivate &tzp)
+{
+ // Known datetimes
+ const auto UTC = QTimeZone::UTC;
+ const auto eastOneHour = QTimeZone::fromSecondsAheadOfUtc(3600);
+ qint64 std = QDateTime(QDate(2012, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 dst = QDateTime(QDate(2012, 6, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+ qint64 prev = QDateTime(QDate(2011, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch();
+
+ QCOMPARE(tzp.offsetFromUtc(std), 3600);
+ QCOMPARE(tzp.offsetFromUtc(dst), 7200);
+
+ QCOMPARE(tzp.standardTimeOffset(std), 3600);
+ QCOMPARE(tzp.standardTimeOffset(dst), 3600);
+
+ QCOMPARE(tzp.daylightTimeOffset(std), 0);
+ QCOMPARE(tzp.daylightTimeOffset(dst), 3600);
+
+ QCOMPARE(tzp.hasDaylightTime(), true);
+ QCOMPARE(tzp.isDaylightTime(std), false);
+ QCOMPARE(tzp.isDaylightTime(dst), true);
+
+ QTimeZonePrivate::Data dat = tzp.data(std);
+ QCOMPARE(dat.atMSecsSinceEpoch, std);
+ QCOMPARE(dat.offsetFromUtc, 3600);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 0);
+ QCOMPARE(dat.abbreviation, tzp.abbreviation(std));
+
+ dat = tzp.data(dst);
+ QCOMPARE(dat.atMSecsSinceEpoch, dst);
+ QCOMPARE(dat.offsetFromUtc, 7200);
+ QCOMPARE(dat.standardTimeOffset, 3600);
+ QCOMPARE(dat.daylightTimeOffset, 3600);
+ QCOMPARE(dat.abbreviation, tzp.abbreviation(dst));
+
+ // Only test transitions if host system supports them
+ if (tzp.hasTransitions()) {
+ QTimeZonePrivate::Data tran = tzp.nextTransition(std);
+ // 2012-03-25 02:00 CET, +1 -> +2
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
+ QCOMPARE(tran.offsetFromUtc, 7200);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ tran = tzp.nextTransition(dst);
+ // 2012-10-28 03:00 CEST, +2 -> +1
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 10, 28), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
+ QCOMPARE(tran.offsetFromUtc, 3600);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+
+ tran = tzp.previousTransition(std);
+ // 2011-10-30 03:00 CEST, +2 -> +1
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2011, 10, 30), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)));
+ QCOMPARE(tran.offsetFromUtc, 3600);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+
+ tran = tzp.previousTransition(dst);
+ // 2012-03-25 02:00 CET, +1 -> +2 (again)
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(2012, 3, 25), QTime(2, 0), eastOneHour));
+ QCOMPARE(tran.offsetFromUtc, 7200);
+ QCOMPARE(tran.standardTimeOffset, 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ QTimeZonePrivate::DataList expected;
+ // 2011-03-27 02:00 CET, +1 -> +2
+ tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 3, 27), QTime(2, 0),
+ eastOneHour).toMSecsSinceEpoch();
+ tran.offsetFromUtc = 7200;
+ tran.standardTimeOffset = 3600;
+ tran.daylightTimeOffset = 3600;
+ expected << tran;
+ // 2011-10-30 03:00 CEST, +2 -> +1
+ tran.atMSecsSinceEpoch = QDateTime(QDate(2011, 10, 30), QTime(3, 0),
+ QTimeZone::fromSecondsAheadOfUtc(2 * 3600)
+ ).toMSecsSinceEpoch();
+ tran.offsetFromUtc = 3600;
+ tran.standardTimeOffset = 3600;
+ tran.daylightTimeOffset = 0;
+ expected << tran;
+ QTimeZonePrivate::DataList result = tzp.transitions(prev, std);
+ QCOMPARE(result.size(), expected.size());
+ for (int i = 0; i < expected.size(); ++i) {
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(result.at(i).atMSecsSinceEpoch, eastOneHour),
+ QDateTime::fromMSecsSinceEpoch(expected.at(i).atMSecsSinceEpoch, eastOneHour));
+ QCOMPARE(result.at(i).offsetFromUtc, expected.at(i).offsetFromUtc);
+ QCOMPARE(result.at(i).standardTimeOffset, expected.at(i).standardTimeOffset);
+ QCOMPARE(result.at(i).daylightTimeOffset, expected.at(i).daylightTimeOffset);
+ }
+ }
+}
+
+// Needs a zone with DST around the epoch; currently America/Toronto (EST5EDT)
+void tst_QTimeZoneBackend::testEpochTranPrivate(const QTimeZonePrivate &tzp)
+{
+ if (!tzp.hasTransitions())
+ return; // test only viable for transitions
+
+ const auto UTC = QTimeZone::UTC;
+ const auto hour = std::chrono::hours{1};
+ QTimeZonePrivate::Data tran = tzp.nextTransition(0); // i.e. first after epoch
+ // 1970-04-26 02:00 EST, -5 -> -4
+ const QDateTime after = QDateTime(QDate(1970, 4, 26), QTime(2, 0),
+ QTimeZone::fromDurationAheadOfUtc(-5 * hour));
+ const QDateTime found = QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC);
+#ifdef USING_WIN_TZ // MS gets the date wrong: 5th April instead of 26th.
+ QCOMPARE(found.toOffsetFromUtc(-5 * 3600).time(), after.time());
+#else
+ QCOMPARE(found, after);
+#endif
+ QCOMPARE(tran.offsetFromUtc, -4 * 3600);
+ QCOMPARE(tran.standardTimeOffset, -5 * 3600);
+ QCOMPARE(tran.daylightTimeOffset, 3600);
+
+ // Pre-epoch time-zones might not be supported at all:
+ tran = tzp.nextTransition(QDateTime(QDate(1601, 1, 1), QTime(0, 0), UTC).toMSecsSinceEpoch());
+ if (tran.atMSecsSinceEpoch != QTimeZonePrivate::invalidMSecs()
+ // Toronto *did* have a transition before 1970 (DST since 1918):
+ && tran.atMSecsSinceEpoch < 0) {
+ // ... but, if they are, we should be able to search back to them:
+ tran = tzp.previousTransition(0); // i.e. last before epoch
+ // 1969-10-26 02:00 EDT, -4 -> -5
+ QCOMPARE(QDateTime::fromMSecsSinceEpoch(tran.atMSecsSinceEpoch, UTC),
+ QDateTime(QDate(1969, 10, 26), QTime(2, 0),
+ QTimeZone::fromDurationAheadOfUtc(-4 * hour)));
+ QCOMPARE(tran.offsetFromUtc, -5 * 3600);
+ QCOMPARE(tran.standardTimeOffset, -5 * 3600);
+ QCOMPARE(tran.daylightTimeOffset, 0);
+ } else {
+ // Do not use QSKIP(): that would discard the rest of this sub-test's caller.
+ qDebug() << "No support for pre-epoch time-zone transitions";
+ }
+}
+
+QTEST_APPLESS_MAIN(tst_QTimeZoneBackend)
+#include "tst_qtimezonebackend.moc"
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 6ff905cb1cb..452b7dcef88 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -729,11 +729,12 @@ void tst_Http2::earlyError()
: QHttpNetworkConnection::ConnectionTypeHTTP2;
QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr,
connectionType);
+#if QT_CONFIG(ssl)
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setAllowedNextProtocols({"h2"});
connection.setSslConfiguration(config);
connection.ignoreSslErrors();
-
+#endif
// SETUP manually setup the QHttpNetworkRequest
QHttpNetworkRequest req;
req.setSsl(true);
@@ -809,11 +810,12 @@ void tst_Http2::abortReply()
: QHttpNetworkConnection::ConnectionTypeHTTP2;
QHttpNetworkConnection connection(1, "127.0.0.1", serverPort, true, false, nullptr,
connectionType);
+#if QT_CONFIG(ssl)
QSslConfiguration config = QSslConfiguration::defaultConfiguration();
config.setAllowedNextProtocols({"h2"});
connection.setSslConfiguration(config);
connection.ignoreSslErrors();
-
+#endif
// SETUP manually setup the QHttpNetworkRequest
QHttpNetworkRequest req;
req.setSsl(true);
diff --git a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
index fdc2dde7921..c2b03b70d9b 100644
--- a/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
+++ b/tests/auto/network/access/qnetworkreply/tst_qnetworkreply.cpp
@@ -10290,7 +10290,7 @@ void tst_QNetworkReply::requestWithTimeout()
QSignalSpy spy(reply.data(), &QNetworkReply::errorOccurred);
QCOMPARE(waitForFinish(reply), int(Failure));
QCOMPARE(spy.size(), 1);
- QCOMPARE(reply->error(), QNetworkReply::OperationCanceledError);
+ QCOMPARE(reply->error(), QNetworkReply::TimeoutError);
}
#endif
diff --git a/tests/auto/other/languagechange/tst_languagechange.cpp b/tests/auto/other/languagechange/tst_languagechange.cpp
index 8f99730a192..ab9244c8561 100644
--- a/tests/auto/other/languagechange/tst_languagechange.cpp
+++ b/tests/auto/other/languagechange/tst_languagechange.cpp
@@ -172,10 +172,10 @@ void tst_languageChange::retranslatability_data()
<< "QFileDialog::Back"
<< "QFileDialog::Create New Folder"
<< "QFileDialog::Detail View"
- << "QFileDialog::Files of type:"
+ << "QFileDialog::Files of &type:"
<< "QFileDialog::Forward"
<< "QFileDialog::List View"
- << "QFileDialog::Look in:"
+ << "QFileDialog::&Look in:"
<< "QFileDialog::Open"
<< "QFileDialog::Parent Directory"
<< "QFileDialog::Show "
diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
index 2e92cdffada..764644bb192 100644
--- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
+++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
@@ -1793,6 +1793,15 @@ void tst_QAccessibility::spinBoxTest()
QAccessibleTextInterface *textIface = interface->textInterface();
QVERIFY(textIface);
+ QVERIFY(!spinBox->isReadOnly());
+ QVERIFY(interface->state().editable);
+ QVERIFY(!interface->state().readOnly);
+
+ spinBox->setReadOnly(true);
+ QVERIFY(spinBox->isReadOnly());
+ QVERIFY(!interface->state().editable);
+ QVERIFY(interface->state().readOnly);
+
QTestAccessibility::clearEvents();
}
diff --git a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
index 5f54471bc0f..df3bf7472d0 100644
--- a/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
+++ b/tests/auto/widgets/dialogs/qfiledialog/tst_qfiledialog.cpp
@@ -815,9 +815,9 @@ void tst_QFiledialog::labelText()
QFileDialog fd;
QDialogButtonBox buttonBox;
QPushButton *cancelButton = buttonBox.addButton(QDialogButtonBox::Cancel);
- QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("Look in:"));
+ QCOMPARE(fd.labelText(QFileDialog::LookIn), QString("&Look in:"));
QCOMPARE(fd.labelText(QFileDialog::FileName), QString("File &name:"));
- QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of type:"));
+ QCOMPARE(fd.labelText(QFileDialog::FileType), QString("Files of &type:"));
QCOMPARE(fd.labelText(QFileDialog::Accept), QString("&Open")); ///### see task 241462
QCOMPARE(fd.labelText(QFileDialog::Reject), cancelButton->text());
diff --git a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp
index 6859f22c044..10a67daa02a 100644
--- a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp
+++ b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp
@@ -845,12 +845,12 @@ void tst_QComboBox::virtualAutocompletion()
QKeyEvent kp3(QEvent::KeyPress, Qt::Key_R, {}, "r");
QKeyEvent kr3(QEvent::KeyRelease, Qt::Key_R, {}, "r");
- QTest::qWait(QApplication::keyboardInputInterval());
QApplication::sendEvent(testWidget, &kp3);
+ QTest::qWait(QApplication::keyboardInputInterval());
QApplication::sendEvent(testWidget, &kr3);
QTRY_COMPARE(testWidget->currentIndex(), 3);
- QTest::qWait(QApplication::keyboardInputInterval());
+ QTest::qWait(2 * QApplication::keyboardInputInterval());
testWidget->view()->setKeyboardSearchFlags(Qt::MatchContains | Qt::MatchWrap);
QApplication::sendEvent(testWidget, &kp3);
QApplication::sendEvent(testWidget, &kr3);
@@ -2247,10 +2247,11 @@ void tst_QComboBox::ignoreWheelEvents()
QFETCH(bool, allowWheelScrolling);
+ AllowWheelScrollStyle style(allowWheelScrolling);
WheelEventTestWidget widget;
QComboBox *comboBox = new QComboBox(&widget);
comboBox->addItems({ "0", "1" });
- comboBox->setStyle(new AllowWheelScrollStyle(allowWheelScrolling));
+ comboBox->setStyle(&style);
widget.show();
QVERIFY(QTest::qWaitForWindowExposed(&widget));
diff --git a/tests/manual/CMakeLists.txt b/tests/manual/CMakeLists.txt
index 8ace9592141..ad9db868235 100644
--- a/tests/manual/CMakeLists.txt
+++ b/tests/manual/CMakeLists.txt
@@ -6,7 +6,6 @@ if(UIKIT)
return()
endif()
-add_subdirectory(assets)
add_subdirectory(corelib)
add_subdirectory(filetest)
# diaglib is broken in dev due to missing
diff --git a/tests/manual/assets/CMakeLists.txt b/tests/manual/assets/CMakeLists.txt
deleted file mode 100644
index 03643aa8537..00000000000
--- a/tests/manual/assets/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (C) 2024 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-add_subdirectory(downloader)
diff --git a/tests/manual/assets/assets.pro b/tests/manual/assets/assets.pro
deleted file mode 100644
index 43f09ba46e6..00000000000
--- a/tests/manual/assets/assets.pro
+++ /dev/null
@@ -1,3 +0,0 @@
-TEMPLATE=subdirs
-
-SUBDIRS = downloader
diff --git a/tests/manual/assets/downloader/CMakeLists.txt b/tests/manual/assets/downloader/CMakeLists.txt
deleted file mode 100644
index b95161ac02d..00000000000
--- a/tests/manual/assets/downloader/CMakeLists.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2024 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
-
-#####################################################################
-## tst_manual_downloader Binary:
-#####################################################################
-
-qt_internal_add_manual_test(tst_manual_downloader
- GUI
- SOURCES
- main.cpp
- LIBRARIES
- Qt::ExamplesAssetDownloaderPrivate
- Qt::Widgets
-)
diff --git a/tests/manual/assets/downloader/downloader.pro b/tests/manual/assets/downloader/downloader.pro
deleted file mode 100644
index 53976c538f0..00000000000
--- a/tests/manual/assets/downloader/downloader.pro
+++ /dev/null
@@ -1,5 +0,0 @@
-QT += examples_asset_downloader-private widgets
-
-TARGET = tst_manual_downloader
-
-SOURCES += main.cpp
diff --git a/tests/manual/assets/downloader/main.cpp b/tests/manual/assets/downloader/main.cpp
deleted file mode 100644
index d9d711cfce5..00000000000
--- a/tests/manual/assets/downloader/main.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2024 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include <QtExamplesAssetDownloader/assetdownloader.h>
-
-#include <QApplication>
-#include <QMessageBox>
-#include <QProgressDialog>
-
-using namespace Assets::Downloader;
-
-int main(int argc, char *argv[])
-{
- QApplication app(argc, argv);
- app.setOrganizationName("QtProject");
- app.setApplicationName("Asset Downloader");
-
- QProgressDialog progress;
- progress.setAutoClose(false);
- progress.setRange(0, 0);
- QObject::connect(&progress, &QProgressDialog::canceled, &app, &QApplication::quit);
-
- AssetDownloader downloader;
- downloader.setJsonFileName("car-configurator-assets-v1.json");
- downloader.setZipFileName("car-configurator-assets-v1.zip");
- downloader.setDownloadBase(QUrl("https://download.qt.io/learning/examples/"));
-
- QObject::connect(&downloader, &AssetDownloader::started,
- &progress, &QProgressDialog::show);
- QObject::connect(&downloader, &AssetDownloader::progressChanged, &progress, [&progress](
- int progressValue, int progressMaximum, const QString &progressText) {
- progress.setLabelText(progressText);
- progress.setMaximum(progressMaximum);
- progress.setValue(progressValue);
- });
- QObject::connect(&downloader, &AssetDownloader::finished, &progress, [&](bool success) {
- progress.reset();
- progress.hide();
- if (success)
- QMessageBox::information(nullptr, "Asset Downloader", "Download Finished Successfully.");
- else
- QMessageBox::warning(nullptr, "Asset Downloader", "Download Finished with an Error.");
- });
-
- downloader.start();
-
- return app.exec();
-}
diff --git a/tests/manual/manual.pro b/tests/manual/manual.pro
index 47f2ae9bd0f..e28eb5316a8 100644
--- a/tests/manual/manual.pro
+++ b/tests/manual/manual.pro
@@ -2,7 +2,6 @@ TEMPLATE=subdirs
QT_FOR_CONFIG += network-private gui-private
SUBDIRS = \
-assets \
filetest \
embeddedintoforeignwindow \
foreignwindows \
diff --git a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
index 1fd6e0a0c4f..ff3e67e3fc2 100644
--- a/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
+++ b/tests/manual/wasm/eventloop/suspendresumecontrol_auto/main.cpp
@@ -3,6 +3,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/private/qwasmsuspendresumecontrol_p.h>
+#include <QtCore/qdebug.h>
#include <qtwasmtestlib.h>
using namespace emscripten;
@@ -20,6 +21,7 @@ private slots:
void reuseTimer();
void cancelTimer();
void deleteTimer();
+ void suspendExclusive();
};
// Verify that a single timer fires
@@ -138,6 +140,49 @@ void WasmSuspendResumeControlTest::deleteTimer()
QWASMSUCCESS();
}
+// Verify that an exclusive suspend resumes for the exclusive event only
+void WasmSuspendResumeControlTest::suspendExclusive()
+{
+ QWasmSuspendResumeControl suspendResume;
+
+ // (re) implement a native timer - this gives us a unique event handler
+ // index which we can suspend exclusively on.
+ bool exclusiveTimerFired = false;
+ auto exclusiveTimerHandler = [&exclusiveTimerFired](emscripten::val) {
+ exclusiveTimerFired = true;
+ };
+ uint32_t exlusiveTimerHandlerIndex = suspendResume.registerEventHandler(std::move(exclusiveTimerHandler));
+
+ std::chrono::milliseconds exclusiveTimerTimeout = timerTimeout * 4;
+ double timoutValue = static_cast<double>(exclusiveTimerTimeout.count());
+ val jsHandler = suspendResume.jsEventHandlerAt(exlusiveTimerHandlerIndex);
+ val::global("window").call<double>("setTimeout", jsHandler, timoutValue);
+
+ // Schedule suppressedTimer to fire before the exclusive timer. Expected
+ // behavior is that it doesn't.
+ bool suppressedTimerFired = false;
+ QWasmTimer suppressedTimer(&suspendResume, [&suppressedTimerFired](){
+ suppressedTimerFired = true;
+ });
+ suppressedTimer.setTimeout(timerTimeout);
+
+ // Suspend exclusively for the exclusive timer, and verify that
+ // the correct timers fired.
+ suspendResume.suspendExclusive(exlusiveTimerHandlerIndex);
+ suspendResume.sendPendingEvents(); // <- also clears exclusive mode
+ if (!exclusiveTimerFired)
+ QWASMFAIL("Exclusive timer did not fire");
+ if (suppressedTimerFired)
+ QWASMFAIL("Suppressed timer did fire");
+
+ // Send (all) events, this should give is the suppressed timer
+ suspendResume.sendPendingEvents();
+ if (!suppressedTimerFired)
+ QWASMFAIL("Suppressed timer did not fire");
+
+ QWASMSUCCESS();
+}
+
int main(int argc, char **argv)
{
auto testObject = std::make_shared<WasmSuspendResumeControlTest>();
diff --git a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
index ec03c7209a4..1e49847c97f 100644
--- a/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
+++ b/tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
@@ -127,7 +127,7 @@ void passTest()
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
- emscripten::function("runTestFunction", &runTestFunction);
+ emscripten::function("runTestFunction", &runTestFunction, emscripten::async());
emscripten::function("qtWasmFail", &failTest);
emscripten::function("qtWasmPass", &passTest);
}
diff --git a/util/sbom/cyclonedx/.gitignore b/util/sbom/cyclonedx/.gitignore
new file mode 100644
index 00000000000..140f5f55f12
--- /dev/null
+++ b/util/sbom/cyclonedx/.gitignore
@@ -0,0 +1,6 @@
+__pycache__
+*.egg-info
+.coverage
+.idea
+*.pyc
+build/
diff --git a/util/sbom/cyclonedx/Makefile b/util/sbom/cyclonedx/Makefile
new file mode 100644
index 00000000000..711daf5da22
--- /dev/null
+++ b/util/sbom/cyclonedx/Makefile
@@ -0,0 +1,14 @@
+lint: check format_check
+
+env:
+ uv sync
+
+check:
+ uvx ruff check
+ uvx pyright
+
+format:
+ uvx ruff format
+
+format_check:
+ uvx ruff format --check
diff --git a/util/sbom/cyclonedx/README.md b/util/sbom/cyclonedx/README.md
new file mode 100644
index 00000000000..3a6394f4e74
--- /dev/null
+++ b/util/sbom/cyclonedx/README.md
@@ -0,0 +1,54 @@
+# Qt CycloneDX SBOM helper tool
+
+This repository contains a Python script to create a CycloneDX Software
+Bill of Materials (SBOM) document for Qt-based projects.
+
+## Requirements
+
+- [Python 3.9](https://www.python.org/downloads/),
+- `pip` to manage Python packages.
+- [cyclonedx-python-lib package](https://pypi.org/project/cyclonedx-python-lib/) can be installed via pip
+- optional [tomli package](https://pypi.org/project/tomli/) if using Python 3.9 and the vendored
+ tomli package is causing issues
+
+```sh
+pip install 'cyclonedx-python-lib[json-validation]' tomli
+```
+or
+```
+pip install . # in the current directory to parse the `pyproject.toml` dependencies
+```
+
+## Description
+
+The tool is not meant to be run standalone. Instead, the Qt CMake build system generates an
+intermediate TOML file during the build process, which is then processed by the provided Python
+script to create the final CycloneDX SBOM in JSON format.
+
+## Development
+
+The script is using [uv](https://docs.astral.sh/uv/getting-started/installation/) to manage
+a virtual environment for development. Install it before running the commands below.
+
+Run
+
+```sh
+# Create a virtual environment with with the project dependencies using `uv` in the current dir
+make env
+
+# Optionally install the package in editable mode, for IDE support if needed.
+uv pip install -e .
+
+# Run the script
+python ./qt_cyclonedx_generator/qt_cyclonedx_generator.py
+```
+
+To format the source code run:
+```sh
+make format # uses uvx ruff format
+```
+
+To run the lints:
+```sh
+make lint # uses uvx ruff check and pyright
+```
diff --git a/util/sbom/cyclonedx/pyproject.toml b/util/sbom/cyclonedx/pyproject.toml
new file mode 100644
index 00000000000..19500012555
--- /dev/null
+++ b/util/sbom/cyclonedx/pyproject.toml
@@ -0,0 +1,54 @@
+[project]
+name = "qt-cyclonedx-generator"
+version = "0.1.0"
+description = "Tool to generate a CycloneDX v1.6 SBOM for a Qt project."
+readme = "README.md"
+
+requires-python = ">=3.9"
+
+dependencies = [
+ "cyclonedx-python-lib[json-validation]>=10.0.0",
+ 'tomli ; python_version < "3.11"'
+]
+
+# Development dependencies.
+[dependency-groups]
+dev = [
+ "setuptools>=65",
+ "ruff>=0.11.2",
+ "tomli>=2.3.0",
+ "pyright>=1.1.406",
+]
+
+[project.scripts]
+qt_cyclonedx_generator = "qt_cyclonedx_generator.qt_cyclonedx_generator:main"
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[tool.ruff.lint]
+ignore = ["E402"]
+
+[tool.pyright]
+# To find the imports created by uv
+venvPath = "."
+venv = ".venv"
+
+# Exclude the default folders, plus the build one.
+exclude = [
+ "**/node_modules",
+ "**/__pycache__",
+ "**/.*",
+ "build/*",
+]
+
+# Because some of the toml libraries might not be found
+reportMissingImports = "none"
+
+# Strict is too strict.
+typeCheckingMode = "basic"
+
+pythonVersion = "3.9"
+pythonPlatform = "All"
+
diff --git a/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py b/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py
new file mode 100644
index 00000000000..50aa992f8ba
--- /dev/null
+++ b/util/sbom/cyclonedx/qt_cyclonedx_generator/__init__.py
@@ -0,0 +1,2 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
diff --git a/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py b/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py
new file mode 100644
index 00000000000..3b04a47d714
--- /dev/null
+++ b/util/sbom/cyclonedx/qt_cyclonedx_generator/qt_cyclonedx_generator.py
@@ -0,0 +1,881 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+import importlib.util
+import logging
+import sys
+
+
+def setup_log() -> logging.Logger:
+ logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)
+ log_object = logging.getLogger("qt-cyclonedx-generator")
+ log_object.setLevel(logging.INFO)
+ return log_object
+
+
+log = setup_log()
+
+
+def import_tomllib():
+ """Import the TOML library with fallback options for different Python versions.
+
+ Stock toml lib is available on Python 3.11+,
+ if not found try the tomli library, and if that isn't found
+ try to use the pip vendored tomli library.
+
+ Returns:
+ Tuple of (tomllib_module, success_flag)
+ """
+ if sys.version_info >= (3, 11):
+ import tomllib as tomllib_local
+
+ return tomllib_local, True
+ else:
+ try:
+ import tomli as tomllib_local
+
+ return tomllib_local, True
+ except ModuleNotFoundError:
+ try: # noinspection PyProtectedMember
+ import pip._vendor.tomli as tomllib_local
+
+ return tomllib_local, True
+ except ModuleNotFoundError:
+ return None, False
+
+
+tomllib, tomllib_available = import_tomllib()
+
+
+def check_package_dependencies() -> None:
+ """Check if required package dependencies are available."""
+ missing_packages = []
+
+ if not tomllib_available:
+ missing_packages.append("tomli")
+
+ if importlib.util.find_spec("cyclonedx") is None:
+ missing_packages.append("cyclonedx")
+
+ if importlib.util.find_spec("packageurl") is None:
+ missing_packages.append("packageurl")
+
+ if missing_packages:
+ log.error(f"Missing required dependencies: {', '.join(missing_packages)}")
+ log.error(f"Please install with: `pip install {' '.join(missing_packages)}`")
+ sys.exit(1)
+
+
+check_package_dependencies()
+
+import argparse
+from datetime import datetime, timezone
+from license_expression import get_spdx_licensing
+from pathlib import Path
+from packageurl import PackageURL
+from typing import (
+ TYPE_CHECKING,
+ Optional,
+ Any,
+ TypedDict,
+ Union,
+ cast,
+ Callable,
+)
+from uuid import UUID
+
+from cyclonedx.factory.license import LicenseFactory
+from cyclonedx.exception import MissingOptionalDependencyException
+from cyclonedx.model import (
+ XsUri,
+ ExternalReference,
+ ExternalReferenceType,
+ Property,
+ AttachedText,
+)
+from cyclonedx.model.bom import Bom
+from cyclonedx.model.component import Component, ComponentType
+from cyclonedx.model.contact import OrganizationalEntity, OrganizationalContact
+from cyclonedx.model.license import LicenseAcknowledgement, LicenseExpression
+from cyclonedx.model.lifecycle import PredefinedLifecycle, LifecyclePhase
+from cyclonedx.output.json import JsonV1Dot6
+from cyclonedx.schema import SchemaVersion
+from cyclonedx.validation.json import JsonStrictValidator
+
+if TYPE_CHECKING:
+ from cyclonedx.output.json import Json as JsonOutputter
+
+
+class TomlRequiredFieldError(Exception):
+ pass
+
+
+class MissingTomlFileError(Exception):
+ pass
+
+
+class SBOMWriteFailedError(Exception):
+ pass
+
+
+class ValidationFailedError(Exception):
+ pass
+
+
+class ValidationDependencyMissingError(Exception):
+ pass
+
+
+# TypedDict definitions for TOML data structures
+class CyclonePropertyDict(TypedDict):
+ name: str
+ value: str
+
+
+class LicenseDict(TypedDict):
+ license_id: str
+ text: str
+
+
+class RootComponentDict(TypedDict):
+ name: str
+ spdx_id: str
+ # Optional fields (since TypedDict doesn't support NotRequired in Python 3.9)
+ # We'll use .get() to access these safely
+ version: str
+ description: str
+ download_location: str
+ build_date: str
+ supplier: str
+ supplier_url: str
+ serial_number_uuid: str
+
+
+class ComponentDict(TypedDict):
+ name: str
+ spdx_id: str
+ # Optional fields
+ version: str
+ component_type: str
+ copyright: str
+ external_bom_link: str
+ supplier: str
+ purl_list: list[str]
+ cpe_list: list[str]
+ properties: list[CyclonePropertyDict]
+ dependencies: list[str]
+
+
+class BuildToolDict(TypedDict):
+ # Optional fields
+ name: str
+ component_type: str
+ version: str
+ description: str
+
+
+class TomlDataDict(TypedDict):
+ root_component: RootComponentDict
+ # Optional fields
+ project_build_tools: list[BuildToolDict]
+ components: list[ComponentDict]
+ licenses: list[LicenseDict]
+
+
+# Type alias for licenses dictionary
+LicensesDict = dict[str, LicenseDict]
+
+
+def validate_required_field(
+ data: Union[dict[str, Any], TomlDataDict, ComponentDict, RootComponentDict],
+ field: str,
+ context: str,
+) -> Any:
+ """Validate that a required field exists in the data dictionary.
+
+ Args:
+ data: The dictionary to check
+ field: The required field name
+ context: Description of where this validation is happening (for error messages)
+
+ Returns:
+ The value of the required field
+
+ Raises:
+ TomlRequiredFieldError: If the required field is missing
+ """
+ if field not in data:
+ raise TomlRequiredFieldError(f"Missing required field '{field}' in {context}")
+ return data[field]
+
+
+def _parse_build_date(date_str: str) -> datetime:
+ """Parse build date string, handling Python 3.9+ compatibility for Z suffix."""
+ override_to_utc = False
+ if sys.version_info < (3, 11) and date_str.endswith("Z"):
+ # Remove the Z at the end to support Python 3.9+, because
+ # datetime.fromisoformat learned to parse the Z in 3.11 only.
+ # In that case we need to override the timezone to be UTC manually.
+ date_str = date_str[:-1]
+ override_to_utc = True
+
+ build_date = datetime.fromisoformat(date_str)
+ if override_to_utc:
+ build_date = build_date.replace(tzinfo=timezone.utc)
+ return build_date
+
+
+def assign_optional_field(
+ source_data: Union[
+ dict[str, Any], TomlDataDict, ComponentDict, RootComponentDict, BuildToolDict
+ ],
+ target_args: dict[str, Any],
+ source_field: str,
+ target_field: Optional[str] = None,
+ transform: Optional[Callable[[Any], Any]] = None,
+) -> None:
+ """Assign an optional field from source data to target args if it exists.
+
+ Args:
+ source_data: The source dictionary to read from
+ target_args: The target dictionary to write to
+ source_field: The field name in the source data
+ target_field: The field name in the target args (defaults to source_field if None)
+ transform: Optional function to transform the value before assignment
+ """
+ if target_field is None:
+ target_field = source_field
+
+ if value := source_data.get(source_field):
+ if transform:
+ value = transform(value)
+ # Only assign if the transformed value is not None
+ if value is not None:
+ target_args[target_field] = value
+
+
+def main() -> None:
+ parser = get_cli_parser()
+ args = get_cli_args(parser)
+
+ handle_verbose_logging(args.verbose)
+
+ try:
+ create_cyclone_dx_sbom(
+ args.input_path,
+ args.output_path,
+ args.validate_json,
+ args.validate_json_required,
+ )
+ except (
+ TomlRequiredFieldError,
+ MissingTomlFileError,
+ SBOMWriteFailedError,
+ ValidationFailedError,
+ ValidationDependencyMissingError,
+ ) as e:
+ log.error(str(e))
+ sys.exit(1)
+
+
+def get_cli_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter,
+ description="""
+Generates a CycloneDX v1.6 SBOM using intermediate information from a toml file created by Qt's
+SBOM generator.
+
+Example usage:
+python qt_cyclonedx_generator.py --input-path /qt/qtbase.cdx.toml --output-path /qt/qtbase.cdx.json
+""",
+ )
+ parser.add_argument(
+ "-i",
+ "--input-path",
+ type=Path,
+ help="Path to the intermediate toml file.",
+ required=True,
+ )
+ parser.add_argument(
+ "-o",
+ "--output-path",
+ type=Path,
+ help="Path to the CycloneDX output file.",
+ required=True,
+ )
+ parser.add_argument(
+ "--validate-json",
+ action="store_true",
+ help="Validate the generated CycloneDX JSON output against the CycloneDX schema.",
+ )
+ parser.add_argument(
+ "--validate-json-required",
+ action="store_true",
+ help="Error out if validation dependencies are missing.",
+ )
+ parser.add_argument(
+ "--verbose",
+ action="store_true",
+ help="Enable verbose logging output.",
+ )
+
+ return parser
+
+
+def get_cli_args(parser: argparse.ArgumentParser) -> argparse.Namespace:
+ args = parser.parse_args(args=None if sys.argv[1:] else ["--help"])
+ return args
+
+
+def handle_verbose_logging(verbose: bool) -> None:
+ if verbose:
+ log.setLevel(logging.DEBUG)
+
+
+# Reads a toml file and generates a CycloneDX SBOM file.
+def create_cyclone_dx_sbom(
+ input_path: Path,
+ output_path: Path,
+ validate_json=False,
+ validate_json_required=False,
+) -> None:
+ if not input_path or not input_path.exists():
+ raise MissingTomlFileError(
+ f"Failed to read toml file. Input file '{input_path}' does not exist."
+ )
+
+ cydx_bom = get_cydx_bom()
+ toml_data = read_toml_file(input_path)
+ process_toml(toml_data, cydx_bom)
+ json = write_cydx_sbom(cydx_bom, output_path)
+ if validate_json:
+ validate_cydx_bom(json, validate_json_required)
+
+
+def read_toml_file(input_path: Path) -> TomlDataDict:
+ log.debug(f"Reading toml file from '{input_path}'")
+ with open(input_path, "rb") as f:
+ # Manually read and decode the file contents, because older
+ # tomli versions vendored in pip don't handle this properly.
+ buffer = f.read()
+ toml_str = buffer.decode()
+ return cast(TomlDataDict, cast(Any, tomllib).loads(toml_str))
+
+
+# Serializes the CycloneDX BOM to JSON and writes it to the output path.
+def write_cydx_sbom(cydx_bom: Bom, output_path: Path) -> str:
+ json_outputter: JsonOutputter = JsonV1Dot6(cydx_bom)
+ log.debug(f"Serializing CycloneDX BOM to JSON at '{output_path}'")
+ serialized_json = json_outputter.output_as_string(indent=2)
+
+ try:
+ with open(output_path, "w") as f:
+ f.write(serialized_json)
+ except (OSError, IOError) as e:
+ raise SBOMWriteFailedError(f"Failed to write SBOM to '{output_path}': {e}")
+
+ return serialized_json
+
+
+def validate_cydx_bom(serialized_json: str, validate_json_required: bool) -> None:
+ log.debug("Validating CycloneDX JSON output against schema v1.6")
+ json_validator = JsonStrictValidator(SchemaVersion.V1_6)
+ try:
+ validation_errors = json_validator.validate_str(serialized_json)
+ if validation_errors:
+ raise ValidationFailedError(f"JSON validation failed: {validation_errors}")
+ except MissingOptionalDependencyException as error:
+ missing_dep_message = (
+ f"JSON-validation was failed due to missing Python dependency: {error}"
+ )
+ if validate_json_required:
+ raise ValidationDependencyMissingError(missing_dep_message)
+ else:
+ log.warning(missing_dep_message)
+
+
+def get_cydx_bom() -> Bom:
+ bom = Bom()
+ return bom
+
+
+# Reads toml data created by Qt's SBOM generator and populates the CycloneDX BOM.
+# At minimum, expects a root component dict, and a list of components.
+def process_toml(toml_data: TomlDataDict, cydx_bom: Bom) -> None:
+ components: dict[str, Component] = {}
+
+ log.debug("Processing TOML data.")
+
+ root_component_object, main_supplier, project_spdx_id = handle_root_component(
+ cydx_bom, toml_data
+ )
+ components[project_spdx_id] = root_component_object
+
+ if "project_build_tools" in toml_data:
+ project_build_tools = toml_data["project_build_tools"]
+ handle_project_build_tools(cydx_bom, project_build_tools)
+
+ external_components_spdx_ids = set()
+ components_toml = []
+ license_factory = LicenseFactory()
+
+ # Create one component per CMake target (in spdx it would be a spdx package).
+ if "components" in toml_data:
+ components_toml = toml_data["components"]
+
+ components_len = len(components_toml)
+ log.debug(f"Processing {components_len} components from TOML data.")
+
+ licenses_dict: LicensesDict = {}
+ if "licenses" in toml_data:
+ licenses_toml = toml_data["licenses"]
+ for license_entry in licenses_toml:
+ license_id = license_entry["license_id"]
+ licenses_dict[license_id] = license_entry
+
+ for component_toml in components_toml:
+ process_component(
+ cydx_bom,
+ component_toml,
+ licenses_dict,
+ components,
+ external_components_spdx_ids,
+ main_supplier,
+ license_factory,
+ )
+
+ # Register dependencies for each component.
+ log.debug("Processing component dependencies.")
+ for component_toml in components_toml:
+ handle_component_dependencies(
+ cydx_bom,
+ component_toml,
+ components,
+ external_components_spdx_ids,
+ root_component_object,
+ )
+
+
+# Creates the root component of the BOM.
+# The CycloneDX root component is mapped to the Qt spdx project package e.g. qtbase.
+def handle_root_component(
+ cydx_bom: Bom, toml_data: TomlDataDict
+) -> tuple[Component, Optional[OrganizationalEntity], str]:
+ root_components_args: dict[str, Any] = {
+ "type": ComponentType.FRAMEWORK,
+ }
+
+ root_component = validate_required_field(toml_data, "root_component", "TOML data")
+ error_context = "root_component"
+ project_name = validate_required_field(root_component, "name", error_context)
+ root_components_args["name"] = project_name
+
+ project_spdx_id = validate_required_field(root_component, "spdx_id", error_context)
+ root_components_args["bom_ref"] = project_spdx_id
+
+ # Process optional fields
+ assign_optional_field(root_component, root_components_args, "version")
+ assign_optional_field(root_component, root_components_args, "description")
+
+ # Process optional fields with transformations
+ assign_optional_field(
+ root_component,
+ root_components_args,
+ source_field="download_location",
+ target_field="external_references",
+ transform=lambda url: [
+ ExternalReference(
+ type=ExternalReferenceType.DISTRIBUTION, # pyright: ignore [reportCallIssue]
+ url=XsUri(url), # pyright: ignore [reportCallIssue]
+ )
+ ],
+ )
+
+ if project_build_date_str := root_component.get("build_date"):
+ project_build_date = _parse_build_date(project_build_date_str)
+ cydx_bom.metadata.timestamp = project_build_date # pyright: ignore [reportAttributeAccessIssue]
+
+ if serial_number_uuid := root_component.get("serial_number_uuid"):
+ cydx_bom.serial_number = UUID(serial_number_uuid) # pyright: ignore [reportAttributeAccessIssue]
+
+ project_supplier = root_component.get("supplier")
+ project_supplier_url = root_component.get("supplier_url")
+
+ # TODO: This is currently not supplied by the build system API.
+ project_supplier_email = root_component.get("supplier_email")
+
+ main_supplier = create_organization_entity(project_supplier, project_supplier_url)
+ main_author = create_organization_contact(project_supplier, project_supplier_email)
+
+ if main_supplier:
+ cydx_bom.metadata.manufacturer = main_supplier # pyright: ignore [reportAttributeAccessIssue]
+ cydx_bom.metadata.supplier = main_supplier # pyright: ignore [reportAttributeAccessIssue]
+
+ # FOSSA complains if author is not set.
+ cydx_bom.metadata.authors = [main_author] # pyright: ignore [reportAttributeAccessIssue]
+
+ root_components_args["supplier"] = main_supplier
+ root_components_args["manufacturer"] = main_supplier
+
+ root_component_object = Component(**root_components_args)
+ cydx_bom.metadata.component = root_component_object # pyright: ignore [reportAttributeAccessIssue]
+
+ cydx_bom.metadata.lifecycles = [PredefinedLifecycle(phase=LifecyclePhase.BUILD)] # pyright: ignore [reportAttributeAccessIssue, reportCallIssue]
+
+ root_component_name_str = f"{root_component_object.name}" # pyright: ignore [reportAttributeAccessIssue]
+ root_component_version_str = f"(version {root_component_object.version})" # pyright: ignore [reportAttributeAccessIssue]
+ log.debug(
+ f"Created root component of the BOM. "
+ f"{root_component_name_str} ${root_component_version_str}"
+ )
+
+ return root_component_object, main_supplier, project_spdx_id
+
+
+# Handles project build tools and adds them as components to the BOM metadata tools section.
+# This is for tools like CMake, Ninja, etc.
+# Unclear yet if compilers and linkers should also go here.
+def handle_project_build_tools(
+ cydx_bom: Bom, project_build_tools_toml: list[BuildToolDict]
+) -> None:
+ log.debug("Processing project build tools.")
+
+ for build_tool_toml in project_build_tools_toml:
+ component_args = {}
+
+ assign_optional_field(build_tool_toml, component_args, source_field="name")
+ assign_optional_field(
+ build_tool_toml,
+ component_args,
+ source_field="component_type",
+ target_field="type",
+ transform=get_component_type_for_str,
+ )
+ assign_optional_field(build_tool_toml, component_args, "version")
+ assign_optional_field(build_tool_toml, component_args, "description")
+
+ component = Component(**component_args)
+ log.debug(
+ f"Created build tool component '{component.name} (version {component.version})'." # pyright: ignore [reportAttributeAccessIssue]
+ )
+ cydx_bom.metadata.tools.components.add(component) # pyright: ignore [reportAttributeAccessIssue]
+
+
+# Creates a CycloneDX component from the toml component data and adds it to the BOM.
+# The SPDX equivalent would be a SPDX package backed by a CMake target.
+# We mostly reuse spdx ids as bom-refs, rather than generating new ones. Makes it
+# easier to cross-check with the original SPDX data. And it's not like CycloneDX forbids it.
+def process_component(
+ cydx_bom: Bom,
+ component_toml: ComponentDict,
+ licenses_dict: LicensesDict,
+ components: dict[str, Component],
+ external_components_spdx_ids: set[str],
+ main_supplier: Optional[OrganizationalEntity],
+ license_factory: LicenseFactory,
+) -> None:
+ component_args = {}
+ properties = []
+
+ error_context = "component"
+ component_name = validate_required_field(component_toml, "name", error_context)
+ component_args["name"] = component_name
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ "version",
+ transform=lambda v: v if v != "unknown" else None,
+ )
+
+ # A spdx id maps to a bom-ref in CycloneDX.
+ spdx_id = validate_required_field(component_toml, "spdx_id", error_context)
+ component_args["bom_ref"] = spdx_id
+
+ # Some components might be external to the current BOM.
+ # For example for the QtSvg module, QtCore is external, because it's built in qtbase,
+ # not qtsvg. To be able to declare a dependency on QtCore, we still need to add it as a
+ # component. But we add a BOM external reference link to it, signalling that the full
+ # component information is in a different BOM.
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="external_bom_link",
+ target_field="external_references",
+ transform=lambda link: [
+ ExternalReference(
+ type=ExternalReferenceType.BOM, # pyright: ignore [reportCallIssue]
+ url=XsUri(link), # pyright: ignore [reportCallIssue]
+ )
+ ],
+ )
+ if component_toml.get("external_bom_link"):
+ external_components_spdx_ids.add(spdx_id)
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="component_type",
+ target_field="type",
+ transform=get_component_type_for_str,
+ )
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="copyright",
+ target_field="copyright",
+ )
+
+ handle_component_licenses(
+ component_toml, licenses_dict, license_factory, component_args
+ )
+
+ purl_list = component_toml.get("purl_list", [])
+ cpe_list = component_toml.get("cpe_list", [])
+ cpe_purl_args, extra_cpe_purl_properties = get_purl_and_cpe_component_args(
+ purl_list, cpe_list
+ )
+ if extra_cpe_purl_properties:
+ properties.extend(extra_cpe_purl_properties)
+
+ if supplier_name := component_toml.get("supplier"):
+ # If the specified supplier is the same as the main supplier, reuse it, because it has a
+ # url.
+ if main_supplier and supplier_name == main_supplier.name: # pyright: ignore [reportAttributeAccessIssue]
+ component_args["supplier"] = main_supplier
+ else:
+ component_supplier = create_organization_entity(supplier_name)
+ component_args["supplier"] = component_supplier
+
+ # Properties are extra vendor-specific information that are not part of the official spec.
+ if properties_list := component_toml.get("properties"):
+ for prop in properties_list:
+ properties.append(
+ Property(
+ name=prop["name"], # pyright: ignore [reportCallIssue]
+ value=prop["value"], # pyright: ignore [reportCallIssue]
+ )
+ )
+ if properties:
+ component_args["properties"] = properties
+
+ component = Component(**component_args, **cpe_purl_args)
+
+ log.debug(f"Created component '{component.name} (version {component.version})'.") # pyright: ignore [reportAttributeAccessIssue]
+
+ cydx_bom.components.add(component) # pyright: ignore [reportAttributeAccessIssue]
+ components[spdx_id] = component
+
+
+# Creates dependencies between components in the BOM, as well as between the root component
+# and non-external components that are part of the current BOM.
+def handle_component_dependencies(
+ cydx_bom: Bom,
+ component_toml: ComponentDict,
+ components: dict[str, Component],
+ external_components_spdx_ids: set[str],
+ root_component_object: Component,
+) -> None:
+ component_id = component_toml["spdx_id"]
+ component_object = components.get(component_id)
+
+ # If the component is not external, register it as a dependency of the root component,
+ # aka the root component "contains" the non-external component.
+ if component_id not in external_components_spdx_ids:
+ cydx_bom.register_dependency(root_component_object, [component_object]) # pyright: ignore [reportAttributeAccessIssue, reportArgumentType]
+
+ # Register dependencies declared in the toml.
+ # CycloneDX does not support different dependency types, like SPDX does.
+ if dependencies := component_toml.get("dependencies"):
+ for dependency_spdx_id in dependencies:
+ if dependency_spdx_id not in components:
+ log.warning(
+ f"Component {component_id} has a dependency on unknown component {dependency_spdx_id}, skipping."
+ )
+ continue
+ dep_component = components[dependency_spdx_id]
+ cydx_bom.register_dependency(component_object, [dep_component]) # pyright: ignore [reportAttributeAccessIssue, reportArgumentType]
+ log.debug(
+ f"Created dependency from '{component_id}' to '{dependency_spdx_id}'."
+ )
+
+
+# Handles PURL and CPE entries for a component.
+# The spec says that a component can have exactly one PURL. Qt generates several, because it's
+# unclear what will end up being useful for tooling. The other PURLs are added as extra
+# component properties. Same for CPEs.
+# The field names were picked to match the names generated by the python spdx to cyclonedx converter.
+def get_purl_and_cpe_component_args(
+ purl_list: list[str],
+ cpe_list: list[str],
+) -> tuple[dict[str, Any], list[Property]]:
+ component_args = {}
+ properties = []
+
+ purl_objects = [PackageURL.from_string(purl) for purl in purl_list if purl]
+
+ if len(purl_objects) > 0:
+ # First PURL will be the main one.
+ purl = purl_objects.pop(0)
+ component_args["purl"] = purl
+
+ # Rest will be properties.
+ for purl_entry in purl_objects:
+ properties.append(
+ Property(
+ name="spdx:external-reference:package-manager:purl", # pyright: ignore [reportCallIssue]
+ value=str(purl_entry), # pyright: ignore [reportCallIssue]
+ )
+ )
+
+ if len(cpe_list) > 0:
+ # First CPE will be the main one.
+ cpe = cpe_list.pop(0)
+ component_args["cpe"] = cpe
+
+ # Rest will be properties.
+ for cpe_entry in cpe_list:
+ properties.append(
+ Property(
+ name="spdx:external-reference:security:cpe23", # pyright: ignore [reportCallIssue]
+ value=str(cpe_entry), # pyright: ignore [reportCallIssue]
+ )
+ )
+
+ return component_args, properties
+
+
+# Handles licenses for a component.
+def handle_component_licenses(
+ component_toml: ComponentDict,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ component_args: dict[str, Any],
+) -> None:
+ def transformer(license_expression: str) -> Optional[list[LicenseExpression]]:
+ acknowledgement = LicenseAcknowledgement.CONCLUDED
+ return convert_license_expression_license_objects(
+ license_expression, licenses_dict, license_factory, acknowledgement
+ )
+
+ assign_optional_field(
+ component_toml,
+ component_args,
+ source_field="license_concluded_expression",
+ target_field="licenses",
+ transform=transformer,
+ )
+
+
+# Transforms a spdx license expression into CycloneDX license objects.
+def convert_license_expression_license_objects(
+ license_expression: str,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> Optional[list[LicenseExpression]]:
+ if "LicenseRef-" in license_expression:
+ return handle_custom_license_ref_expression(
+ license_expression, licenses_dict, license_factory, acknowledgement
+ )
+ else:
+ return handle_standard_license_ref_expression(
+ license_expression, license_factory, acknowledgement
+ )
+
+
+# CycloneDX v1.6 doesn't support an expression that contains LicenseRef- in it.
+# CycloneDX v1.7 seemingly does.
+# https://github.com/CycloneDX/specification/issues/454
+# https://github.com/CycloneDX/specification/issues/554
+# https://github.com/CycloneDX/specification/pull/582
+#
+# Split LicenseRef-* expressions into separate licenses, by which we lose the expression structure,
+# but at least preserve the license information.
+#
+# TODO: Improve this when CycloneDX v1.7 generation is added.
+def handle_custom_license_ref_expression(
+ license_expression: str,
+ licenses_dict: LicensesDict,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> list[LicenseExpression]:
+ license_objects = []
+ licensing = get_spdx_licensing()
+ symbols = licensing.license_symbols(license_expression)
+
+ for symbol in symbols:
+ license_text = None
+ # This can be either a known public spdx id, in which case we won't have text for.
+ # Or it can be a LicenseRef-* for which we have license text in the licenses_dict.
+ maybe_license_id = symbol.key # pyright: ignore [reportAttributeAccessIssue]
+
+ if maybe_license_id in licenses_dict:
+ # Get the text for the LicenseRef-* entry.
+ maybe_license_text = licenses_dict[maybe_license_id].get("text")
+ license_text = AttachedText(content=maybe_license_text) # pyright: ignore [reportCallIssue]
+
+ license_obj = license_factory.make_from_string(
+ maybe_license_id,
+ license_text=license_text,
+ license_acknowledgement=acknowledgement,
+ )
+ license_objects.append(license_obj)
+
+ return license_objects
+
+
+# Transforms a spdx license expression without LicenseRef-* into a CycloneDX license object.
+def handle_standard_license_ref_expression(
+ license_expression: str,
+ license_factory: LicenseFactory,
+ acknowledgement: LicenseAcknowledgement,
+) -> list[LicenseExpression]:
+ license_obj = license_factory.make_with_expression(
+ expression=license_expression, acknowledgement=acknowledgement
+ )
+ return [license_obj]
+
+
+# Transform from enum string value to actual enum.
+def get_component_type_for_str(component_type_str: str) -> ComponentType:
+ return ComponentType(component_type_str)
+
+
+# Creates an OrganizationalEntity for a supplier/manufacturer.
+def create_organization_entity(
+ supplier_name: Optional[str] = None, supplier_url: Optional[str] = None
+) -> Optional[OrganizationalEntity]:
+ args = {}
+ if supplier_name:
+ args["name"] = supplier_name
+ if supplier_url:
+ args["urls"] = [XsUri(supplier_url)] # pyright: ignore [reportCallIssue]
+
+ # Check if args has at least one entry.
+ if len(args) > 0:
+ return OrganizationalEntity(**args)
+
+ return None
+
+
+# Creates an OrganizationalContact for a supplier/manufacturer.
+def create_organization_contact(
+ contact_name: Optional[str] = None, contact_email: Optional[str] = None
+) -> Optional[OrganizationalContact]:
+ args = {}
+ if contact_name:
+ args["name"] = contact_name
+ if contact_email:
+ args["email"] = contact_email # pyright: ignore [reportCallIssue]
+
+ # Check if args has at least one entry.
+ if len(args) > 0:
+ return OrganizationalContact(**args)
+
+ return None
+
+
+if __name__ == "__main__":
+ main()
diff --git a/util/sbom/cyclonedx/uv.lock b/util/sbom/cyclonedx/uv.lock
new file mode 100644
index 00000000000..0b16a99e481
--- /dev/null
+++ b/util/sbom/cyclonedx/uv.lock
@@ -0,0 +1,625 @@
+version = 1
+revision = 3
+requires-python = ">=3.9"
+resolution-markers = [
+ "python_full_version >= '3.10'",
+ "python_full_version < '3.10'",
+]
+
+[[package]]
+name = "arrow"
+version = "1.4.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "python-dateutil" },
+ { name = "tzdata" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
+]
+
+[[package]]
+name = "attrs"
+version = "25.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" },
+]
+
+[[package]]
+name = "boolean-py"
+version = "5.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/c4/cf/85379f13b76f3a69bca86b60237978af17d6aa0bc5998978c3b8cf05abb2/boolean_py-5.0.tar.gz", hash = "sha256:60cbc4bad079753721d32649545505362c754e121570ada4658b852a3a318d95", size = 37047, upload-time = "2025-04-03T10:39:49.734Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e5/ca/78d423b324b8d77900030fa59c4aa9054261ef0925631cd2501dd015b7b7/boolean_py-5.0-py3-none-any.whl", hash = "sha256:ef28a70bd43115208441b53a045d1549e2f0ec6e3d08a9d142cbc41c1938e8d9", size = 26577, upload-time = "2025-04-03T10:39:48.449Z" },
+]
+
+[[package]]
+name = "cyclonedx-python-lib"
+version = "11.3.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "license-expression" },
+ { name = "packageurl-python" },
+ { name = "py-serializable" },
+ { name = "sortedcontainers" },
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/23/3b/c130406ae35bd264be7f816afbb01f95ccc4a63a5eeadfea5249d110efb2/cyclonedx_python_lib-11.3.0.tar.gz", hash = "sha256:ee3135a59ead90397339ea585b341a7ea9c53e734ad13f48a9865cfcf0f1139a", size = 1046415, upload-time = "2025-10-22T11:03:04.883Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/28/3bebcfe7f87e485164fba1dca08b6307bdbee6cc45d6bf57343552346f85/cyclonedx_python_lib-11.3.0-py3-none-any.whl", hash = "sha256:08094495d0cbbaddbe605f60f6e48ae98d29263386d7926df83ffe9bdea55f78", size = 383140, upload-time = "2025-10-22T11:03:03.102Z" },
+]
+
+[package.optional-dependencies]
+json-validation = [
+ { name = "jsonschema", extra = ["format-nongpl"] },
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" },
+]
+
+[[package]]
+name = "fqdn"
+version = "1.5.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
+]
+
+[[package]]
+name = "idna"
+version = "3.11"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
+]
+
+[[package]]
+name = "isoduration"
+version = "20.11.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "arrow" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
+]
+
+[[package]]
+name = "jsonpointer"
+version = "3.0.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.25.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "attrs" },
+ { name = "jsonschema-specifications" },
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
+]
+
+[package.optional-dependencies]
+format-nongpl = [
+ { name = "fqdn" },
+ { name = "idna" },
+ { name = "isoduration" },
+ { name = "jsonpointer" },
+ { name = "rfc3339-validator" },
+ { name = "rfc3986-validator" },
+ { name = "rfc3987-syntax" },
+ { name = "uri-template" },
+ { name = "webcolors" },
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.9.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "referencing", version = "0.36.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
+ { name = "referencing", version = "0.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
+]
+
+[[package]]
+name = "lark"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/1d/37/a13baf0135f348af608c667633cbe5d13aa2c5c15a56ae9ad3e6cba45ae3/lark-1.3.0.tar.gz", hash = "sha256:9a3839d0ca5e1faf7cfa3460e420e859b66bcbde05b634e73c369c8244c5fa48", size = 259551, upload-time = "2025-09-22T13:45:05.072Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a8/3e/1c6b43277de64fc3c0333b0e72ab7b52ddaaea205210d60d9b9f83c3d0c7/lark-1.3.0-py3-none-any.whl", hash = "sha256:80661f261fb2584a9828a097a2432efd575af27d20be0fd35d17f0fe37253831", size = 113002, upload-time = "2025-09-22T13:45:03.747Z" },
+]
+
+[[package]]
+name = "license-expression"
+version = "30.4.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "boolean-py" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/40/71/d89bb0e71b1415453980fd32315f2a037aad9f7f70f695c7cec7035feb13/license_expression-30.4.4.tar.gz", hash = "sha256:73448f0aacd8d0808895bdc4b2c8e01a8d67646e4188f887375398c761f340fd", size = 186402, upload-time = "2025-07-22T11:13:32.17Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/af/40/791891d4c0c4dab4c5e187c17261cedc26285fd41541577f900470a45a4d/license_expression-30.4.4-py3-none-any.whl", hash = "sha256:421788fdcadb41f049d2dc934ce666626265aeccefddd25e162a26f23bcbf8a4", size = 120615, upload-time = "2025-07-22T11:13:31.217Z" },
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.9.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
+]
+
+[[package]]
+name = "packageurl-python"
+version = "0.17.5"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/3a/f0/de0ac00a4484c0d87b71e3d9985518278d89797fa725e90abd3453bccb42/packageurl_python-0.17.5.tar.gz", hash = "sha256:a7be3f3ba70d705f738ace9bf6124f31920245a49fa69d4b416da7037dd2de61", size = 43832, upload-time = "2025-08-06T14:08:20.235Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/be/78/9dbb7d2ef240d20caf6f79c0f66866737c9d0959601fd783ff635d1d019d/packageurl_python-0.17.5-py3-none-any.whl", hash = "sha256:f0e55452ab37b5c192c443de1458e3f3b4d8ac27f747df6e8c48adeab081d321", size = 30544, upload-time = "2025-08-06T14:08:19.055Z" },
+]
+
+[[package]]
+name = "py-serializable"
+version = "2.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "defusedxml" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/73/21/d250cfca8ff30c2e5a7447bc13861541126ce9bd4426cd5d0c9f08b5547d/py_serializable-2.1.0.tar.gz", hash = "sha256:9d5db56154a867a9b897c0163b33a793c804c80cee984116d02d49e4578fc103", size = 52368, upload-time = "2025-07-21T09:56:48.07Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9b/bf/7595e817906a29453ba4d99394e781b6fabe55d21f3c15d240f85dd06bb1/py_serializable-2.1.0-py3-none-any.whl", hash = "sha256:b56d5d686b5a03ba4f4db5e769dc32336e142fc3bd4d68a8c25579ebb0a67304", size = 23045, upload-time = "2025-07-21T09:56:46.848Z" },
+]
+
+[[package]]
+name = "pyright"
+version = "1.1.406"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "nodeenv" },
+ { name = "typing-extensions" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/f7/16/6b4fbdd1fef59a0292cbb99f790b44983e390321eccbc5921b4d161da5d1/pyright-1.1.406.tar.gz", hash = "sha256:c4872bc58c9643dac09e8a2e74d472c62036910b3bd37a32813989ef7576ea2c", size = 4113151, upload-time = "2025-10-02T01:04:45.488Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/f6/a2/e309afbb459f50507103793aaef85ca4348b66814c86bc73908bdeb66d12/pyright-1.1.406-py3-none-any.whl", hash = "sha256:1d81fb43c2407bf566e97e57abb01c811973fdb21b2df8df59f870f688bdca71", size = 5980982, upload-time = "2025-10-02T01:04:43.137Z" },
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
+]
+
+[[package]]
+name = "qt-cyclonedx-generator"
+version = "0.1.0"
+source = { editable = "." }
+dependencies = [
+ { name = "cyclonedx-python-lib", extra = ["json-validation"] },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pyright" },
+ { name = "ruff" },
+ { name = "setuptools" },
+ { name = "tomli" },
+]
+
+[package.metadata]
+requires-dist = [
+ { name = "cyclonedx-python-lib", extras = ["json-validation"], specifier = ">=10.0.0" },
+ { name = "tomli", marker = "python_full_version < '3.11'" },
+]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pyright", specifier = ">=1.1.406" },
+ { name = "ruff", specifier = ">=0.11.2" },
+ { name = "setuptools", specifier = ">=65" },
+ { name = "tomli", specifier = ">=2.3.0" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version < '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version < '3.10'" },
+ { name = "rpds-py", marker = "python_full_version < '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version < '3.10'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
+]
+
+[[package]]
+name = "referencing"
+version = "0.37.0"
+source = { registry = "https://pypi.org/simple" }
+resolution-markers = [
+ "python_full_version >= '3.10'",
+]
+dependencies = [
+ { name = "attrs", marker = "python_full_version >= '3.10'" },
+ { name = "rpds-py", marker = "python_full_version >= '3.10'" },
+ { name = "typing-extensions", marker = "python_full_version >= '3.10' and python_full_version < '3.13'" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
+]
+
+[[package]]
+name = "rfc3339-validator"
+version = "0.1.4"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "six" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
+]
+
+[[package]]
+name = "rfc3986-validator"
+version = "0.1.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
+]
+
+[[package]]
+name = "rfc3987-syntax"
+version = "1.1.0"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "lark" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" },
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.27.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" },
+ { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" },
+ { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" },
+ { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" },
+ { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" },
+ { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" },
+ { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" },
+ { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" },
+ { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" },
+ { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" },
+ { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" },
+ { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" },
+ { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" },
+ { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" },
+ { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" },
+ { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" },
+ { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" },
+ { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" },
+ { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" },
+ { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" },
+ { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" },
+ { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" },
+ { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" },
+ { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" },
+ { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" },
+ { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" },
+ { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" },
+ { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" },
+ { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" },
+ { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" },
+ { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" },
+ { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" },
+ { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" },
+ { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" },
+ { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" },
+ { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" },
+ { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" },
+ { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" },
+ { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" },
+ { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" },
+ { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" },
+ { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" },
+ { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" },
+ { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" },
+ { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" },
+ { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" },
+ { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" },
+ { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" },
+ { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" },
+ { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" },
+ { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" },
+ { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" },
+ { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" },
+ { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" },
+ { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" },
+ { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" },
+ { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" },
+ { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" },
+ { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" },
+ { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" },
+ { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" },
+ { url = "https://files.pythonhosted.org/packages/7f/6c/252e83e1ce7583c81f26d1d884b2074d40a13977e1b6c9c50bbf9a7f1f5a/rpds_py-0.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c918c65ec2e42c2a78d19f18c553d77319119bf43aa9e2edf7fb78d624355527", size = 372140, upload-time = "2025-08-27T12:15:05.441Z" },
+ { url = "https://files.pythonhosted.org/packages/9d/71/949c195d927c5aeb0d0629d329a20de43a64c423a6aa53836290609ef7ec/rpds_py-0.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1fea2b1a922c47c51fd07d656324531adc787e415c8b116530a1d29c0516c62d", size = 354086, upload-time = "2025-08-27T12:15:07.404Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/02/e43e332ad8ce4f6c4342d151a471a7f2900ed1d76901da62eb3762663a71/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbf94c58e8e0cd6b6f38d8de67acae41b3a515c26169366ab58bdca4a6883bb8", size = 382117, upload-time = "2025-08-27T12:15:09.275Z" },
+ { url = "https://files.pythonhosted.org/packages/d0/05/b0fdeb5b577197ad72812bbdfb72f9a08fa1e64539cc3940b1b781cd3596/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2a8fed130ce946d5c585eddc7c8eeef0051f58ac80a8ee43bd17835c144c2cc", size = 394520, upload-time = "2025-08-27T12:15:10.727Z" },
+ { url = "https://files.pythonhosted.org/packages/67/1f/4cfef98b2349a7585181e99294fa2a13f0af06902048a5d70f431a66d0b9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:037a2361db72ee98d829bc2c5b7cc55598ae0a5e0ec1823a56ea99374cfd73c1", size = 522657, upload-time = "2025-08-27T12:15:12.613Z" },
+ { url = "https://files.pythonhosted.org/packages/44/55/ccf37ddc4c6dce7437b335088b5ca18da864b334890e2fe9aa6ddc3f79a9/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5281ed1cc1d49882f9997981c88df1a22e140ab41df19071222f7e5fc4e72125", size = 402967, upload-time = "2025-08-27T12:15:14.113Z" },
+ { url = "https://files.pythonhosted.org/packages/74/e5/5903f92e41e293b07707d5bf00ef39a0eb2af7190aff4beaf581a6591510/rpds_py-0.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd50659a069c15eef8aa3d64bbef0d69fd27bb4a50c9ab4f17f83a16cbf8905", size = 384372, upload-time = "2025-08-27T12:15:15.842Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/e3/fbb409e18aeefc01e49f5922ac63d2d914328430e295c12183ce56ebf76b/rpds_py-0.27.1-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:c4b676c4ae3921649a15d28ed10025548e9b561ded473aa413af749503c6737e", size = 401264, upload-time = "2025-08-27T12:15:17.388Z" },
+ { url = "https://files.pythonhosted.org/packages/55/79/529ad07794e05cb0f38e2f965fc5bb20853d523976719400acecc447ec9d/rpds_py-0.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:079bc583a26db831a985c5257797b2b5d3affb0386e7ff886256762f82113b5e", size = 418691, upload-time = "2025-08-27T12:15:19.144Z" },
+ { url = "https://files.pythonhosted.org/packages/33/39/6554a7fd6d9906fda2521c6d52f5d723dca123529fb719a5b5e074c15e01/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4e44099bd522cba71a2c6b97f68e19f40e7d85399de899d66cdb67b32d7cb786", size = 558989, upload-time = "2025-08-27T12:15:21.087Z" },
+ { url = "https://files.pythonhosted.org/packages/19/b2/76fa15173b6f9f445e5ef15120871b945fb8dd9044b6b8c7abe87e938416/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e202e6d4188e53c6661af813b46c37ca2c45e497fc558bacc1a7630ec2695aec", size = 589835, upload-time = "2025-08-27T12:15:22.696Z" },
+ { url = "https://files.pythonhosted.org/packages/ee/9e/5560a4b39bab780405bed8a88ee85b30178061d189558a86003548dea045/rpds_py-0.27.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f41f814b8eaa48768d1bb551591f6ba45f87ac76899453e8ccd41dba1289b04b", size = 555227, upload-time = "2025-08-27T12:15:24.278Z" },
+ { url = "https://files.pythonhosted.org/packages/52/d7/cd9c36215111aa65724c132bf709c6f35175973e90b32115dedc4ced09cb/rpds_py-0.27.1-cp39-cp39-win32.whl", hash = "sha256:9e71f5a087ead99563c11fdaceee83ee982fd39cf67601f4fd66cb386336ee52", size = 217899, upload-time = "2025-08-27T12:15:25.926Z" },
+ { url = "https://files.pythonhosted.org/packages/5b/e0/d75ab7b4dd8ba777f6b365adbdfc7614bbfe7c5f05703031dfa4b61c3d6c/rpds_py-0.27.1-cp39-cp39-win_amd64.whl", hash = "sha256:71108900c9c3c8590697244b9519017a400d9ba26a36c48381b3f64743a44aab", size = 228725, upload-time = "2025-08-27T12:15:27.398Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" },
+ { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" },
+ { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" },
+ { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" },
+ { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" },
+ { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" },
+ { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" },
+ { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" },
+ { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" },
+ { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" },
+ { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" },
+ { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" },
+ { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" },
+ { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" },
+ { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" },
+ { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" },
+ { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" },
+ { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" },
+ { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" },
+ { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" },
+ { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" },
+ { url = "https://files.pythonhosted.org/packages/4e/ea/5463cd5048a7a2fcdae308b6e96432802132c141bfb9420260142632a0f1/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa8933159edc50be265ed22b401125c9eebff3171f570258854dbce3ecd55475", size = 371778, upload-time = "2025-08-27T12:16:13.851Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/c8/f38c099db07f5114029c1467649d308543906933eebbc226d4527a5f4693/rpds_py-0.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a50431bf02583e21bf273c71b89d710e7a710ad5e39c725b14e685610555926f", size = 354394, upload-time = "2025-08-27T12:16:15.609Z" },
+ { url = "https://files.pythonhosted.org/packages/7d/79/b76f97704d9dd8ddbd76fed4c4048153a847c5d6003afe20a6b5c3339065/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78af06ddc7fe5cc0e967085a9115accee665fb912c22a3f54bad70cc65b05fe6", size = 382348, upload-time = "2025-08-27T12:16:17.251Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/3f/ef23d3c1be1b837b648a3016d5bbe7cfe711422ad110b4081c0a90ef5a53/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:70d0738ef8fee13c003b100c2fbd667ec4f133468109b3472d249231108283a3", size = 394159, upload-time = "2025-08-27T12:16:19.251Z" },
+ { url = "https://files.pythonhosted.org/packages/74/8a/9e62693af1a34fd28b1a190d463d12407bd7cf561748cb4745845d9548d3/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2f6fd8a1cea5bbe599b6e78a6e5ee08db434fc8ffea51ff201c8765679698b3", size = 522775, upload-time = "2025-08-27T12:16:20.929Z" },
+ { url = "https://files.pythonhosted.org/packages/36/0d/8d5bb122bf7a60976b54c5c99a739a3819f49f02d69df3ea2ca2aff47d5c/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8177002868d1426305bb5de1e138161c2ec9eb2d939be38291d7c431c4712df8", size = 402633, upload-time = "2025-08-27T12:16:22.548Z" },
+ { url = "https://files.pythonhosted.org/packages/0f/0e/237948c1f425e23e0cf5a566d702652a6e55c6f8fbd332a1792eb7043daf/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:008b839781d6c9bf3b6a8984d1d8e56f0ec46dc56df61fd669c49b58ae800400", size = 384867, upload-time = "2025-08-27T12:16:24.29Z" },
+ { url = "https://files.pythonhosted.org/packages/d6/0a/da0813efcd998d260cbe876d97f55b0f469ada8ba9cbc47490a132554540/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:a55b9132bb1ade6c734ddd2759c8dc132aa63687d259e725221f106b83a0e485", size = 401791, upload-time = "2025-08-27T12:16:25.954Z" },
+ { url = "https://files.pythonhosted.org/packages/51/78/c6c9e8a8aaca416a6f0d1b6b4a6ee35b88fe2c5401d02235d0a056eceed2/rpds_py-0.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a46fdec0083a26415f11d5f236b79fa1291c32aaa4a17684d82f7017a1f818b1", size = 419525, upload-time = "2025-08-27T12:16:27.659Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/69/5af37e1d71487cf6d56dd1420dc7e0c2732c1b6ff612aa7a88374061c0a8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:8a63b640a7845f2bdd232eb0d0a4a2dd939bcdd6c57e6bb134526487f3160ec5", size = 559255, upload-time = "2025-08-27T12:16:29.343Z" },
+ { url = "https://files.pythonhosted.org/packages/40/7f/8b7b136069ef7ac3960eda25d832639bdb163018a34c960ed042dd1707c8/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:7e32721e5d4922deaaf963469d795d5bde6093207c52fec719bd22e5d1bedbc4", size = 590384, upload-time = "2025-08-27T12:16:31.005Z" },
+ { url = "https://files.pythonhosted.org/packages/d8/06/c316d3f6ff03f43ccb0eba7de61376f8ec4ea850067dddfafe98274ae13c/rpds_py-0.27.1-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:2c426b99a068601b5f4623573df7a7c3d72e87533a2dd2253353a03e7502566c", size = 555959, upload-time = "2025-08-27T12:16:32.73Z" },
+ { url = "https://files.pythonhosted.org/packages/60/94/384cf54c430b9dac742bbd2ec26c23feb78ded0d43d6d78563a281aec017/rpds_py-0.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4fc9b7fe29478824361ead6e14e4f5aed570d477e06088826537e202d25fe859", size = 228784, upload-time = "2025-08-27T12:16:34.428Z" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.14.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" },
+ { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" },
+ { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" },
+ { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" },
+ { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" },
+ { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" },
+ { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" },
+ { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" },
+ { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" },
+ { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" },
+ { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" },
+ { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" },
+ { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" },
+ { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" },
+ { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" },
+ { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" },
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" },
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
+]
+
+[[package]]
+name = "tomli"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
+ { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
+ { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
+ { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
+ { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
+ { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
+ { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
+ { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
+ { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
+ { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
+ { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
+ { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
+ { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
+ { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
+ { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
+ { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
+ { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
+ { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
+ { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
+ { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
+ { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
+ { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
+ { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
+ { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
+ { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
+ { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
+ { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
+ { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
+ { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
+ { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
+ { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
+ { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
+ { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
+ { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
+ { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
+ { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
+ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.15.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.2"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" },
+]
+
+[[package]]
+name = "uri-template"
+version = "1.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
+]
+
+[[package]]
+name = "webcolors"
+version = "24.11.1"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7b/29/061ec845fb58521848f3739e466efd8250b4b7b98c1b6c5bf4d40b419b7e/webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6", size = 45064, upload-time = "2024-11-11T07:43:24.224Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/60/e8/c0e05e4684d13459f93d312077a9a2efbe04d59c393bc2b8802248c908d4/webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9", size = 14934, upload-time = "2024-11-11T07:43:22.529Z" },
+]