summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bin/qt-cmake.bat.in4
-rwxr-xr-xbin/qt-cmake.in5
-rw-r--r--src/3rdparty/libpng/ANNOUNCE52
-rw-r--r--src/3rdparty/libpng/CHANGES11
-rw-r--r--src/3rdparty/libpng/README2
-rw-r--r--src/3rdparty/libpng/libpng-manual.txt2
-rw-r--r--src/3rdparty/libpng/png.c4
-rw-r--r--src/3rdparty/libpng/png.h14
-rw-r--r--src/3rdparty/libpng/pngconf.h2
-rw-r--r--src/3rdparty/libpng/pnglibconf.h2
-rw-r--r--src/3rdparty/libpng/pngread.c51
-rw-r--r--src/3rdparty/libpng/pngrtran.c1
-rw-r--r--src/3rdparty/libpng/qt_attribution.json4
-rw-r--r--src/3rdparty/sqlite/qt_attribution.json4
-rw-r--r--src/3rdparty/sqlite/sqlite3.c58
-rw-r--r--src/3rdparty/sqlite/sqlite3.h14
-rwxr-xr-xsrc/3rdparty/sqlite/update_sqlite.sh2
-rw-r--r--src/corelib/CMakeLists.txt12
-rw-r--r--src/corelib/Qt6CoreMacros.cmake2
-rw-r--r--src/corelib/compat/removed_api.cpp14
-rw-r--r--src/corelib/configure.cmake27
-rw-r--r--src/corelib/doc/snippets/CMakeLists.txt1
-rw-r--r--src/corelib/doc/snippets/code/doc_src_properties.cpp2
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp16
-rw-r--r--src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp24
-rw-r--r--src/corelib/doc/snippets/qrangemodeladapter/main.cpp366
-rw-r--r--src/corelib/doc/src/jni.qdoc10
-rw-r--r--src/corelib/doc/src/objectmodel/properties.qdoc16
-rw-r--r--src/corelib/global/qnamespace.h2
-rw-r--r--src/corelib/global/qnamespace.qdoc1
-rw-r--r--src/corelib/global/qnumeric.h21
-rw-r--r--src/corelib/global/qtdeprecationmarkers.h8
-rw-r--r--src/corelib/io/qioring.cpp30
-rw-r--r--src/corelib/io/qioring_linux.cpp13
-rw-r--r--src/corelib/io/qioring_p.h39
-rw-r--r--src/corelib/io/qioring_win.cpp754
-rw-r--r--src/corelib/itemmodels/qrangemodel.cpp28
-rw-r--r--src/corelib/itemmodels/qrangemodel.h8
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h256
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter.h1671
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter.qdoc920
-rw-r--r--src/corelib/itemmodels/qrangemodeladapter_impl.h402
-rw-r--r--src/corelib/kernel/qassociativeiterable.cpp14
-rw-r--r--src/corelib/kernel/qassociativeiterable.h31
-rw-r--r--src/corelib/kernel/qiterable.cpp38
-rw-r--r--src/corelib/kernel/qiterable.h12
-rw-r--r--src/corelib/kernel/qjniobject.cpp175
-rw-r--r--src/corelib/kernel/qjniobject.h11
-rw-r--r--src/corelib/kernel/qjnitypes.h3
-rw-r--r--src/corelib/kernel/qmetaassociation.cpp195
-rw-r--r--src/corelib/kernel/qmetaassociation.h266
-rw-r--r--src/corelib/kernel/qmetacontainer.h121
-rw-r--r--src/corelib/kernel/qmetaobject.cpp55
-rw-r--r--src/corelib/kernel/qmetaobject.h2
-rw-r--r--src/corelib/kernel/qmetaobject_p.h1
-rw-r--r--src/corelib/kernel/qmetaobjectbuilder.cpp55
-rw-r--r--src/corelib/kernel/qmetaobjectbuilder_p.h4
-rw-r--r--src/corelib/kernel/qmetasequence.cpp176
-rw-r--r--src/corelib/kernel/qmetasequence.h308
-rw-r--r--src/corelib/kernel/qmetatype.cpp188
-rw-r--r--src/corelib/kernel/qobjectdefs.h2
-rw-r--r--src/corelib/kernel/qsequentialiterable.cpp14
-rw-r--r--src/corelib/kernel/qsequentialiterable.h31
-rw-r--r--src/corelib/kernel/qtmocconstants.h6
-rw-r--r--src/corelib/kernel/qtmochelpers.h7
-rw-r--r--src/corelib/kernel/qvariant.cpp10
-rw-r--r--src/corelib/kernel/qvariant.h27
-rw-r--r--src/corelib/mimetypes/qmimeprovider.cpp7
-rw-r--r--src/corelib/thread/qfuture.qdoc14
-rw-r--r--src/corelib/tools/qflatmap_p.h15
-rw-r--r--src/corelib/tools/qmargins.h15
-rw-r--r--src/corelib/tools/qpoint.h7
-rw-r--r--src/corelib/tools/qsize.h5
-rw-r--r--src/dbus/qdbusmetaobject.cpp3
-rw-r--r--src/gui/accessible/linux/atspiadaptor.cpp44
-rw-r--r--src/gui/accessible/linux/qspi_struct_marshallers.cpp19
-rw-r--r--src/gui/accessible/linux/qspi_struct_marshallers_p.h16
-rw-r--r--src/gui/accessible/linux/qspimatchrulematcher.cpp2
-rw-r--r--src/gui/accessible/qaccessiblecache.cpp23
-rw-r--r--src/gui/image/qabstractfileiconprovider.cpp2
-rw-r--r--src/gui/kernel/qguiapplication_p.h4
-rw-r--r--src/gui/math3d/qmatrix4x4.cpp32
-rw-r--r--src/gui/math3d/qquaternion.cpp2
-rw-r--r--src/gui/math3d/qquaternion.h8
-rw-r--r--src/gui/math3d/qvectornd.cpp20
-rw-r--r--src/gui/platform/unix/qxkbcommon.cpp6
-rw-r--r--src/gui/rhi/qrhivulkan.cpp77
-rw-r--r--src/gui/rhi/qrhivulkan_p.h10
-rw-r--r--src/gui/text/qfont.cpp80
-rw-r--r--src/gui/text/qfont_p.h2
-rw-r--r--src/gui/text/qrawfont.cpp2
-rw-r--r--src/gui/text/qrawfont.h2
-rw-r--r--src/gui/text/qtextengine.cpp6
-rw-r--r--src/gui/text/qtextengine_p.h2
-rw-r--r--src/network/CMakeLists.txt1
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h1
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp12
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h6
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp16
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp2
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h2
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp3
-rw-r--r--src/network/access/qnetworkrequest.cpp99
-rw-r--r--src/network/access/qnetworkrequest.h21
-rw-r--r--src/network/access/qtcpkeepaliveconfiguration_p.h50
-rw-r--r--src/network/socket/qabstractsocket.cpp14
-rw-r--r--src/network/socket/qabstractsocket_p.h2
-rw-r--r--src/plugins/platforms/cocoa/qnsview_drawing.mm3
-rw-r--r--src/plugins/platforms/directfb/qdirectfbconvenience.cpp1
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.cpp50
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.cpp4
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.h5
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp2
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.cpp42
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.h13
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp3
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp13
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h5
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow.cpp1
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow_p.h1
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp7
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.cpp2
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style.cpp269
-rw-r--r--src/plugins/tls/schannel/qtls_schannel.cpp9
-rw-r--r--src/sql/doc/qtsql.qdocconf3
-rw-r--r--src/sql/doc/src/sql-programming.qdoc2
-rw-r--r--src/tools/moc/generator.cpp35
-rw-r--r--src/tools/moc/generator.h10
-rw-r--r--src/tools/moc/moc.cpp89
-rw-r--r--src/tools/moc/moc.h2
-rw-r--r--src/widgets/CMakeLists.txt8
-rw-r--r--src/widgets/accessible/itemviews.cpp31
-rw-r--r--src/widgets/itemviews/qabstractitemview.cpp41
-rw-r--r--src/widgets/itemviews/qabstractitemview_p.h12
-rw-r--r--src/widgets/itemviews/qlistview.cpp16
-rw-r--r--src/widgets/itemviews/qlistview_p.h4
-rw-r--r--src/widgets/itemviews/qtableview.cpp6
-rw-r--r--src/widgets/itemviews/qtableview_p.h5
-rw-r--r--src/widgets/itemviews/qtreeview.cpp10
-rw-r--r--src/widgets/itemviews/qtreeview_p.h4
-rw-r--r--src/widgets/kernel/qtooltip.cpp17
-rw-r--r--src/widgets/styles/images/fusion_normalizedockup-10.png (renamed from src/widgets/styles/images/fusion_normalizedockup_10.png)bin234 -> 234 bytes
-rw-r--r--src/widgets/styles/images/fusion_normalizedockup-20.png (renamed from src/widgets/styles/images/fusion_normalizedockup_20.png)bin342 -> 342 bytes
-rw-r--r--src/widgets/styles/images/fusion_normalizedockup-48.png (renamed from src/widgets/styles/images/fusion_normalizedockup_48.png)bin487 -> 487 bytes
-rw-r--r--src/widgets/styles/images/fusion_normalizedockup-64.png (renamed from src/widgets/styles/images/fusion_normalizedockup_64.png)bin579 -> 579 bytes
-rw-r--r--tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json3
-rw-r--r--tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json3
-rw-r--r--tests/auto/corelib/global/qxp/is_virtual_base_of/tst_is_virtual_base_of.cpp8
-rw-r--r--tests/auto/corelib/io/CMakeLists.txt2
-rw-r--r--tests/auto/corelib/io/qioring/tst_qioring.cpp15
-rw-r--r--tests/auto/corelib/io/qurl/tst_qurl.cpp2
-rw-r--r--tests/auto/corelib/itemmodels/CMakeLists.txt1
-rw-r--r--tests/auto/corelib/itemmodels/qrangemodel/data.h37
-rw-r--r--tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp37
-rw-r--r--tests/auto/corelib/itemmodels/qrangemodeladapter/CMakeLists.txt32
-rw-r--r--tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp2718
-rw-r--r--tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp30
-rw-r--r--tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp37
-rw-r--r--tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp270
-rw-r--r--tests/auto/gui/kernel/qguivariant/test/tst_qguivariant.cpp2
-rw-r--r--tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp77
-rw-r--r--tests/auto/gui/text/qfont/tst_qfont.cpp59
-rw-r--r--tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp47
-rw-r--r--tests/auto/network/ssl/qsslsocket/certs/no_common_name.crt20
-rw-r--r--tests/auto/network/ssl/qsslsocket/certs/no_common_name.key28
-rw-r--r--tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp18
-rw-r--r--tests/auto/other/qaccessibility/tst_qaccessibility.cpp16
-rw-r--r--tests/auto/tools/moc/allmocs_baseline_in.json180
-rw-r--r--tests/auto/tools/moc/tst_moc.cpp20
-rw-r--r--tests/auto/tools/mochelpers/tst_mochelpers.cpp7
-rw-r--r--tests/auto/wasm/CMakeLists.txt1
-rw-r--r--tests/auto/wasm/qwasmkeytranslator/tst_qwasmkeytranslator.cpp404
-rw-r--r--tests/manual/corelib/itemmodels/qrangemodel/main.cpp84
175 files changed, 10860 insertions, 1310 deletions
diff --git a/bin/qt-cmake.bat.in b/bin/qt-cmake.bat.in
index 5d831ebce93..934f79c2599 100644
--- a/bin/qt-cmake.bat.in
+++ b/bin/qt-cmake.bat.in
@@ -7,5 +7,5 @@ set script_dir_path=%~dp0
set cmake_path=@CMAKE_COMMAND@
if not exist "%cmake_path%" set cmake_path=cmake
-set toolchain_path=%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\qt.toolchain.cmake
-"%cmake_path%" -DCMAKE_TOOLCHAIN_FILE="%toolchain_path%" @__qt_cmake_extra@ %*
+set CMAKE_TOOLCHAIN_FILE=%script_dir_path%\@__GlobalConfig_relative_path_from_bin_dir_to_cmake_config_dir@\qt.toolchain.cmake
+"%cmake_path%" @__qt_cmake_extra@ %*
diff --git a/bin/qt-cmake.in b/bin/qt-cmake.in
index 363c490960c..5203e49f878 100755
--- a/bin/qt-cmake.in
+++ b/bin/qt-cmake.in
@@ -16,4 +16,7 @@ toolchain_path="$script_dir_path/@__GlobalConfig_relative_path_from_bin_dir_to_c
@extra_qt_cmake_code@
# Find the qt toolchain relative to the absolute bin dir path where the script is located.
-exec "$cmake_path" -DCMAKE_TOOLCHAIN_FILE="$toolchain_path" @__qt_cmake_extra@ "$@"
+CMAKE_TOOLCHAIN_FILE="$toolchain_path"
+export CMAKE_TOOLCHAIN_FILE
+
+exec "$cmake_path" @__qt_cmake_extra@ "$@"
diff --git a/src/3rdparty/libpng/ANNOUNCE b/src/3rdparty/libpng/ANNOUNCE
index ae0b6ccc13b..10dee70d834 100644
--- a/src/3rdparty/libpng/ANNOUNCE
+++ b/src/3rdparty/libpng/ANNOUNCE
@@ -1,5 +1,5 @@
-libpng 1.6.51 - November 21, 2025
-=================================
+libpng 1.6.52 - December 3, 2025
+================================
This is a public release of libpng, intended for use in production code.
@@ -7,15 +7,12 @@ This is a public release of libpng, intended for use in production code.
Files available for download
----------------------------
-Source files with LF line endings (for Unix/Linux):
+Source files:
- * libpng-1.6.51.tar.xz (LZMA-compressed, recommended)
- * libpng-1.6.51.tar.gz (deflate-compressed)
-
-Source files with CRLF line endings (for Windows):
-
- * lpng1651.7z (LZMA-compressed, recommended)
- * lpng1651.zip (deflate-compressed)
+ * libpng-1.6.52.tar.xz (LZMA-compressed, recommended)
+ * libpng-1.6.52.tar.gz (deflate-compressed)
+ * lpng1652.7z (LZMA-compressed)
+ * lpng1652.zip (deflate-compressed)
Other information:
@@ -25,33 +22,18 @@ Other information:
* TRADEMARK.md
-Changes from version 1.6.50 to version 1.6.51
+Changes from version 1.6.51 to version 1.6.52
---------------------------------------------
- * Fixed CVE-2025-64505 (moderate severity):
- Heap buffer overflow in `png_do_quantize` via malformed palette index.
- (Reported by Samsung; analyzed by Fabio Gritti.)
- * Fixed CVE-2025-64506 (moderate severity):
- Heap buffer over-read in `png_write_image_8bit` with 8-bit input and
- `convert_to_8bit` enabled.
- (Reported by Samsung and <[email protected]>;
- analyzed by Fabio Gritti.)
- * Fixed CVE-2025-64720 (high severity):
- Buffer overflow in `png_image_read_composite` via incorrect palette
- premultiplication.
- (Reported by Samsung; analyzed by John Bowler.)
- * Fixed CVE-2025-65018 (high severity):
- Heap buffer overflow in `png_combine_row` triggered via
- `png_image_finish_read`.
- (Reported by <[email protected]>.)
- * Fixed a memory leak in `png_set_quantize`.
- (Reported by Samsung; analyzed by Fabio Gritti.)
- * Removed the experimental and incomplete ERROR_NUMBERS code.
- (Contributed by Tobias Stoeckmann.)
- * Improved the RISC-V vector extension support; required RVV 1.0 or newer.
- (Contributed by Filip Wasil.)
- * Added GitHub Actions workflows for automated testing.
- * Performed various refactorings and cleanups.
+ * Fixed CVE-2025-66293 (high severity):
+ Out-of-bounds read in `png_image_read_composite`.
+ (Reported by flyfish101 <[email protected]>.)
+ * Fixed the Paeth filter handling in the RISC-V RVV implementation.
+ (Reported by Filip Wasil; fixed by Liang Junzhao.)
+ * Improved the performance of the RISC-V RVV implementation.
+ (Contributed by Liang Junzhao.)
+ * Added allocation failure fuzzing to oss-fuzz.
+ (Contributed by Philippe Antoine.)
Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
diff --git a/src/3rdparty/libpng/CHANGES b/src/3rdparty/libpng/CHANGES
index 2478fd0fc08..f8ad74bbdf3 100644
--- a/src/3rdparty/libpng/CHANGES
+++ b/src/3rdparty/libpng/CHANGES
@@ -6304,6 +6304,17 @@ Version 1.6.51 [November 21, 2025]
Added GitHub Actions workflows for automated testing.
Performed various refactorings and cleanups.
+Version 1.6.52 [December 3, 2025]
+ Fixed CVE-2025-66293 (high severity):
+ Out-of-bounds read in `png_image_read_composite`.
+ (Reported by flyfish101 <[email protected]>.)
+ Fixed the Paeth filter handling in the RISC-V RVV implementation.
+ (Reported by Filip Wasil; fixed by Liang Junzhao.)
+ Improved the performance of the RISC-V RVV implementation.
+ (Contributed by Liang Junzhao.)
+ Added allocation failure fuzzing to oss-fuzz.
+ (Contributed by Philippe Antoine.)
+
Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
Subscription is required; visit
https://lists.sourceforge.net/lists/listinfo/png-mng-implement
diff --git a/src/3rdparty/libpng/README b/src/3rdparty/libpng/README
index 5ea329ee3da..87e5f8b177e 100644
--- a/src/3rdparty/libpng/README
+++ b/src/3rdparty/libpng/README
@@ -1,4 +1,4 @@
-README for libpng version 1.6.51
+README for libpng version 1.6.52
================================
See the note about version numbers near the top of `png.h`.
diff --git a/src/3rdparty/libpng/libpng-manual.txt b/src/3rdparty/libpng/libpng-manual.txt
index f342c18e814..f284d987ba6 100644
--- a/src/3rdparty/libpng/libpng-manual.txt
+++ b/src/3rdparty/libpng/libpng-manual.txt
@@ -9,7 +9,7 @@ libpng-manual.txt - A description on how to use and modify libpng
Based on:
- libpng version 1.6.36, December 2018, through 1.6.51 - November 2025
+ libpng version 1.6.36, December 2018, through 1.6.52 - December 2025
Updated and distributed by Cosmin Truta
Copyright (c) 2018-2025 Cosmin Truta
diff --git a/src/3rdparty/libpng/png.c b/src/3rdparty/libpng/png.c
index 380c4c19e6a..11b65d1f13e 100644
--- a/src/3rdparty/libpng/png.c
+++ b/src/3rdparty/libpng/png.c
@@ -13,7 +13,7 @@
#include "pngpriv.h"
/* Generate a compiler error if there is an old png.h in the search path. */
-typedef png_libpng_version_1_6_51 Your_png_h_is_not_version_1_6_51;
+typedef png_libpng_version_1_6_52 Your_png_h_is_not_version_1_6_52;
/* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the
* corresponding macro definitions. This causes a compile time failure if
@@ -817,7 +817,7 @@ png_get_copyright(png_const_structrp png_ptr)
return PNG_STRING_COPYRIGHT
#else
return PNG_STRING_NEWLINE \
- "libpng version 1.6.51" PNG_STRING_NEWLINE \
+ "libpng version 1.6.52" PNG_STRING_NEWLINE \
"Copyright (c) 2018-2025 Cosmin Truta" PNG_STRING_NEWLINE \
"Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \
PNG_STRING_NEWLINE \
diff --git a/src/3rdparty/libpng/png.h b/src/3rdparty/libpng/png.h
index fb93d2242b5..bceb9aa45d7 100644
--- a/src/3rdparty/libpng/png.h
+++ b/src/3rdparty/libpng/png.h
@@ -1,6 +1,6 @@
/* png.h - header file for PNG reference library
*
- * libpng version 1.6.51
+ * libpng version 1.6.52
*
* Copyright (c) 2018-2025 Cosmin Truta
* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
@@ -14,7 +14,7 @@
* libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger
* libpng versions 0.97, January 1998, through 1.6.35, July 2018:
* Glenn Randers-Pehrson
- * libpng versions 1.6.36, December 2018, through 1.6.51, November 2025:
+ * libpng versions 1.6.36, December 2018, through 1.6.52, December 2025:
* Cosmin Truta
* See also "Contributing Authors", below.
*/
@@ -238,7 +238,7 @@
* ...
* 1.5.30 15 10530 15.so.15.30[.0]
* ...
- * 1.6.51 16 10651 16.so.16.51[.0]
+ * 1.6.52 16 10651 16.so.16.52[.0]
*
* Henceforth the source version will match the shared-library major and
* minor numbers; the shared-library major version number will be used for
@@ -274,7 +274,7 @@
*/
/* Version information for png.h - this should match the version in png.c */
-#define PNG_LIBPNG_VER_STRING "1.6.51"
+#define PNG_LIBPNG_VER_STRING "1.6.52"
#define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n"
/* The versions of shared library builds should stay in sync, going forward */
@@ -285,7 +285,7 @@
/* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
#define PNG_LIBPNG_VER_MAJOR 1
#define PNG_LIBPNG_VER_MINOR 6
-#define PNG_LIBPNG_VER_RELEASE 51
+#define PNG_LIBPNG_VER_RELEASE 52
/* This should be zero for a public release, or non-zero for a
* development version.
@@ -316,7 +316,7 @@
* From version 1.0.1 it is:
* XXYYZZ, where XX=major, YY=minor, ZZ=release
*/
-#define PNG_LIBPNG_VER 10651 /* 1.6.51 */
+#define PNG_LIBPNG_VER 10652 /* 1.6.52 */
/* Library configuration: these options cannot be changed after
* the library has been built.
@@ -426,7 +426,7 @@ extern "C" {
/* This triggers a compiler error in png.c, if png.c and png.h
* do not agree upon the version number.
*/
-typedef char* png_libpng_version_1_6_51;
+typedef char* png_libpng_version_1_6_52;
/* Basic control structions. Read libpng-manual.txt or libpng.3 for more info.
*
diff --git a/src/3rdparty/libpng/pngconf.h b/src/3rdparty/libpng/pngconf.h
index 981df68d87a..76b5c20bdff 100644
--- a/src/3rdparty/libpng/pngconf.h
+++ b/src/3rdparty/libpng/pngconf.h
@@ -1,6 +1,6 @@
/* pngconf.h - machine-configurable file for libpng
*
- * libpng version 1.6.51
+ * libpng version 1.6.52
*
* Copyright (c) 2018-2025 Cosmin Truta
* Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
diff --git a/src/3rdparty/libpng/pnglibconf.h b/src/3rdparty/libpng/pnglibconf.h
index 00432d6c033..f4a993441f7 100644
--- a/src/3rdparty/libpng/pnglibconf.h
+++ b/src/3rdparty/libpng/pnglibconf.h
@@ -1,6 +1,6 @@
/* pnglibconf.h - library build configuration */
-/* libpng version 1.6.51 */
+/* libpng version 1.6.52 */
/* Copyright (c) 2018-2025 Cosmin Truta */
/* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */
diff --git a/src/3rdparty/libpng/pngread.c b/src/3rdparty/libpng/pngread.c
index 79917daaaf9..f8ca2b7e31d 100644
--- a/src/3rdparty/libpng/pngread.c
+++ b/src/3rdparty/libpng/pngread.c
@@ -3207,6 +3207,7 @@ png_image_read_composite(png_voidp argument)
ptrdiff_t step_row = display->row_bytes;
unsigned int channels =
(image->format & PNG_FORMAT_FLAG_COLOR) != 0 ? 3 : 1;
+ int optimize_alpha = (png_ptr->flags & PNG_FLAG_OPTIMIZE_ALPHA) != 0;
int pass;
for (pass = 0; pass < passes; ++pass)
@@ -3263,20 +3264,44 @@ png_image_read_composite(png_voidp argument)
if (alpha < 255) /* else just use component */
{
- /* This is PNG_OPTIMIZED_ALPHA, the component value
- * is a linear 8-bit value. Combine this with the
- * current outrow[c] value which is sRGB encoded.
- * Arithmetic here is 16-bits to preserve the output
- * values correctly.
- */
- component *= 257*255; /* =65535 */
- component += (255-alpha)*png_sRGB_table[outrow[c]];
+ if (optimize_alpha != 0)
+ {
+ /* This is PNG_OPTIMIZED_ALPHA, the component value
+ * is a linear 8-bit value. Combine this with the
+ * current outrow[c] value which is sRGB encoded.
+ * Arithmetic here is 16-bits to preserve the output
+ * values correctly.
+ */
+ component *= 257*255; /* =65535 */
+ component += (255-alpha)*png_sRGB_table[outrow[c]];
- /* So 'component' is scaled by 255*65535 and is
- * therefore appropriate for the sRGB to linear
- * conversion table.
- */
- component = PNG_sRGB_FROM_LINEAR(component);
+ /* Clamp to the valid range to defend against
+ * unforeseen cases where the data might be sRGB
+ * instead of linear premultiplied.
+ * (Belt-and-suspenders for GitHub Issue #764.)
+ */
+ if (component > 255*65535)
+ component = 255*65535;
+
+ /* So 'component' is scaled by 255*65535 and is
+ * therefore appropriate for the sRGB-to-linear
+ * conversion table.
+ */
+ component = PNG_sRGB_FROM_LINEAR(component);
+ }
+ else
+ {
+ /* Compositing was already done on the palette
+ * entries. The data is sRGB premultiplied on black.
+ * Composite with the background in sRGB space.
+ * This is not gamma-correct, but matches what was
+ * done to the palette.
+ */
+ png_uint_32 background = outrow[c];
+ component += ((255-alpha) * background + 127) / 255;
+ if (component > 255)
+ component = 255;
+ }
}
outrow[c] = (png_byte)component;
diff --git a/src/3rdparty/libpng/pngrtran.c b/src/3rdparty/libpng/pngrtran.c
index 2f520225515..507d11381ec 100644
--- a/src/3rdparty/libpng/pngrtran.c
+++ b/src/3rdparty/libpng/pngrtran.c
@@ -1843,6 +1843,7 @@ png_init_read_transformations(png_structrp png_ptr)
* transformations elsewhere.
*/
png_ptr->transformations &= ~(PNG_COMPOSE | PNG_GAMMA);
+ png_ptr->flags &= ~PNG_FLAG_OPTIMIZE_ALPHA;
} /* color_type == PNG_COLOR_TYPE_PALETTE */
/* if (png_ptr->background_gamma_type!=PNG_BACKGROUND_GAMMA_UNKNOWN) */
diff --git a/src/3rdparty/libpng/qt_attribution.json b/src/3rdparty/libpng/qt_attribution.json
index fe8ba663881..1f942f8f564 100644
--- a/src/3rdparty/libpng/qt_attribution.json
+++ b/src/3rdparty/libpng/qt_attribution.json
@@ -7,8 +7,8 @@
"Description": "libpng is the official PNG reference library.",
"Homepage": "http://www.libpng.org/pub/png/libpng.html",
- "Version": "1.6.51",
- "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.51.tar.xz",
+ "Version": "1.6.52",
+ "DownloadLocation": "https://download.sourceforge.net/libpng/libpng-1.6.52.tar.xz",
"PURL": "pkg:github/pnggroup/libpng@v$<VERSION>",
"CPE": "cpe:2.3:a:libpng:libpng:$<VERSION>:*:*:*:*:*:*:*",
diff --git a/src/3rdparty/sqlite/qt_attribution.json b/src/3rdparty/sqlite/qt_attribution.json
index 2f8bbc30a94..392d7adf0e3 100644
--- a/src/3rdparty/sqlite/qt_attribution.json
+++ b/src/3rdparty/sqlite/qt_attribution.json
@@ -7,10 +7,10 @@
"Description": "SQLite is a small C library that implements a self-contained, embeddable, zero-configuration SQL database engine.",
"Homepage": "https://www.sqlite.org/",
- "Version": "3.51.0",
+ "Version": "3.51.1",
"PURL": "pkg:github/sqlite/sqlite@version-$<VERSION>",
"CPE": "cpe:2.3:a:sqlite:sqlite:$<VERSION>:*:*:*:*:*:*:*",
- "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3510000.zip",
+ "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3510100.zip",
"License": "SQLite Blessing",
"LicenseId": "blessing",
"Copyright": "The authors disclaim copyright to the source code. However, a license can be obtained if needed."
diff --git a/src/3rdparty/sqlite/sqlite3.c b/src/3rdparty/sqlite/sqlite3.c
index 03d65b62820..912ac26944c 100644
--- a/src/3rdparty/sqlite/sqlite3.c
+++ b/src/3rdparty/sqlite/sqlite3.c
@@ -1,6 +1,6 @@
/******************************************************************************
** This file is an amalgamation of many separate C source files from SQLite
-** version 3.51.0. By combining all the individual C code files into this
+** version 3.51.1. By combining all the individual C code files into this
** single large file, the entire code can be compiled as a single translation
** unit. This allows many compilers to do optimizations that would not be
** possible if the files were compiled separately. Performance improvements
@@ -18,7 +18,7 @@
** separate file. This file contains only code for the core SQLite library.
**
** The content in this amalgamation comes from Fossil check-in
-** fb2c931ae597f8d00a37574ff67aeed3eced with changes in files:
+** 281fc0e9afc38674b9b0991943b9e9d1e64c with changes in files:
**
**
*/
@@ -467,12 +467,12 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.51.0"
-#define SQLITE_VERSION_NUMBER 3051000
-#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
-#define SQLITE_SCM_BRANCH "trunk"
-#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
-#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
+#define SQLITE_VERSION "3.51.1"
+#define SQLITE_VERSION_NUMBER 3051001
+#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
+#define SQLITE_SCM_BRANCH "branch-3.51"
+#define SQLITE_SCM_TAGS "release version-3.51.1"
+#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -10747,7 +10747,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
** &nbsp; ){
** &nbsp; // do something with pVal
** &nbsp; }
-** &nbsp; if( rc!=SQLITE_OK ){
+** &nbsp; if( rc!=SQLITE_DONE ){
** &nbsp; // an error has occurred
** &nbsp; }
** </pre></blockquote>)^
@@ -38004,6 +38004,7 @@ SQLITE_PRIVATE void *sqlite3HashInsert(Hash *pH, const char *pKey, void *data){
return 0;
}
+
/************** End of hash.c ************************************************/
/************** Begin file opcodes.c *****************************************/
/* Automatically generated. Do not edit */
@@ -130655,6 +130656,7 @@ SQLITE_PRIVATE void sqlite3SchemaClear(void *p){
for(pElem=sqliteHashFirst(&temp2); pElem; pElem=sqliteHashNext(pElem)){
sqlite3DeleteTrigger(&xdb, (Trigger*)sqliteHashData(pElem));
}
+
sqlite3HashClear(&temp2);
sqlite3HashInit(&pSchema->tblHash);
for(pElem=sqliteHashFirst(&temp1); pElem; pElem=sqliteHashNext(pElem)){
@@ -160976,9 +160978,12 @@ SQLITE_PRIVATE int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
addModuleArgument(pParse, pTab, 0);
addModuleArgument(pParse, pTab, sqlite3DbStrDup(db, pTab->zName));
+ db->nSchemaLock++;
rc = vtabCallConstructor(db, pTab, pMod, pModule->xConnect, &zErr);
+ db->nSchemaLock--;
if( rc ){
sqlite3ErrorMsg(pParse, "%s", zErr);
+ pParse->rc = rc;
sqlite3DbFree(db, zErr);
sqlite3VtabEponymousTableClear(db, pMod);
}
@@ -174040,8 +174045,22 @@ SQLITE_PRIVATE void sqlite3WhereEnd(WhereInfo *pWInfo){
sqlite3VdbeAddOp2(v, OP_Goto, 1, pLevel->p2);
}
#endif /* SQLITE_DISABLE_SKIPAHEAD_DISTINCT */
- if( pTabList->a[pLevel->iFrom].fg.fromExists ){
- sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2);
+ if( pTabList->a[pLevel->iFrom].fg.fromExists && i==pWInfo->nLevel-1 ){
+ /* If the EXISTS-to-JOIN optimization was applied, then the EXISTS
+ ** loop(s) will be the inner-most loops of the join. There might be
+ ** multiple EXISTS loops, but they will all be nested, and the join
+ ** order will not have been changed by the query planner. If the
+ ** inner-most EXISTS loop sees a single successful row, it should
+ ** break out of *all* EXISTS loops. But only the inner-most of the
+ ** nested EXISTS loops should do this breakout. */
+ int nOuter = 0; /* Nr of outer EXISTS that this one is nested within */
+ while( nOuter<i ){
+ if( !pTabList->a[pLevel[-nOuter-1].iFrom].fg.fromExists ) break;
+ nOuter++;
+ }
+ testcase( nOuter>0 );
+ sqlite3VdbeAddOp2(v, OP_Goto, 0, pLevel[-nOuter].addrBrk);
+ VdbeComment((v, "EXISTS break"));
}
/* The common case: Advance to the next row */
if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont);
@@ -186225,6 +186244,7 @@ SQLITE_PRIVATE void sqlite3LeaveMutexAndCloseZombie(sqlite3 *db){
/* Clear the TEMP schema separately and last */
if( db->aDb[1].pSchema ){
sqlite3SchemaClear(db->aDb[1].pSchema);
+ assert( db->aDb[1].pSchema->trigHash.count==0 );
}
sqlite3VtabUnlockList(db);
@@ -187553,7 +187573,7 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){
*/
SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){
int rc = SQLITE_OK;
- if( !sqlite3SafetyCheckSickOrOk(db) ){
+ if( !sqlite3SafetyCheckOk(db) ){
return SQLITE_MISUSE_BKPT;
}
sqlite3_mutex_enter(db->mutex);
@@ -249220,6 +249240,7 @@ static void fts5SegIterReverseInitPage(Fts5Index *p, Fts5SegIter *pIter){
while( 1 ){
u64 iDelta = 0;
+ if( i>=n ) break;
if( eDetail==FTS5_DETAIL_NONE ){
/* todo */
if( i<n && a[i]==0 ){
@@ -260283,7 +260304,7 @@ static void fts5SourceIdFunc(
){
assert( nArg==0 );
UNUSED_PARAM2(nArg, apUnused);
- sqlite3_result_text(pCtx, "fts5: 2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b", -1, SQLITE_TRANSIENT);
+ sqlite3_result_text(pCtx, "fts5: 2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88", -1, SQLITE_TRANSIENT);
}
/*
@@ -265104,7 +265125,12 @@ static int fts5VocabOpenMethod(
return rc;
}
+/*
+** Restore cursor pCsr to the state it was in immediately after being
+** created by the xOpen() method.
+*/
static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
+ int nCol = pCsr->pFts5->pConfig->nCol;
pCsr->rowid = 0;
sqlite3Fts5IterClose(pCsr->pIter);
sqlite3Fts5StructureRelease(pCsr->pStruct);
@@ -265114,6 +265140,12 @@ static void fts5VocabResetCursor(Fts5VocabCursor *pCsr){
pCsr->nLeTerm = -1;
pCsr->zLeTerm = 0;
pCsr->bEof = 0;
+ pCsr->iCol = 0;
+ pCsr->iInstPos = 0;
+ pCsr->iInstOff = 0;
+ pCsr->colUsed = 0;
+ memset(pCsr->aCnt, 0, sizeof(i64)*nCol);
+ memset(pCsr->aDoc, 0, sizeof(i64)*nCol);
}
/*
diff --git a/src/3rdparty/sqlite/sqlite3.h b/src/3rdparty/sqlite/sqlite3.h
index 70a4a1b1a5e..76c567d050a 100644
--- a/src/3rdparty/sqlite/sqlite3.h
+++ b/src/3rdparty/sqlite/sqlite3.h
@@ -146,12 +146,12 @@ extern "C" {
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
** [sqlite_version()] and [sqlite_source_id()].
*/
-#define SQLITE_VERSION "3.51.0"
-#define SQLITE_VERSION_NUMBER 3051000
-#define SQLITE_SOURCE_ID "2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b"
-#define SQLITE_SCM_BRANCH "trunk"
-#define SQLITE_SCM_TAGS "release major-release version-3.51.0"
-#define SQLITE_SCM_DATETIME "2025-11-04T19:38:17.314Z"
+#define SQLITE_VERSION "3.51.1"
+#define SQLITE_VERSION_NUMBER 3051001
+#define SQLITE_SOURCE_ID "2025-11-28 17:28:25 281fc0e9afc38674b9b0991943b9e9d1e64c6cbdb133d35f6f5c87ff6af38a88"
+#define SQLITE_SCM_BRANCH "branch-3.51"
+#define SQLITE_SCM_TAGS "release version-3.51.1"
+#define SQLITE_SCM_DATETIME "2025-11-28T17:28:25.933Z"
/*
** CAPI3REF: Run-Time Library Version Numbers
@@ -10426,7 +10426,7 @@ SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle);
** &nbsp; ){
** &nbsp; // do something with pVal
** &nbsp; }
-** &nbsp; if( rc!=SQLITE_OK ){
+** &nbsp; if( rc!=SQLITE_DONE ){
** &nbsp; // an error has occurred
** &nbsp; }
** </pre></blockquote>)^
diff --git a/src/3rdparty/sqlite/update_sqlite.sh b/src/3rdparty/sqlite/update_sqlite.sh
index 4b8e1869d8c..3f7447dc4e5 100755
--- a/src/3rdparty/sqlite/update_sqlite.sh
+++ b/src/3rdparty/sqlite/update_sqlite.sh
@@ -8,7 +8,7 @@
version_maj=3
version_min=51
-version_patch=0
+version_patch=1
year=2025
version=${version_maj}.${version_min}.${version_patch}
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index e12d824cebb..ea8cf7b9c8e 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -175,12 +175,12 @@ qt_internal_add_module(Core
kernel/qfunctions_p.h
kernel/qiterable.cpp kernel/qiterable.h kernel/qiterable_impl.h
kernel/qmath.cpp kernel/qmath.h
- kernel/qmetaassociation.cpp
+ kernel/qmetaassociation.cpp kernel/qmetaassociation.h
kernel/qmetacontainer.cpp kernel/qmetacontainer.h
kernel/qmetaobject.cpp kernel/qmetaobject.h kernel/qmetaobject_p.h
kernel/qmetaobject_moc_p.h
kernel/qmetaobjectbuilder.cpp kernel/qmetaobjectbuilder_p.h
- kernel/qmetasequence.cpp
+ kernel/qmetasequence.cpp kernel/qmetasequence.h
kernel/qmetatype.cpp kernel/qmetatype.h kernel/qmetatype_p.h
kernel/qmimedata.cpp kernel/qmimedata.h
kernel/qtmetamacros.h kernel/qtmocconstants.h kernel/qtmochelpers.h
@@ -583,7 +583,7 @@ if(QT_FEATURE_async_io)
SOURCES
io/qrandomaccessasyncfile_darwin.mm
)
- elseif(LINUX AND QT_FEATURE_liburing)
+ elseif((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring))
qt_internal_extend_target(Core
SOURCES
io/qrandomaccessasyncfile_qioring.cpp
@@ -763,6 +763,11 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_liburing
uring
)
+qt_internal_extend_target(Core CONDITION QT_FEATURE_windows_ioring
+ SOURCES
+ io/qioring.cpp io/qioring_win.cpp io/qioring_p.h
+)
+
# Workaround for QTBUG-101411
# Remove if QCC (gcc version 8.3.0) for QNX 7.1.0 is no longer supported
qt_internal_extend_target(Core CONDITION QCC AND (CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL "8.3.0")
@@ -1231,6 +1236,7 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_itemmodel
itemmodels/qabstractitemmodel.cpp itemmodels/qabstractitemmodel.h itemmodels/qabstractitemmodel_p.h
itemmodels/qitemselectionmodel.cpp itemmodels/qitemselectionmodel.h itemmodels/qitemselectionmodel_p.h
itemmodels/qrangemodel.h itemmodels/qrangemodel_impl.h itemmodels/qrangemodel.cpp
+ itemmodels/qrangemodeladapter.h itemmodels/qrangemodeladapter_impl.h
)
qt_internal_extend_target(Core CONDITION QT_FEATURE_proxymodel
diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake
index 6f3a9fd05f5..b69d0285de2 100644
--- a/src/corelib/Qt6CoreMacros.cmake
+++ b/src/corelib/Qt6CoreMacros.cmake
@@ -3264,7 +3264,7 @@ function(_qt_internal_setup_deploy_support)
list(JOIN candidate_paths "\n " candidate_paths_joined)
- if(NOT QT_NO_QTPATHS_DEPLOYMENT_WARNING AND NOT target_qtpaths_path)
+ if(WIN32 AND NOT QT_NO_QTPATHS_DEPLOYMENT_WARNING AND NOT target_qtpaths_path)
message(WARNING
"No qtpaths executable found for deployment purposes. Candidates searched: \n "
"${candidate_paths_joined}"
diff --git a/src/corelib/compat/removed_api.cpp b/src/corelib/compat/removed_api.cpp
index 7fe8aeb63b7..63fce94dfac 100644
--- a/src/corelib/compat/removed_api.cpp
+++ b/src/corelib/compat/removed_api.cpp
@@ -1291,13 +1291,6 @@ QByteArray QMetaEnum::valueToKeys(int value) const
#include "qmutex.h"
-#include "qbytearray.h"
-
-QByteArray QByteArray::percentDecoded(char percent) const
-{
- return fromPercentEncoding(*this, percent);
-}
-
#if QT_CONFIG(thread)
void QBasicMutex::destroyInternal(QMutexPrivate *d)
{
@@ -1495,6 +1488,13 @@ bool QObject::doSetProperty(const char *name, const QVariant *lvalue, QVariant *
#if QT_CORE_REMOVED_SINCE(6, 11)
+#include "qbytearray.h"
+
+QByteArray QByteArray::percentDecoded(char percent) const
+{
+ return fromPercentEncoding(*this, percent);
+}
+
#if QT_CONFIG(thread)
// some of the previously inlined API became removed
#include "qreadwritelock.h"
diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake
index c1d15c75054..7216f2920fe 100644
--- a/src/corelib/configure.cmake
+++ b/src/corelib/configure.cmake
@@ -605,6 +605,27 @@ int main(void)
"
)
+qt_config_compile_test(windows_ioring
+ LABEL "Windows SDK: IORing"
+ CODE
+"#include <windows.h>
+#include <ioringapi.h>
+
+int main(void)
+{
+ /* BEGIN TEST: */
+ IORING_CREATE_FLAGS flags;
+ memset(&flags, 0, sizeof(flags));
+ HIORING ioRingHandle = nullptr;
+ HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, 1, 1, &ioRingHandle);
+ if (hr == IORING_E_SUBMISSION_QUEUE_FULL) // not valid, but test that this #define exists
+ return 0;
+ /* END TEST: */
+ return 0;
+}
+"
+)
+
# cpp_winrt
qt_config_compile_test(cpp_winrt
LABEL "cpp/winrt"
@@ -785,6 +806,11 @@ qt_feature("winsdkicu" PRIVATE
CONDITION TEST_winsdkicu
DISABLE QT_FEATURE_icu
)
+qt_feature("windows_ioring" PRIVATE
+ LABEL "Windows I/O Ring"
+ AUTODETECT WIN32 AND CMAKE_HOST_SYSTEM_VERSION VERSION_GREATER_EQUAL 10.0.22000
+ CONDITION TEST_windows_ioring
+)
qt_feature("inotify" PUBLIC PRIVATE
LABEL "inotify"
CONDITION TEST_inotify OR TEST_fsnotify
@@ -1272,6 +1298,7 @@ qt_configure_add_summary_entry(ARGS "glib")
qt_configure_add_summary_entry(ARGS "icu")
qt_configure_add_summary_entry(ARGS "jemalloc")
qt_configure_add_summary_entry(ARGS "liburing")
+qt_configure_add_summary_entry(ARGS "windows_ioring")
qt_configure_add_summary_entry(ARGS "timezone_tzdb")
qt_configure_add_summary_entry(ARGS "system-libb2")
qt_configure_add_summary_entry(ARGS "mimetype-database")
diff --git a/src/corelib/doc/snippets/CMakeLists.txt b/src/corelib/doc/snippets/CMakeLists.txt
index 55db84ccbea..c0d15463e9c 100644
--- a/src/corelib/doc/snippets/CMakeLists.txt
+++ b/src/corelib/doc/snippets/CMakeLists.txt
@@ -13,6 +13,7 @@ add_library(corelib_snippets OBJECT
qmessageauthenticationcode/main.cpp
qmetatype/registerConverters.cpp
qrangemodel/main.cpp
+ qrangemodeladapter/main.cpp
qstack/main.cpp
qstringlist/main.cpp
qstringlistmodel/main.cpp
diff --git a/src/corelib/doc/snippets/code/doc_src_properties.cpp b/src/corelib/doc/snippets/code/doc_src_properties.cpp
index eafa7acda3b..07f574c2de2 100644
--- a/src/corelib/doc/snippets/code/doc_src_properties.cpp
+++ b/src/corelib/doc/snippets/code/doc_src_properties.cpp
@@ -16,6 +16,8 @@ Q_PROPERTY(type name
[BINDABLE bindableProperty]
[CONSTANT]
[FINAL]
+ [VIRTUAL]
+ [OVERRIDE]
[REQUIRED])
//! [0]
diff --git a/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp b/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp
index e23f6c9d103..1d7a3fa6409 100644
--- a/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp
+++ b/src/corelib/doc/snippets/code/src_corelib_kernel_qvariant.cpp
@@ -7,8 +7,8 @@
#include <QVariant>
#include <QColor>
#include <QPalette>
-#include <QSequentialIterable>
-#include <QAssociativeIterable>
+#include <QMetaSequence>
+#include <QMetaAssociation>
QString tr(const char *s)
{
@@ -125,14 +125,14 @@ QVariant examples()
QVariant variant = QVariant::fromValue(intList);
if (variant.canConvert<QVariantList>()) {
- QSequentialIterable iterable = variant.value<QSequentialIterable>();
+ QMetaSequence::Iterable iterable = variant.value<QMetaSequence::Iterable>();
// Can use C++11 range-for:
for (const QVariant &v : iterable) {
qDebug() << v;
}
// Can use iterators:
- QSequentialIterable::const_iterator it = iterable.begin();
- const QSequentialIterable::const_iterator end = iterable.end();
+ QMetaSequence::Iterable::const_iterator it = iterable.begin();
+ const QMetaSequence::Iterable::const_iterator end = iterable.end();
for ( ; it != end; ++it) {
qDebug() << *it;
}
@@ -149,14 +149,14 @@ QVariant examples()
QVariant variant = QVariant::fromValue(mapping);
if (variant.canConvert<QVariantHash>()) {
- QAssociativeIterable iterable = variant.value<QAssociativeIterable>();
+ QMetaAssociation::Iterable iterable = variant.value<QMetaAssociation::Iterable>();
// Can use C++11 range-for over the values:
for (const QVariant &v : iterable) {
qDebug() << v;
}
// Can use iterators:
- QAssociativeIterable::const_iterator it = iterable.begin();
- const QAssociativeIterable::const_iterator end = iterable.end();
+ QMetaAssociation::Iterable::const_iterator it = iterable.begin();
+ const QMetaAssociation::Iterable::const_iterator end = iterable.end();
for ( ; it != end; ++it) {
qDebug() << *it; // The current value
qDebug() << it.key();
diff --git a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp
index 6531d025bef..89ac917ccd3 100644
--- a/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp
+++ b/src/corelib/doc/snippets/code/src_corelib_thread_qfuture.cpp
@@ -543,6 +543,30 @@ void examples(QFuture<QString> someQStringFuture,
f.cancelChain();
//! [38]
}
+
+ {
+ auto createFuture = [] { return QtFuture::makeReadyVoidFuture(); };
+ auto runNestedComputation = [] { return QtFuture::makeReadyVoidFuture(); };
+ //! [39]
+ QFuture<void> nested;
+ auto f = createFuture()
+ .then([&]{
+ nested = runNestedComputation();
+ // do some other work
+ return nested;
+ })
+ .unwrap()
+ .then([]{
+ // other continuation
+ })
+ .onCanceled([]{
+ // handle cancellation
+ });
+ //...
+ f.cancelChain();
+ nested.cancel();
+ //! [39]
+ }
}
class SomeClass : public QObject
diff --git a/src/corelib/doc/snippets/qrangemodeladapter/main.cpp b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp
new file mode 100644
index 00000000000..a0791dd1dd1
--- /dev/null
+++ b/src/corelib/doc/snippets/qrangemodeladapter/main.cpp
@@ -0,0 +1,366 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtCore/qrangemodeladapter.h>
+
+#ifndef QT_NO_WIDGETS
+
+using namespace Qt::StringLiterals;
+
+#include <QtWidgets/qlistview.h>
+#include <QtWidgets/qtableview.h>
+#include <QtWidgets/qtreeview.h>
+#include <vector>
+
+class Book
+{
+ Q_GADGET
+ Q_PROPERTY(QString title READ title)
+ Q_PROPERTY(QString author READ author)
+ Q_PROPERTY(QString summary MEMBER m_summary)
+ Q_PROPERTY(int rating READ rating WRITE setRating)
+public:
+ enum Roles
+ {
+ TitleRole = Qt::UserRole,
+ AuthorRole,
+ SummaryRole,
+ RatingRole,
+ };
+
+ Book() = default;
+ Book(const QString &title, const QString &author);
+
+ // C++ rule of 0: destructor, as well as copy/move operations
+ // provided by the compiler.
+
+ // read-only properties
+ QString title() const { return m_title; }
+ QString author() const { return m_author; }
+
+ // read/writable property with input validation
+ int rating() const { return m_rating; }
+ void setRating(int rating)
+ {
+ m_rating = qBound(0, rating, 5);
+ }
+
+private:
+ QString m_title;
+ QString m_author;
+ QString m_summary;
+ int m_rating = 0;
+};
+
+template <> struct QRangeModel::RowOptions<Book>
+{
+ static constexpr auto rowCategory = QRangeModel::RowCategory::MultiRoleItem;
+};
+
+struct TreeRow;
+using Tree = QList<TreeRow *>;
+
+struct TreeRow
+{
+ QString firstColumn;
+ int secondColumn = 0;
+
+ TreeRow() = default;
+ explicit TreeRow(const QString &first, int second, std::optional<Tree> children = std::nullopt)
+ : firstColumn(first), secondColumn(second), m_children(children)
+ {}
+
+ TreeRow *parentRow() const { return m_parent; }
+ void setParentRow(TreeRow *parent) { m_parent = parent; }
+ const std::optional<Tree> &childRows() const { return m_children; }
+ std::optional<Tree> &childRows() { return m_children; }
+
+private:
+ TreeRow *m_parent;
+ std::optional<Tree> m_children;
+
+ template <size_t I> friend inline decltype(auto) get(const TreeRow &row) {
+ static_assert(I < 2);
+ return false;
+ }
+};
+
+namespace std {
+ template<> struct tuple_size<TreeRow> : integral_constant<int, 2> {};
+ template<> struct tuple_element<0, TreeRow> { using type = QString; };
+ template<> struct tuple_element<1, TreeRow> { using type = int; };
+}
+
+void construct_and_use()
+{
+ //! [construct]
+ std::vector<int> data = {1, 2, 3, 4, 5};
+ QRangeModelAdapter adapter(&data);
+ //! [construct]
+
+ //! [use-model]
+ QListView listView;
+ listView.setModel(adapter.model());
+ //! [use-model]
+}
+
+void get_and_set()
+{
+ QListView tableView;
+ //! [get-range]
+ QList<Book> books = {
+ // ...
+ };
+ QRangeModelAdapter adapter(books);
+ tableView.setModel(adapter.model());
+
+ // show UI and where the user can modify the list
+
+ QList<Book> modifiedBooks = adapter;
+ // or
+ modifiedBooks = adapter.range();
+ //! [get-range]
+
+ //! [set-range]
+ // reset to the original
+ adapter = books;
+ // or
+ adapter.setRange(books);
+ //! [set-range]
+}
+
+void dataAccess()
+{
+ int row = 0;
+ int column = 0;
+ int path = 0;
+ int to = 0;
+ int branch = 0;
+
+ QRangeModelAdapter listAdapter(QList<int>{});
+ //! [list-data]
+ QVariant listItem = listAdapter.data(row);
+ //! [list-data]
+
+ QRangeModelAdapter tableAdapter(QList<QList<int>>{});
+ //! [table-data]
+ QVariant tableItem = tableAdapter.data(row, column);
+ //! [table-data]
+
+ QRangeModelAdapter treeAdapter(QList<TreeRow *>{});
+ //! [tree-data]
+ QVariant treeItem = treeAdapter.data({path, to, branch}, column);
+ //! [tree-data]
+
+ //! [multirole-data]
+ QRangeModelAdapter listOfBooks(QList<Book>{
+ // ~~~
+ });
+ QString bookTitle = listOfBooks.data(0, Book::TitleRole).toString();
+ Book multiRoleItem = listOfBooks.data(0).value<Book>();
+ //! [multirole-data]
+}
+
+void list_access()
+{
+ QListView listView;
+ {
+ //! [list-access]
+ QRangeModelAdapter list(std::vector<int>{1, 2, 3, 4, 5});
+ listView.setModel(list.model());
+
+ int firstValue = list.at(0); // == 1
+ list.at(0) = -1;
+ list.at(1) = list.at(4);
+ //! [list-access]
+ }
+ {
+ //! [list-access-multirole]
+ QRangeModelAdapter books(QList<Book>{
+ // ~~~
+ });
+ Book firstBook = books.at(0);
+ Book newBook = {};
+ books.at(0) = newBook; // dataChanged() emitted
+ //! [list-access-multirole]
+
+ //! [list-access-multirole-member-access]
+ QString title = books.at(0)->title();
+ //! [list-access-multirole-member-access]
+
+ //! [list-access-multirole-write-back]
+ // books.at(0)->setRating(5); - not possible even though 'books' is not const
+ firstBook = books.at(0);
+ firstBook.setRating(5);
+ books.at(0) = firstBook; // dataChanged() emitted
+ //! [list-access-multirole-write-back]
+ }
+}
+
+void table_access()
+{
+ QTableView tableView;
+ {
+ //! [table-item-access]
+ QRangeModelAdapter table(std::vector<std::vector<double>>{
+ {1.0, 2.0, 3.0, 4.0, 5.0},
+ {6.0, 7.0, 8.0, 9.0, 10.0},
+ });
+ tableView.setModel(table.model());
+
+ double value = table.at(0, 2); // value == 3.0
+ table.at(0, 2) = value * 2; // table[0, 2] == 6.0
+ //! [table-item-access]
+
+ //! [table-row-const-access]
+ const auto &constTable = table;
+ const std::vector<double> &topRow = constTable.at(0);
+ //! [table-row-const-access]
+
+ //! [table-row-access]
+ auto lastRow = table.at(table.rowCount() - 1);
+ lastRow = { 6.5, 7.5, 8.0, 9.0, 10 }; // emits dataChanged() for entire row
+ //! [table-row-access]
+ }
+
+ {
+ //! [table-mixed-type-access]
+ QRangeModelAdapter table(std::vector<std::tuple<int, QString>>{
+ // ~~~
+ });
+ int number = table.at(0, 0)->toInt();
+ QString text = table.at(0, 1)->toString();
+ //! [table-mixed-type-access]
+ }
+}
+
+void tree_access()
+{
+ QTreeView treeView;
+
+ //! [tree-row-access]
+ QRangeModelAdapter tree = QRangeModelAdapter(Tree{
+ new TreeRow{"Germany", 357002, Tree{
+ new TreeRow("Bavaria", 70550)
+ },
+ },
+ new TreeRow{"France", 632702},
+ });
+ treeView.setModel(tree.model());
+
+ auto germanyData = tree.at(0);
+ auto bavariaData = tree.at({0, 0});
+ //! [tree-row-access]
+
+ //! [tree-item-access]
+ auto germanyName = tree.at(0, 0);
+ auto bavariaSize = tree.at({0, 0}, 1);
+ //! [tree-item-access]
+
+ //! [tree-row-write]
+ // deletes the old row - tree was moved in
+ tree.at({0, 0}) = new TreeRow{"Berlin", 892};
+ //! [tree-row-write]
+}
+
+void read_only()
+{
+#if 0
+ //! [read-only]
+ const QStringList strings = {"On", "Off"};
+ QRangeModelAdapter adapter(strings);
+ adapter.at(0) = "Undecided"; // compile error: return value of 'at' is const
+ adapter.insertRow(0); // compiler error: requirements not satisfied
+ //! [read-only]
+#endif
+}
+
+void list_iterate()
+{
+ QRangeModelAdapter books(QList<Book>{
+ // ~~~
+ });
+ QListView view;
+ view.setModel(books.model());
+
+ //! [ranged-for-const-list]
+ for (const auto &book : std::as_const(books)) {
+ qDebug() << "The book" << book.title()
+ << "written by" << book.author()
+ << "has" << book.rating() << "stars";
+ }
+ //! [ranged-for-const-list]
+
+ //! [ranged-for-mutable-list]
+ for (auto book : books) {
+ qDebug() << "The book" << book->title()
+ << "written by" << book->author()
+ << "has" << book->rating() << "stars";
+
+ Book copy = book;
+ copy.setRating(copy.rating() + 1);
+ book = copy;
+ }
+ //! [ranged-for-mutable-list]
+}
+
+void table_iterate()
+{
+ //! [ranged-for-const-table]
+ QRangeModelAdapter table(std::vector<std::pair<int, QString>>{
+ // ~~~
+ });
+
+ for (const auto &row : std::as_const(table)) {
+ qDebug() << "Number is" << row->first << "and string is" << row->second;
+ }
+ //! [ranged-for-const-table]
+
+ //! [ranged-for-const-table-items]
+ for (const auto &row : std::as_const(table)) {
+ for (const auto &item : row) {
+ qDebug() << item; // item is a QVariant
+ }
+ }
+ //! [ranged-for-const-table-items]
+
+ //! [ranged-for-mutable-table]
+ for (auto row : table) {
+ qDebug() << "Number is" << row->first << "and string is" << row->second;
+ row = std::make_pair(42, u"forty-two"_s);
+ }
+ //! [ranged-for-mutable-table]
+
+ //! [ranged-for-mutable-table-items]
+ for (auto row : table) {
+ for (auto item : row) {
+ item = 42;
+ }
+ }
+ //! [ranged-for-mutable-table-items]
+}
+
+void tree_iterate()
+{
+ QRangeModelAdapter tree = QRangeModelAdapter(Tree{
+ new TreeRow{"1", 1, Tree{
+ new TreeRow("1.1", 11)
+ },
+ },
+ new TreeRow{"2", 2},
+ });
+
+ static_assert(std::is_same_v<typename decltype(tree)::DataReference::value_type, QVariant>);
+
+ //! [ranged-for-tree]
+ for (auto row : tree) {
+ if (row.hasChildren()) {
+ for (auto child : row.children()) {
+ // ~~~
+ }
+ }
+ }
+ //! [ranged-for-tree]
+}
+
+#endif
diff --git a/src/corelib/doc/src/jni.qdoc b/src/corelib/doc/src/jni.qdoc
index d05dd44ff60..72b29506c34 100644
--- a/src/corelib/doc/src/jni.qdoc
+++ b/src/corelib/doc/src/jni.qdoc
@@ -65,10 +65,10 @@
\endcode
The C++ classes \c{QtJniTypes::File} and \c{QtJniTypes::FileWriter} are
- then QJniObject-like types that can be used to instantiate the
- corresponding Java class, to call methods, and to pass such instances
- through QJniObject variadic template methods with automatic, compile-time
- signature deduction.
+ then QJniObject-like types (specializations of QtJniTypes::JObject, to be
+ precise) that can be used to instantiate the corresponding Java class, to
+ call methods, and to pass such instances through QJniObject variadic
+ template methods with automatic, compile-time signature deduction.
\code
using namespace QtJniTypes;
@@ -89,7 +89,7 @@
});
\endcode
- \sa Q_DECLARE_JNI_NATIVE_METHOD, Q_JNI_NATIVE_METHOD
+ \sa Q_DECLARE_JNI_NATIVE_METHOD, Q_JNI_NATIVE_METHOD, QtJniTypes::JObject
*/
/*!
diff --git a/src/corelib/doc/src/objectmodel/properties.qdoc b/src/corelib/doc/src/objectmodel/properties.qdoc
index 0e66c8445c2..71e14222763 100644
--- a/src/corelib/doc/src/objectmodel/properties.qdoc
+++ b/src/corelib/doc/src/objectmodel/properties.qdoc
@@ -128,10 +128,18 @@
constant value may be different for different instances of the object. A
constant property cannot have a WRITE method or a NOTIFY signal.
- \li The presence of the \c FINAL attribute indicates that the property
- will not be overridden by a derived class. This can be used for performance
- optimizations in some cases, but is not enforced by moc. Care must be taken
- never to override a \c FINAL property.
+ \li \c FINAL, \c VIRTUAL, \c OVERRIDE modifiers mirror the semantics of their C++ and
+ \l {Override Semantics}{QML counterparts}, allowing to make property overriding explicit at the
+ meta-object level.
+
+ \note At present, these modifiers are not enforced by moc.
+ They are recognized syntactically and are primarily used for QML runtime enforcement and tooling
+ diagnostics. Future versions may introduce stricter compile-time validation and warnings for
+ invalid overrides across modules.
+
+ \note If you want to change accessing behaviour for a property, use the
+ polymorphism provided by C++.
+
\li The presence of the \c REQUIRED attribute indicates that the property
should be set by a user of the class. This is not enforced by moc, and is
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h
index fb37e8df8e4..8332d1fd9bf 100644
--- a/src/corelib/global/qnamespace.h
+++ b/src/corelib/global/qnamespace.h
@@ -1533,6 +1533,8 @@ namespace Qt {
WhatsThisPropertyRole = 31,
// QRangeModel support for QML's required property var modelData
RangeModelDataRole = 40,
+ // QRangeModelAdapter support for accessing entire multi-role objects
+ RangeModelAdapterRole = 41,
// Reserved
UserRole = 0x0100,
diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc
index e530b28e7f1..69418af4b68 100644
--- a/src/corelib/global/qnamespace.qdoc
+++ b/src/corelib/global/qnamespace.qdoc
@@ -2830,6 +2830,7 @@
\omitvalue StatusTipPropertyRole
\omitvalue WhatsThisPropertyRole
\omitvalue RangeModelDataRole
+ \omitvalue RangeModelAdapterRole
\omitvalue StandardItemFlagsRole
\omitvalue FileInfoRole
\omitvalue RemoteObjectsCacheRole
diff --git a/src/corelib/global/qnumeric.h b/src/corelib/global/qnumeric.h
index 48e736ff124..4568d089590 100644
--- a/src/corelib/global/qnumeric.h
+++ b/src/corelib/global/qnumeric.h
@@ -627,6 +627,27 @@ QT_WARNING_DISABLE_FLOAT_COMPARE
QT_WARNING_POP
+namespace QtPrivate {
+/*
+ A version of qFuzzyCompare that works for all values (qFuzzyCompare()
+ requires that neither argument is numerically 0).
+
+ It's private because we need a fix for the many qFuzzyCompare() uses that
+ ignore the precondition, even for older branches.
+
+ See QTBUG-142020 for discussion of a longer-term solution.
+*/
+template <typename T, typename S>
+[[nodiscard]] constexpr bool fuzzyCompare(const T &lhs, const S &rhs) noexcept
+{
+ static_assert(noexcept(qIsNull(lhs) && qIsNull(rhs) && qFuzzyIsNull(lhs - rhs) && qFuzzyCompare(lhs, rhs)),
+ "The operations qIsNull(), qFuzzyIsNull() and qFuzzyCompare() must be noexcept"
+ "for both argument types!");
+ return qIsNull(lhs) || qIsNull(rhs) ? qFuzzyIsNull(lhs - rhs) : qFuzzyCompare(lhs, rhs);
+}
+} // namespace QtPrivate
+
+
inline int qIntCast(double f) { return int(f); }
inline int qIntCast(float f) { return int(f); }
diff --git a/src/corelib/global/qtdeprecationmarkers.h b/src/corelib/global/qtdeprecationmarkers.h
index 6ebeb3afe89..6a6efe81b9f 100644
--- a/src/corelib/global/qtdeprecationmarkers.h
+++ b/src/corelib/global/qtdeprecationmarkers.h
@@ -258,6 +258,14 @@ QT_BEGIN_NAMESPACE
# define QT_DEPRECATED_VERSION_6_14
#endif
+#if QT_WARN_DEPRECATED_UP_TO >= QT_VERSION_CHECK(6, 15, 0)
+# define QT_DEPRECATED_VERSION_X_6_15(text) QT_DEPRECATED_X(text)
+# define QT_DEPRECATED_VERSION_6_15 QT_DEPRECATED
+#else
+# define QT_DEPRECATED_VERSION_X_6_15(text)
+# define QT_DEPRECATED_VERSION_6_15
+#endif
+
#define QT_DEPRECATED_VERSION_X_5(minor, text) QT_DEPRECATED_VERSION_X_5_##minor(text)
#define QT_DEPRECATED_VERSION_X(major, minor, text) QT_DEPRECATED_VERSION_X_##major##_##minor(text)
diff --git a/src/corelib/io/qioring.cpp b/src/corelib/io/qioring.cpp
index 28849b49b04..2eb013e24fc 100644
--- a/src/corelib/io/qioring.cpp
+++ b/src/corelib/io/qioring.cpp
@@ -8,6 +8,20 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQIORing, "qt.core.ioring", QtCriticalMsg)
+QIORing *QIORing::sharedInstance()
+{
+ thread_local QIORing instance;
+ if (!instance.initializeIORing())
+ return nullptr;
+ return &instance;
+}
+
+QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize)
+ : sqEntries(submissionQueueSize), cqEntries(completionQueueSize)
+{
+ // Destructor in respective _<platform>.cpp
+}
+
auto QIORing::queueRequestInternal(GenericRequestType &request) -> QueuedRequestStatus
{
if (!ensureInitialized() || preparingRequests) { // preparingRequests protects against recursing
@@ -65,12 +79,20 @@ template <typename T>
constexpr bool HasResultMember = qxp::is_detected_v<DetectResult, T>;
}
+void QIORing::setFileErrorResult(QIORing::GenericRequestType &req, QFileDevice::FileError error)
+{
+ invokeOnOp(req, [error](auto *concreteRequest) {
+ if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>)
+ setFileErrorResult(*concreteRequest, error);
+ });
+}
+
void QIORing::finishRequestWithError(QIORing::GenericRequestType &req, QFileDevice::FileError error)
{
- invokeOnOp(req, [error](auto *req) {
- if constexpr (QtPrivate::HasResultMember<decltype(*req)>)
- req->result.template emplace<QFileDevice::FileError>(error);
- invokeCallback(*req);
+ invokeOnOp(req, [error](auto *concreteRequest) {
+ if constexpr (QtPrivate::HasResultMember<decltype(*concreteRequest)>)
+ setFileErrorResult(*concreteRequest, error);
+ invokeCallback(*concreteRequest);
});
}
diff --git a/src/corelib/io/qioring_linux.cpp b/src/corelib/io/qioring_linux.cpp
index b296b916c81..2b5865f3c2d 100644
--- a/src/corelib/io/qioring_linux.cpp
+++ b/src/corelib/io/qioring_linux.cpp
@@ -35,19 +35,6 @@ static io_uring_op toUringOp(QIORing::Operation op);
static void prepareFileReadWrite(io_uring_sqe *sqe, const QIORingRequestOffsetFdBase &request,
const void *address, qsizetype size);
-
-QIORing *QIORing::sharedInstance()
-{
- thread_local QIORing instance;
- if (!instance.initializeIORing())
- return nullptr;
- return &instance;
-}
-
-QIORing::QIORing(quint32 submissionQueueSize, quint32 completionQueueSize)
- : sqEntries(submissionQueueSize), cqEntries(completionQueueSize)
-{
-}
QIORing::~QIORing()
{
if (eventDescriptor != -1)
diff --git a/src/corelib/io/qioring_p.h b/src/corelib/io/qioring_p.h
index d4c4308122e..0db832bc6bf 100644
--- a/src/corelib/io/qioring_p.h
+++ b/src/corelib/io/qioring_p.h
@@ -22,7 +22,6 @@
#include <QtCore/qspan.h>
#include <QtCore/qhash.h>
#include <QtCore/qfiledevice.h>
-#include <QtCore/qwineventnotifier.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qdeadlinetimer.h>
@@ -30,10 +29,15 @@
# include <QtCore/qsocketnotifier.h>
struct io_uring_sqe;
struct io_uring_cqe;
+#elif defined(Q_OS_WIN)
+# include <QtCore/qwineventnotifier.h>
+# include <qt_windows.h>
+# include <ioringapi.h>
#endif
#include <algorithm>
#include <filesystem>
+#include <QtCore/qxpfunctional.h>
#include <variant>
#include <optional>
#include <type_traits>
@@ -162,6 +166,12 @@ private:
template <typename Fun>
static auto invokeOnOp(GenericRequestType &req, Fun fn);
+ template <Operation Op>
+ static void setFileErrorResult(QIORingRequest<Op> &req, QFileDevice::FileError error)
+ {
+ req.result.template emplace<QFileDevice::FileError>(error);
+ }
+ static void setFileErrorResult(GenericRequestType &req, QFileDevice::FileError error);
static void finishRequestWithError(GenericRequestType &req, QFileDevice::FileError error);
static bool verifyFd(GenericRequestType &req);
@@ -205,6 +215,28 @@ private:
ReadWriteStatus handleReadCompletion(const io_uring_cqe *cqe, GenericRequestType *request);
template <Operation Op>
ReadWriteStatus handleWriteCompletion(const io_uring_cqe *cqe, GenericRequestType *request);
+#elif defined(Q_OS_WIN)
+ // We use UINT32 because that's the type used for size parameters in their API.
+ static constexpr qsizetype MaxReadWriteLen = std::numeric_limits<UINT32>::max();
+ std::optional<QWinEventNotifier> notifier;
+ HIORING ioRingHandle = nullptr;
+ HANDLE eventHandle = INVALID_HANDLE_VALUE;
+
+ bool initialized = false;
+ bool queueWasFull = false;
+ [[nodiscard]]
+ RequestPrepResult prepareRequest(GenericRequestType &request);
+ QIORing::ReadWriteStatus handleReadCompletion(
+ HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra,
+ qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult);
+ template <Operation Op>
+ ReadWriteStatus handleReadCompletion(const IORING_CQE *cqe, GenericRequestType *request);
+ ReadWriteStatus handleWriteCompletion(
+ HRESULT result, quintptr information, const QSpan<const std::byte> *sources,
+ void *voidExtra,
+ qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResult);
+ template <Operation Op>
+ ReadWriteStatus handleWriteCompletion(const IORING_CQE *cqe, GenericRequestType *request);
#endif
};
@@ -243,6 +275,7 @@ struct QIORingRequestBase : Base
template <>
struct QIORingResult<QtPrivate::Operation::Open>
{
+ // On Windows this is a HANDLE
qintptr fd;
};
template <>
@@ -260,6 +293,7 @@ template <>
struct QIORingRequest<QtPrivate::Operation::Close> final
: QIORingRequestBase<QtPrivate::Operation::Close, QIORingRequestEmptyBase>
{
+ // On Windows this is a HANDLE
qintptr fd;
};
@@ -318,6 +352,7 @@ struct QIORingResult<QtPrivate::Operation::Flush> final
template <>
struct QIORingRequest<QtPrivate::Operation::Flush> final : QIORingRequestBase<QtPrivate::Operation::Flush, QIORingRequestEmptyBase>
{
+ // On Windows this is a HANDLE
qintptr fd;
};
@@ -330,6 +365,7 @@ template <>
struct QIORingRequest<QtPrivate::Operation::Stat> final
: QIORingRequestBase<QtPrivate::Operation::Stat, QIORingRequestEmptyBase>
{
+ // On Windows this is a HANDLE
qintptr fd;
};
@@ -473,6 +509,7 @@ namespace QtPrivate {
// The 'extra' struct for Read/Write operations that must be split up
struct ReadWriteExtra
{
+ qint64 totalProcessed = 0;
qsizetype spanIndex = 0;
qsizetype spanOffset = 0;
qsizetype numSpans = 1;
diff --git a/src/corelib/io/qioring_win.cpp b/src/corelib/io/qioring_win.cpp
new file mode 100644
index 00000000000..42c51f428d6
--- /dev/null
+++ b/src/corelib/io/qioring_win.cpp
@@ -0,0 +1,754 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#include "qioring_p.h"
+
+QT_REQUIRE_CONFIG(windows_ioring);
+
+#include <QtCore/qcompilerdetection.h>
+#include <QtCore/qobject.h>
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <qt_windows.h>
+#include <ioringapi.h>
+
+#include <QtCore/q26numeric.h>
+
+QT_BEGIN_NAMESPACE
+
+// We don't really build for 32-bit windows anymore, but this code is definitely wrong if someone
+// does.
+static_assert(sizeof(qsizetype) > sizeof(UINT32),
+ "This code is written with assuming 64-bit Windows.");
+
+using namespace Qt::StringLiterals;
+
+static HRESULT buildReadOperation(HIORING ioRingHandle, qintptr fd, QSpan<std::byte> destination,
+ quint64 offset, quintptr userData)
+{
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ const IORING_HANDLE_REF fileRef((HANDLE(fd)));
+ const IORING_BUFFER_REF bufferRef(destination.data());
+ const auto maxSize = q26::saturate_cast<UINT32>(destination.size());
+ Q_ASSERT(maxSize == destination.size());
+ return BuildIoRingReadFile(ioRingHandle, fileRef, bufferRef, maxSize, offset, userData,
+ IOSQE_FLAGS_NONE);
+}
+
+static HRESULT buildWriteOperation(HIORING ioRingHandle, qintptr fd, QSpan<const std::byte> source,
+ quint64 offset, quintptr userData)
+{
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ const IORING_HANDLE_REF fileRef((HANDLE(fd)));
+ const IORING_BUFFER_REF bufferRef(const_cast<std::byte *>(source.data()));
+ const auto maxSize = q26::saturate_cast<UINT32>(source.size());
+ Q_ASSERT(maxSize == source.size());
+ // @todo: FILE_WRITE_FLAGS can be set to write-through, could be used for Unbuffered mode.
+ return BuildIoRingWriteFile(ioRingHandle, fileRef, bufferRef, maxSize, offset,
+ FILE_WRITE_FLAGS_NONE, userData, IOSQE_FLAGS_NONE);
+}
+
+QIORing::~QIORing()
+{
+ if (initialized) {
+ CloseHandle(eventHandle);
+ CloseIoRing(ioRingHandle);
+ }
+}
+
+bool QIORing::initializeIORing()
+{
+ if (initialized)
+ return true;
+
+ IORING_CAPABILITIES capabilities;
+ QueryIoRingCapabilities(&capabilities);
+ if (capabilities.MaxVersion < IORING_VERSION_3) // 3 adds write, flush and drain
+ return false;
+ if ((capabilities.FeatureFlags & IORING_FEATURE_SET_COMPLETION_EVENT) == 0)
+ return false; // We currently require the SET_COMPLETION_EVENT feature
+
+ qCDebug(lcQIORing) << "Creating QIORing, requesting space for" << sqEntries
+ << "submission queue entries, and" << cqEntries
+ << "completion queue entries";
+
+ IORING_CREATE_FLAGS flags;
+ memset(&flags, 0, sizeof(flags));
+ HRESULT hr = CreateIoRing(IORING_VERSION_3, flags, sqEntries, cqEntries, &ioRingHandle);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "failed to initialize QIORing");
+ return false;
+ }
+ auto earlyExitCleanup = qScopeGuard([this]() {
+ if (eventHandle != INVALID_HANDLE_VALUE)
+ CloseHandle(eventHandle);
+ CloseIoRing(ioRingHandle);
+ });
+ eventHandle = CreateEvent(nullptr, TRUE, FALSE, nullptr);
+ if (eventHandle == INVALID_HANDLE_VALUE) {
+ qErrnoWarning("Failed to create event handle");
+ return false;
+ }
+ notifier.emplace(eventHandle);
+ hr = SetIoRingCompletionEvent(ioRingHandle, eventHandle);
+ if (FAILED(hr)) {
+ qErrnoWarning(hr, "Failed to assign the event handle to QIORing");
+ return false;
+ }
+ IORING_INFO info;
+ if (SUCCEEDED(GetIoRingInfo(ioRingHandle, &info))) {
+ sqEntries = info.SubmissionQueueSize;
+ cqEntries = info.CompletionQueueSize;
+ qCDebug(lcQIORing) << "QIORing configured with capacity for" << sqEntries
+ << "submissions, and" << cqEntries << "completions.";
+ }
+ QObject::connect(std::addressof(*notifier), &QWinEventNotifier::activated,
+ std::addressof(*notifier), [this]() { completionReady(); });
+ initialized = true;
+ earlyExitCleanup.dismiss();
+ return true;
+}
+
+QIORing::ReadWriteStatus QIORing::handleReadCompletion(
+ HRESULT result, quintptr information, QSpan<std::byte> *destinations, void *voidExtra,
+ qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn)
+{
+ if (FAILED(result)) {
+ if (result == HRESULT_FROM_WIN32(ERROR_HANDLE_EOF))
+ return ReadWriteStatus::Finished;
+
+ if (result == E_ABORT)
+ setResultFn(QFileDevice::AbortError);
+ else
+ setResultFn(QFileDevice::ReadError);
+ } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) {
+ const qsizetype bytesRead = q26::saturate_cast<decltype(MaxReadWriteLen)>(information);
+ qCDebug(lcQIORing) << "Partial read of" << bytesRead << "bytes completed";
+ extra->totalProcessed = setResultFn(bytesRead);
+ extra->spanOffset += bytesRead;
+ qCDebug(lcQIORing) << "Read operation progress: span" << extra->spanIndex << "offset"
+ << extra->spanOffset << "of" << destinations[extra->spanIndex].size()
+ << "bytes. Total read:" << extra->totalProcessed << "bytes";
+ // The while loop is in case there is an empty span, we skip over it:
+ while (extra->spanOffset == destinations[extra->spanIndex].size()) {
+ // Move to next span
+ if (++extra->spanIndex == extra->numSpans)
+ return ReadWriteStatus::Finished;
+ extra->spanOffset = 0;
+ }
+ return ReadWriteStatus::MoreToDo;
+ } else {
+ setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information));
+ }
+ return ReadWriteStatus::Finished;
+}
+
+template <QIORing::Operation Op>
+Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleReadCompletion(const IORING_CQE *cqe,
+ GenericRequestType *request)
+{
+ static_assert(Op == Operation::Read || Op == Operation::VectoredRead);
+ QIORingRequest<Op> *readRequest = request->requestData<Op>();
+ Q_ASSERT(readRequest);
+ auto *destinations = [&readRequest]() {
+ if constexpr (Op == Operation::Read)
+ return &readRequest->destination;
+ else
+ return &readRequest->destinations[0];
+ }();
+ auto setResult = [readRequest](const std::variant<QFileDevice::FileError, qint64> &result) {
+ if (result.index() == 0) { // error
+ QIORing::setFileErrorResult(*readRequest, *std::get_if<QFileDevice::FileError>(&result));
+ return 0ll;
+ }
+ // else: success
+ auto &readResult = [&readRequest]() -> QIORingResult<Op> & {
+ if (auto *result = std::get_if<QIORingResult<Op>>(&readRequest->result))
+ return *result;
+ return readRequest->result.template emplace<QIORingResult<Op>>();
+ }();
+ qint64 bytesRead = *std::get_if<qint64>(&result);
+ readResult.bytesRead += bytesRead;
+ return readResult.bytesRead;
+ };
+ QIORing::ReadWriteStatus rwstatus = handleReadCompletion(
+ cqe->ResultCode, cqe->Information, destinations, request->getExtra<void>(), setResult);
+ switch (rwstatus) {
+ case ReadWriteStatus::Finished:
+ if (request->getExtra<void>())
+ --ongoingSplitOperations;
+ break;
+ case ReadWriteStatus::MoreToDo: {
+ // Move the request such that it is next in the list to be processed:
+ auto &it = addrItMap[request];
+ const auto where = lastUnqueuedIterator.value_or(pendingRequests.end());
+ pendingRequests.splice(where, pendingRequests, it);
+ it = std::prev(where);
+ lastUnqueuedIterator = it;
+ break;
+ }
+ }
+ return rwstatus;
+}
+
+QIORing::ReadWriteStatus QIORing::handleWriteCompletion(
+ HRESULT result, quintptr information, const QSpan<const std::byte> *sources, void *voidExtra,
+ qxp::function_ref<qint64(std::variant<QFileDevice::FileError, qint64>)> setResultFn)
+{
+ if (FAILED(result)) {
+ if (result == E_ABORT)
+ setResultFn(QFileDevice::AbortError);
+ else
+ setResultFn(QFileDevice::WriteError);
+ } else if (auto *extra = static_cast<QtPrivate::ReadWriteExtra *>(voidExtra)) {
+ const qsizetype bytesWritten = q26::saturate_cast<decltype(MaxReadWriteLen)>(information);
+ qCDebug(lcQIORing) << "Partial write of" << bytesWritten << "bytes completed";
+ extra->totalProcessed = setResultFn(bytesWritten);
+ extra->spanOffset += bytesWritten;
+ qCDebug(lcQIORing) << "Write operation progress: span" << extra->spanIndex << "offset"
+ << extra->spanOffset << "of" << sources[extra->spanIndex].size()
+ << "bytes. Total written:" << extra->totalProcessed << "bytes";
+ // The while loop is in case there is an empty span, we skip over it:
+ while (extra->spanOffset == sources[extra->spanIndex].size()) {
+ // Move to next span
+ if (++extra->spanIndex == extra->numSpans)
+ return ReadWriteStatus::Finished;
+ extra->spanOffset = 0;
+ }
+ return ReadWriteStatus::MoreToDo;
+ } else {
+ setResultFn(q26::saturate_cast<decltype(MaxReadWriteLen)>(information));
+ }
+ return ReadWriteStatus::Finished;
+}
+
+template <QIORing::Operation Op>
+Q_ALWAYS_INLINE QIORing::ReadWriteStatus QIORing::handleWriteCompletion(const IORING_CQE *cqe,
+ GenericRequestType *request)
+{
+ static_assert(Op == Operation::Write || Op == Operation::VectoredWrite);
+ QIORingRequest<Op> *writeRequest = request->requestData<Op>();
+ auto *sources = [&writeRequest]() {
+ if constexpr (Op == Operation::Write)
+ return &writeRequest->source;
+ else
+ return &writeRequest->sources[0];
+ }();
+ auto setResult = [writeRequest](const std::variant<QFileDevice::FileError, qint64> &result) {
+ if (result.index() == 0) { // error
+ QIORing::setFileErrorResult(*writeRequest, *std::get_if<QFileDevice::FileError>(&result));
+ return 0ll;
+ }
+ // else: success
+ auto &writeResult = [&writeRequest]() -> QIORingResult<Op> & {
+ if (auto *result = std::get_if<QIORingResult<Op>>(&writeRequest->result))
+ return *result;
+ return writeRequest->result.template emplace<QIORingResult<Op>>();
+ }();
+ qint64 bytesWritten = *std::get_if<qint64>(&result);
+ writeResult.bytesWritten += bytesWritten;
+ return writeResult.bytesWritten;
+ };
+ QIORing::ReadWriteStatus rwstatus = handleWriteCompletion(
+ cqe->ResultCode, cqe->Information, sources, request->getExtra<void>(), setResult);
+ switch (rwstatus) {
+ case ReadWriteStatus::Finished:
+ if (request->getExtra<void>())
+ --ongoingSplitOperations;
+ break;
+ case ReadWriteStatus::MoreToDo: {
+ // Move the request such that it is next in the list to be processed:
+ auto &it = addrItMap[request];
+ const auto where = lastUnqueuedIterator.value_or(pendingRequests.end());
+ pendingRequests.splice(where, pendingRequests, it);
+ it = std::prev(where);
+ lastUnqueuedIterator = it;
+ break;
+ }
+ }
+ return rwstatus;
+}
+
+void QIORing::completionReady()
+{
+ ResetEvent(eventHandle);
+ IORING_CQE entry;
+ while (PopIoRingCompletion(ioRingHandle, &entry) == S_OK) {
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ auto *request = reinterpret_cast<GenericRequestType *>(entry.UserData);
+ if (!addrItMap.contains(request)) {
+ qCDebug(lcQIORing) << "Got completed entry, but cannot find it in the map. Likely "
+ "deleted, ignoring. UserData pointer:"
+ << request;
+ continue;
+ }
+ qCDebug(lcQIORing) << "Got completed entry. Operation:" << request->operation()
+ << "- UserData pointer:" << request
+ << "- Result:" << qt_error_string(entry.ResultCode) << '('
+ << QByteArray("0x"_ba + QByteArray::number(entry.ResultCode, 16)).data()
+ << ')';
+ switch (request->operation()) {
+ case Operation::Open: // Synchronously finishes
+ Q_UNREACHABLE_RETURN();
+ case Operation::Close: {
+ auto closeRequest = request->takeRequestData<Operation::Close>();
+ // We ignore the result of the flush, we are closing the handle anyway.
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ if (CloseHandle(HANDLE(closeRequest.fd)))
+ closeRequest.result.emplace<QIORingResult<Operation::Close>>();
+ else
+ closeRequest.result.emplace<QFileDevice::FileError>(QFileDevice::OpenError);
+ invokeCallback(closeRequest);
+ break;
+ }
+ case Operation::Read: {
+ const ReadWriteStatus status = handleReadCompletion<Operation::Read>(&entry, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto readRequest = request->takeRequestData<Operation::Read>();
+ invokeCallback(readRequest);
+ break;
+ }
+ case Operation::Write: {
+ const ReadWriteStatus status = handleWriteCompletion<Operation::Write>(&entry, request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto writeRequest = request->takeRequestData<Operation::Write>();
+ invokeCallback(writeRequest);
+ break;
+ }
+ case Operation::VectoredRead: {
+ const ReadWriteStatus status = handleReadCompletion<Operation::VectoredRead>(&entry,
+ request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto vectoredReadRequest = request->takeRequestData<Operation::VectoredRead>();
+ invokeCallback(vectoredReadRequest);
+ break;
+ }
+ case Operation::VectoredWrite: {
+ const ReadWriteStatus status = handleWriteCompletion<Operation::VectoredWrite>(&entry,
+ request);
+ if (status == ReadWriteStatus::MoreToDo)
+ continue;
+ auto vectoredWriteRequest = request->takeRequestData<Operation::VectoredWrite>();
+ invokeCallback(vectoredWriteRequest);
+ break;
+ }
+ case Operation::Flush: {
+ auto flushRequest = request->takeRequestData<Operation::Flush>();
+ if (FAILED(entry.ResultCode)) {
+ qErrnoWarning(entry.ResultCode, "Flush operation failed");
+ // @todo any FlushError?
+ flushRequest.result.emplace<QFileDevice::FileError>(
+ QFileDevice::FileError::WriteError);
+ } else {
+ flushRequest.result.emplace<QIORingResult<Operation::Flush>>();
+ }
+ invokeCallback(flushRequest);
+ break;
+ }
+ case QtPrivate::Operation::Cancel: {
+ auto cancelRequest = request->takeRequestData<Operation::Cancel>();
+ invokeCallback(cancelRequest);
+ break;
+ }
+ case QtPrivate::Operation::Stat:
+ Q_UNREACHABLE_RETURN(); // Completes synchronously
+ break;
+ case Operation::NumOperations:
+ Q_UNREACHABLE_RETURN();
+ break;
+ }
+ auto it = addrItMap.take(request);
+ pendingRequests.erase(it);
+ --inFlightRequests;
+ queueWasFull = false;
+ }
+ prepareRequests();
+ if (unstagedRequests > 0)
+ submitRequests();
+}
+
+bool QIORing::waitForCompletions(QDeadlineTimer deadline)
+{
+ notifier->setEnabled(false);
+ auto reactivateNotifier = qScopeGuard([this]() {
+ notifier->setEnabled(true);
+ });
+
+ while (!deadline.hasExpired()) {
+ DWORD timeout = 0;
+ if (deadline.isForever()) {
+ timeout = INFINITE;
+ } else {
+ timeout = q26::saturate_cast<DWORD>(deadline.remainingTime());
+ if (timeout == INFINITE)
+ --timeout;
+ }
+ if (WaitForSingleObject(eventHandle, timeout) == WAIT_OBJECT_0)
+ return true;
+ }
+ return false;
+}
+
+static HANDLE openFile(const QIORingRequest<QIORing::Operation::Open> &openRequest)
+{
+ DWORD access = 0;
+ if (openRequest.flags.testFlag(QIODevice::ReadOnly))
+ access |= GENERIC_READ;
+ if (openRequest.flags.testFlag(QIODevice::WriteOnly))
+ access |= GENERIC_WRITE;
+
+ DWORD disposition = 0;
+ if (openRequest.flags.testFlag(QIODevice::Append)) {
+ qCWarning(lcQIORing, "Opening file with Append not supported for random access file");
+ return INVALID_HANDLE_VALUE;
+ }
+ if (openRequest.flags.testFlag(QIODevice::NewOnly)) {
+ disposition = CREATE_NEW;
+ } else {
+ // If Write is specified we _may_ create a file.
+ // See qfsfileengine_p.h openModeCanCreate.
+ disposition = openRequest.flags.testFlag(QIODeviceBase::WriteOnly)
+ && !openRequest.flags.testFlags(QIODeviceBase::ExistingOnly)
+ ? OPEN_ALWAYS
+ : OPEN_EXISTING;
+ }
+ const DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
+ const DWORD flagsAndAttribs = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
+ HANDLE h = CreateFile(openRequest.path.native().c_str(), access, shareMode, nullptr,
+ disposition, flagsAndAttribs, nullptr);
+ if (h != INVALID_HANDLE_VALUE && openRequest.flags.testFlag(QIODeviceBase::Truncate)) {
+ FILE_END_OF_FILE_INFO info;
+ memset(&info, 0, sizeof(info));
+ SetFileInformationByHandle(h, FileEndOfFileInfo, &info, sizeof(info));
+ }
+ return h;
+}
+
+bool QIORing::supportsOperation(Operation op)
+{
+ switch (op) {
+ case QtPrivate::Operation::Open:
+ case QtPrivate::Operation::Close:
+ case QtPrivate::Operation::Read:
+ case QtPrivate::Operation::Write:
+ case QtPrivate::Operation::Flush:
+ case QtPrivate::Operation::Cancel:
+ case QtPrivate::Operation::Stat:
+ case QtPrivate::Operation::VectoredRead:
+ case QtPrivate::Operation::VectoredWrite:
+ return true;
+ case QtPrivate::Operation::NumOperations:
+ return false;
+ }
+ return false; // Not unreachable, we could allow more for io_uring
+}
+
+void QIORing::submitRequests()
+{
+ stagePending = false;
+ if (unstagedRequests == 0)
+ return;
+
+ // We perform a miniscule wait - to see if anything already in the queue is already completed -
+ // if we have been told the queue is full. Then we can try queuing more things right away
+ const bool shouldTryWait = std::exchange(queueWasFull, false);
+ const auto submitToRing = [this, &shouldTryWait] {
+ quint32 submittedEntries = 0;
+ HRESULT hr = SubmitIoRing(ioRingHandle, shouldTryWait ? 1 : 0, 1, &submittedEntries);
+ qCDebug(lcQIORing) << "Submitted" << submittedEntries << "requests";
+ unstagedRequests -= submittedEntries;
+ if (FAILED(hr)) {
+ // Too noisy, not a real problem
+ // qErrnoWarning(hr, "Failed to submit QIORing request: %u", submittedEntries);
+ return false;
+ }
+ return submittedEntries > 0;
+ };
+ if (submitToRing() && shouldTryWait) {
+ // We try to prepare some more request and submit more if able
+ prepareRequests();
+ if (unstagedRequests > 0)
+ submitToRing();
+ }
+}
+
+void QIORing::prepareRequests()
+{
+ if (!lastUnqueuedIterator)
+ return;
+ Q_ASSERT(!preparingRequests);
+ QScopedValueRollback<bool> prepareGuard(preparingRequests, true);
+
+ auto it = *lastUnqueuedIterator;
+ lastUnqueuedIterator.reset();
+ const auto end = pendingRequests.end();
+ while (!queueWasFull && it != end) {
+ auto &request = *it;
+ switch (prepareRequest(request)) {
+ case RequestPrepResult::Ok:
+ ++unstagedRequests;
+ ++inFlightRequests;
+ break;
+ case RequestPrepResult::QueueFull:
+ qCDebug(lcQIORing) << "Queue was reported as full, in flight requests:"
+ << inFlightRequests << "submission queue size:" << sqEntries
+ << "completion queue size:" << cqEntries;
+ queueWasFull = true;
+ lastUnqueuedIterator = it;
+ return;
+ case RequestPrepResult::Defer:
+ qCDebug(lcQIORing) << "Request for" << request.operation()
+ << "had to be deferred, will not queue any more requests at the "
+ "moment.";
+ lastUnqueuedIterator = it;
+ return; //
+ case RequestPrepResult::RequestCompleted:
+ // Used for requests that immediately finish. So we erase it:
+ qCDebug(lcQIORing) << "Request for" << request.operation()
+ << "completed synchronously.";
+ addrItMap.remove(&request);
+ it = pendingRequests.erase(it);
+ continue; // Don't increment iterator again
+ }
+ ++it;
+ }
+}
+
+namespace QtPrivate {
+template <typename T>
+using DetectHasFd = decltype(std::declval<const T &>().fd);
+
+template <typename T>
+constexpr bool OperationHasFd_v = qxp::is_detected_v<DetectHasFd, T>;
+} // namespace QtPrivate
+
+auto QIORing::prepareRequest(GenericRequestType &request) -> RequestPrepResult
+{
+ qCDebug(lcQIORing) << "Preparing a request with operation" << request.operation();
+ HRESULT hr = -1;
+
+ if (!verifyFd(request)) {
+ finishRequestWithError(request, QFileDevice::OpenError);
+ return RequestPrepResult::RequestCompleted;
+ }
+
+ switch (request.operation()) {
+ case Operation::Open: {
+ QIORingRequest<Operation::Open> openRequest = request.takeRequestData<Operation::Open>();
+ HANDLE fileDescriptor = openFile(openRequest);
+ if (fileDescriptor == INVALID_HANDLE_VALUE) {
+ openRequest.result.emplace<QFileDevice::FileError>(QFileDevice::FileError::OpenError);
+ } else {
+ auto &result = openRequest.result.emplace<QIORingResult<Operation::Open>>();
+ result.fd = qintptr(fileDescriptor);
+ }
+ invokeCallback(openRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ case Operation::Close: {
+ if (ongoingSplitOperations > 0)
+ return RequestPrepResult::Defer;
+
+ // We need to wait until all previous OPS are done before we close the request.
+ // There is no no-op request in the Windows QIORing, so we issue a flush.
+ auto *closeRequest = request.requestData<Operation::Close>();
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ const IORING_HANDLE_REF fileRef(HANDLE(closeRequest->fd));
+ hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_MIN_METADATA,
+ quintptr(std::addressof(request)),
+ IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
+ break;
+ }
+ case Operation::Read: {
+ auto *readRequest = request.requestData<Operation::Read>();
+ auto span = readRequest->destination;
+ auto offset = readRequest->offset;
+ if (span.size() > MaxReadWriteLen) {
+ qCDebug(lcQIORing) << "Requested Read of size" << span.size() << "has to be split";
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ if (extra->spanOffset == 0)
+ ++ongoingSplitOperations;
+ const qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ offset += extra->totalProcessed;
+ }
+ hr = buildReadOperation(ioRingHandle, readRequest->fd, span, offset,
+ quintptr(std::addressof(request)));
+ break;
+ }
+ case Operation::VectoredRead: {
+ auto *vectoredReadRequest = request.requestData<Operation::VectoredRead>();
+ auto span = vectoredReadRequest->destinations.front();
+ auto offset = vectoredReadRequest->offset;
+ if (Q_LIKELY(vectoredReadRequest->destinations.size() > 1
+ || span.size() > MaxReadWriteLen)) {
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ if (extra->spanOffset == 0 && extra->spanIndex == 0)
+ ++ongoingSplitOperations;
+ extra->numSpans = vectoredReadRequest->destinations.size();
+
+ span = vectoredReadRequest->destinations[extra->spanIndex];
+
+ const qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ offset += extra->totalProcessed;
+ }
+ hr = buildReadOperation(ioRingHandle, vectoredReadRequest->fd, span,
+ offset, quintptr(std::addressof(request)));
+ break;
+ }
+ case Operation::Write: {
+ auto *writeRequest = request.requestData<Operation::Write>();
+ auto span = writeRequest->source;
+ auto offset = writeRequest->offset;
+ if (span.size() > MaxReadWriteLen) {
+ qCDebug(lcQIORing) << "Requested Write of size" << span.size() << "has to be split";
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ if (extra->spanOffset == 0)
+ ++ongoingSplitOperations;
+ const qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ offset += extra->totalProcessed;
+ }
+ hr = buildWriteOperation(ioRingHandle, writeRequest->fd, span, offset,
+ quintptr(std::addressof(request)));
+ break;
+ }
+ case Operation::VectoredWrite: {
+ auto *vectoredWriteRequest = request.requestData<Operation::VectoredWrite>();
+ auto span = vectoredWriteRequest->sources.front();
+ auto offset = vectoredWriteRequest->offset;
+ if (Q_LIKELY(vectoredWriteRequest->sources.size() > 1
+ || span.size() > MaxReadWriteLen)) {
+ auto *extra = request.getOrInitializeExtra<QtPrivate::ReadWriteExtra>();
+ if (extra->spanOffset == 0 && extra->spanIndex == 0)
+ ++ongoingSplitOperations;
+ extra->numSpans = vectoredWriteRequest->sources.size();
+
+ span = vectoredWriteRequest->sources[extra->spanIndex];
+
+ const qsizetype remaining = span.size() - extra->spanOffset;
+ span.slice(extra->spanOffset, std::min(remaining, MaxReadWriteLen));
+ offset += extra->totalProcessed;
+ }
+ hr = buildWriteOperation(ioRingHandle, vectoredWriteRequest->fd, span,
+ offset, quintptr(std::addressof(request)));
+ break;
+ }
+ case Operation::Flush: {
+ if (ongoingSplitOperations > 0)
+ return RequestPrepResult::Defer;
+ auto *flushRequest = request.requestData<Operation::Flush>();
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ const IORING_HANDLE_REF fileRef(HANDLE(flushRequest->fd));
+ hr = BuildIoRingFlushFile(ioRingHandle, fileRef, FILE_FLUSH_DEFAULT,
+ quintptr(std::addressof(request)),
+ IOSQE_FLAGS_DRAIN_PRECEDING_OPS);
+ break;
+ }
+ case QtPrivate::Operation::Stat: {
+ auto statRequest = request.takeRequestData<Operation::Stat>();
+ FILE_STANDARD_INFO info;
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ if (!GetFileInformationByHandleEx(HANDLE(statRequest.fd), FileStandardInfo, &info,
+ sizeof(info))) {
+ DWORD winErr = GetLastError();
+ QFileDevice::FileError error = QFileDevice::UnspecifiedError;
+ if (winErr == ERROR_FILE_NOT_FOUND || winErr == ERROR_INVALID_HANDLE)
+ error = QFileDevice::OpenError;
+ else if (winErr == ERROR_ACCESS_DENIED)
+ error = QFileDevice::PermissionsError;
+ statRequest.result.emplace<QFileDevice::FileError>(error);
+ } else {
+ auto &result = statRequest.result.emplace<QIORingResult<Operation::Stat>>();
+ result.size = info.EndOfFile.QuadPart;
+ }
+ invokeCallback(statRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ case Operation::Cancel: {
+ auto *cancelRequest = request.requestData<Operation::Cancel>();
+ auto *otherOperation = reinterpret_cast<GenericRequestType *>(cancelRequest->handle);
+ if (!otherOperation || !addrItMap.contains(otherOperation)) {
+ qCDebug(lcQIORing, "Invalid cancel for non-existant operation");
+ invokeCallback(*cancelRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ qCDebug(lcQIORing) << "Cancelling operation of type" << otherOperation->operation()
+ << "which was"
+ << (otherOperation->wasQueued() ? "queued" : "not queued");
+ Q_ASSERT(&request != otherOperation);
+ if (!otherOperation->wasQueued()) {
+ // The request hasn't been queued yet, so we can just drop it from
+ // the pending requests and call the callback.
+ auto it = addrItMap.take(otherOperation);
+ finishRequestWithError(*otherOperation, QFileDevice::AbortError);
+ pendingRequests.erase(it); // otherOperation is deleted
+ invokeCallback(*cancelRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ qintptr fd = -1;
+ invokeOnOp(*otherOperation, [&fd](auto *request) {
+ if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>)
+ fd = request->fd;
+ });
+ if (fd == -1) {
+ qCDebug(lcQIORing, "Invalid cancel for non-existant fd");
+ invokeCallback(*cancelRequest);
+ return RequestPrepResult::RequestCompleted;
+ }
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ const IORING_HANDLE_REF fileRef((HANDLE(fd)));
+ hr = BuildIoRingCancelRequest(ioRingHandle, fileRef, quintptr(otherOperation),
+ quintptr(std::addressof(request)));
+ break;
+ }
+ case Operation::NumOperations:
+ Q_UNREACHABLE_RETURN(RequestPrepResult::RequestCompleted);
+ break;
+ }
+ if (hr == IORING_E_SUBMISSION_QUEUE_FULL)
+ return RequestPrepResult::QueueFull;
+ if (FAILED(hr)) {
+ finishRequestWithError(request, QFileDevice::UnspecifiedError);
+ return RequestPrepResult::RequestCompleted;
+ }
+ request.setQueued(true);
+ return RequestPrepResult::Ok;
+}
+
+bool QIORing::verifyFd(GenericRequestType &req)
+{
+ bool result = true;
+ invokeOnOp(req, [&](auto *request) {
+ if constexpr (QtPrivate::OperationHasFd_v<decltype(*request)>) {
+ result = quintptr(request->fd) > 0 && quintptr(request->fd) != quintptr(INVALID_HANDLE_VALUE);
+ }
+ });
+ return result;
+}
+
+void QIORing::GenericRequestType::cleanupExtra(Operation op, void *extra)
+{
+ switch (op) {
+ case QtPrivate::Operation::Read:
+ case QtPrivate::Operation::VectoredRead:
+ case QtPrivate::Operation::Write:
+ case QtPrivate::Operation::VectoredWrite:
+ delete static_cast<QtPrivate::ReadWriteExtra *>(extra);
+ break;
+ case QtPrivate::Operation::Open:
+ case QtPrivate::Operation::Close:
+ case QtPrivate::Operation::Flush:
+ case QtPrivate::Operation::Stat:
+ case QtPrivate::Operation::Cancel:
+ case QtPrivate::Operation::NumOperations:
+ break;
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp
index f533605c721..f37812876ea 100644
--- a/src/corelib/itemmodels/qrangemodel.cpp
+++ b/src/corelib/itemmodels/qrangemodel.cpp
@@ -154,7 +154,7 @@ static bool connectPropertiesHelper(const QModelIndex &index, QObject *item, QOb
const QHash<int, QMetaProperty> &properties)
{
if (!item)
- return false;
+ return true;
for (auto &&[role, property] : properties.asKeyValueRange()) {
if (property.hasNotifySignal()) {
if (!Handler(index, item, context, role, property))
@@ -171,7 +171,7 @@ bool QRangeModelImplBase::connectProperty(const QModelIndex &index, QObject *ite
int role, const QMetaProperty &property)
{
if (!item)
- return false;
+ return true; // nothing to do, continue
PropertyChangedHandler handler{index, role};
auto connection = property.enclosingMetaObject()->connect(item, property.notifySignal(),
context, std::move(handler));
@@ -199,7 +199,7 @@ bool QRangeModelImplBase::connectPropertyConst(const QModelIndex &index, QObject
int role, const QMetaProperty &property)
{
if (!item)
- return false;
+ return true; // nothing to do, continue
ConstPropertyChangedHandler handler{index, role};
if (!property.enclosingMetaObject()->connect(item, property.notifySignal(),
context, std::move(handler))) {
@@ -216,6 +216,27 @@ bool QRangeModelImplBase::connectPropertiesConst(const QModelIndex &index, QObje
return connectPropertiesHelper<QRangeModelImplBase::connectPropertyConst>(index, item, context, properties);
}
+namespace QRangeModelDetails
+{
+Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index)
+{
+ QModelRoleData result[] = {
+ QModelRoleData{Qt::RangeModelAdapterRole},
+ QModelRoleData{Qt::RangeModelDataRole},
+ QModelRoleData{Qt::DisplayRole},
+ };
+ index.multiData(result);
+ QVariant variant;
+ size_t r = 0;
+ do {
+ variant = result[r].data();
+ ++r;
+ } while (!variant.isValid() && r < std::size(result));
+
+ return variant;
+}
+}
+
/*!
\class QRangeModel
\inmodule QtCore
@@ -1364,6 +1385,7 @@ void QRangeModel::resetRoleNames()
/*!
\property QRangeModel::autoConnectPolicy
+ \since 6.11
\brief if and when the model auto-connects to property changed notifications.
If QRangeModel operates on a data structure that holds the same type of
diff --git a/src/corelib/itemmodels/qrangemodel.h b/src/corelib/itemmodels/qrangemodel.h
index d15f40d37a9..b8500f9ae94 100644
--- a/src/corelib/itemmodels/qrangemodel.h
+++ b/src/corelib/itemmodels/qrangemodel.h
@@ -150,6 +150,14 @@ void QRangeModelImplBase::dataChanged(const QModelIndex &from, const QModelIndex
{
m_rangeModel->dataChanged(from, to, roles);
}
+void QRangeModelImplBase::beginResetModel()
+{
+ m_rangeModel->beginResetModel();
+}
+void QRangeModelImplBase::endResetModel()
+{
+ m_rangeModel->endResetModel();
+}
void QRangeModelImplBase::beginInsertColumns(const QModelIndex &parent, int start, int count)
{
m_rangeModel->beginInsertColumns(parent, start, count);
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index 96dacda0260..f6b08099fe7 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -23,6 +23,7 @@
#include <QtCore/qmap.h>
#include <QtCore/qscopedvaluerollback.h>
#include <QtCore/qset.h>
+#include <QtCore/qvarlengtharray.h>
#include <algorithm>
#include <functional>
@@ -741,6 +742,8 @@ namespace QRangeModelDetails
} // namespace QRangeModelDetails
class QRangeModel;
+// forward declare so that we can declare friends
+template <typename, typename, typename> class QRangeModelAdapter;
class QRangeModelImplBase : public QtPrivate::QQuasiVirtualInterface<QRangeModelImplBase>
{
@@ -903,6 +906,8 @@ protected:
inline void changePersistentIndexList(const QModelIndexList &from, const QModelIndexList &to);
inline void dataChanged(const QModelIndex &from, const QModelIndex &to,
const QList<int> &roles);
+ inline void beginResetModel();
+ inline void endResetModel();
inline void beginInsertColumns(const QModelIndex &parent, int start, int count);
inline void endInsertColumns();
inline void beginRemoveColumns(const QModelIndex &parent, int start, int count);
@@ -1199,75 +1204,49 @@ public:
return std::move(result.data());
}
+ static constexpr bool isRangeModelRole(int role)
+ {
+ return role == Qt::RangeModelDataRole
+ || role == Qt::RangeModelAdapterRole;
+ }
+
+ static constexpr bool isPrimaryRole(int role)
+ {
+ return role == Qt::DisplayRole || role == Qt::EditRole;
+ }
+
QMap<int, QVariant> itemData(const QModelIndex &index) const
{
QMap<int, QVariant> result;
- bool tried = false;
- const auto readItemData = [this, &index, &result, &tried](const auto &value){
- Q_UNUSED(this);
- Q_UNUSED(index);
- using value_type = q20::remove_cvref_t<decltype(value)>;
- using multi_role = QRangeModelDetails::is_multi_role<value_type>;
- using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
- if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) {
- using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
- tried = true;
+ if (index.isValid()) {
+ bool tried = false;
+
+ // optimisation for items backed by a QMap<int, QVariant> or equivalent
+ readAt(index, [&result, &tried](const auto &value) {
+ if constexpr (std::is_convertible_v<decltype(value), decltype(result)>) {
+ tried = true;
+ result = value;
+ }
+ });
+ if (!tried) {
const auto roles = this->itemModel().roleNames().keys();
- for (auto &role : roles) {
- if (role == Qt::RangeModelDataRole)
+ QVarLengthArray<QModelRoleData, 16> roleDataArray;
+ roleDataArray.reserve(roles.size());
+ for (auto role : roles) {
+ if (isRangeModelRole(role))
continue;
- QVariant data = ItemAccess::readRole(value, role);
- if (data.isValid())
- result[role] = std::move(data);
+ roleDataArray.emplace_back(role);
}
- } else if constexpr (multi_role()) {
- tried = true;
- if constexpr (std::is_convertible_v<value_type, decltype(result)>) {
- result = value;
- } else {
- const auto roleNames = [this]() -> QHash<int, QByteArray> {
- Q_UNUSED(this);
- if constexpr (!multi_role::int_key)
- return this->itemModel().roleNames();
- else
- return {};
- }();
- for (auto it = std::begin(value); it != std::end(value); ++it) {
- const int role = [&roleNames, key = QRangeModelDetails::key(it)]() {
- Q_UNUSED(roleNames);
- if constexpr (multi_role::int_key)
- return int(key);
- else
- return roleNames.key(key.toUtf8(), -1);
- }();
-
- if (role != -1 && role != Qt::RangeModelDataRole)
- result.insert(role, QRangeModelDetails::value(it));
- }
- }
- } else if constexpr (has_metaobject<value_type>) {
- if (row_traits::fixed_size() <= 1) {
- tried = true;
- const auto roleNames = this->itemModel().roleNames();
- const auto end = roleNames.keyEnd();
- for (auto it = roleNames.keyBegin(); it != end; ++it) {
- const int role = *it;
- if (role == Qt::RangeModelDataRole)
- continue;
- QVariant data = readRole(index, role, QRangeModelDetails::pointerTo(value));
- if (data.isValid())
- result[role] = std::move(data);
- }
+ QModelRoleDataSpan roleDataSpan(roleDataArray);
+ multiData(index, roleDataSpan);
+
+ for (auto &&roleData : std::move(roleDataSpan)) {
+ QVariant data = roleData.data();
+ if (data.isValid())
+ result[roleData.role()] = std::move(data);
}
}
- };
-
- if (index.isValid()) {
- readAt(index, readItemData);
-
- if (!tried) // no multi-role item found
- result = this->itemModel().QAbstractItemModel::itemData(index);
}
return result;
}
@@ -1282,21 +1261,34 @@ public:
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
using wrapped_value_type = QRangeModelDetails::wrapped_t<value_type>;
+ const auto readModelData = [&value](QModelRoleData &roleData){
+ const int role = roleData.role();
+ if (role == Qt::RangeModelDataRole) {
+ // Qt QML support: "modelData" role returns the entire multi-role item.
+ // QML can only use raw pointers to QObject (so we unwrap), and gadgets
+ // only by value (so we take the reference).
+ if constexpr (std::is_copy_assignable_v<wrapped_value_type>)
+ roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
+ else
+ roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
+ } else if (role == Qt::RangeModelAdapterRole) {
+ // for QRangeModelAdapter however, we want to respect smart pointer wrappers
+ if constexpr (std::is_copy_assignable_v<value_type>)
+ roleData.setData(QVariant::fromValue(value));
+ else
+ roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
+ } else {
+ return false;
+ }
+ return true;
+ };
+
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
tried = true;
for (auto &roleData : roleDataSpan) {
- if (roleData.role() == Qt::RangeModelDataRole) {
- // Qt QML support: "modelData" role returns the entire multi-role item.
- // QML can only use raw pointers to QObject (so we unwrap), and gadgets
- // only by value (so we take the reference).
- if constexpr (std::is_copy_assignable_v<wrapped_value_type>)
- roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
- else
- roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
- } else {
+ if (!readModelData(roleData))
roleData.setData(ItemAccess::readRole(value, roleData.role()));
- }
}
} else if constexpr (multi_role()) {
tried = true;
@@ -1325,15 +1317,7 @@ public:
if (row_traits::fixed_size() <= 1) {
tried = true;
for (auto &roleData : roleDataSpan) {
- if (roleData.role() == Qt::RangeModelDataRole) {
- // Qt QML support: "modelData" role returns the entire multi-role item.
- // QML can only use raw pointers to QObject (so we unwrap), and gadgets
- // only by value (so we take the reference).
- if constexpr (std::is_copy_assignable_v<wrapped_value_type>)
- roleData.setData(QVariant::fromValue(QRangeModelDetails::refTo(value)));
- else
- roleData.setData(QVariant::fromValue(QRangeModelDetails::pointerTo(value)));
- } else {
+ if (!readModelData(roleData)) {
roleData.setData(readRole(index, roleData.role(),
QRangeModelDetails::pointerTo(value)));
}
@@ -1342,7 +1326,7 @@ public:
tried = true;
for (auto &roleData : roleDataSpan) {
const int role = roleData.role();
- if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ if (isPrimaryRole(role)) {
roleData.setData(readProperty(index.column(),
QRangeModelDetails::pointerTo(value)));
} else {
@@ -1354,12 +1338,10 @@ public:
tried = true;
for (auto &roleData : roleDataSpan) {
const int role = roleData.role();
- if (role == Qt::DisplayRole || role == Qt::EditRole
- || role == Qt::RangeModelDataRole) {
+ if (isPrimaryRole(role) || isRangeModelRole(role))
roleData.setData(read(value));
- } else {
+ else
roleData.clearData();
- }
}
}
});
@@ -1378,6 +1360,7 @@ public:
if (success) {
Q_EMIT this->dataChanged(index, index,
role == Qt::EditRole || role == Qt::RangeModelDataRole
+ || role == Qt::RangeModelAdapterRole
? QList<int>{} : QList<int>{role});
}
});
@@ -1390,16 +1373,34 @@ public:
using multi_role = QRangeModelDetails::is_multi_role<value_type>;
auto setRangeModelDataRole = [&target, &data]{
- auto &targetRef = QRangeModelDetails::refTo(target);
constexpr auto targetMetaType = QMetaType::fromType<value_type>();
const auto dataMetaType = data.metaType();
+ constexpr bool isWrapped = QRangeModelDetails::is_wrapped<value_type>();
if constexpr (!std::is_copy_assignable_v<wrapped_value_type>) {
- // This covers move-only types, but also polymorph types like QObject.
- // We don't support replacing a stored object with another one, as this
- // makes object ownership very messy.
- // fall through to error handling
- } else if constexpr (QRangeModelDetails::is_wrapped<value_type>()) {
- if (QRangeModelDetails::isValid(targetRef)) {
+ // we don't support replacing objects that are stored as raw pointers,
+ // as this makes object ownership very messy. But we can replace objects
+ // stored in smart pointers, and we can initialize raw nullptr objects.
+ if constexpr (isWrapped) {
+ constexpr bool is_raw_pointer = std::is_pointer_v<value_type>;
+ if constexpr (!is_raw_pointer && std::is_copy_assignable_v<value_type>) {
+ if (data.canConvert(targetMetaType)) {
+ target = data.value<value_type>();
+ return true;
+ }
+ } else if constexpr (is_raw_pointer) {
+ if (!QRangeModelDetails::isValid(target) && data.canConvert(targetMetaType)) {
+ target = data.value<value_type>();
+ return true;
+ }
+ } else {
+ Q_UNUSED(target);
+ }
+ }
+ // Otherwise we have a move-only or polymorph type. fall through to
+ // error handling.
+ } else if constexpr (isWrapped) {
+ if (QRangeModelDetails::isValid(target)) {
+ auto &targetRef = QRangeModelDetails::refTo(target);
// we need to get a wrapped value type out of the QVariant, which
// might carry a pointer. We have to try all alternatives.
if (const auto mt = QMetaType::fromType<wrapped_value_type>();
@@ -1413,10 +1414,10 @@ public:
}
}
} else if (targetMetaType == dataMetaType) {
- targetRef = data.value<value_type>();
+ QRangeModelDetails::refTo(target) = data.value<value_type>();
return true;
} else if (dataMetaType.flags() & QMetaType::PointerToGadget) {
- targetRef = *data.value<value_type *>();
+ QRangeModelDetails::refTo(target) = *data.value<value_type *>();
return true;
}
#ifndef QT_NO_DEBUG
@@ -1428,16 +1429,16 @@ public:
if constexpr (QRangeModelDetails::item_access<wrapped_value_type>()) {
using ItemAccess = QRangeModelDetails::QRangeModelItemAccess<wrapped_value_type>;
- if (role == Qt::RangeModelDataRole)
+ if (isRangeModelRole(role))
return setRangeModelDataRole();
return ItemAccess::writeRole(target, data, role);
} if constexpr (has_metaobject<value_type>) {
if (row_traits::fixed_size() <= 1) { // multi-role value
- if (role == Qt::RangeModelDataRole)
+ if (isRangeModelRole(role))
return setRangeModelDataRole();
return writeRole(role, QRangeModelDetails::pointerTo(target), data);
} else if (column <= row_traits::fixed_size() // multi-column
- && (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::RangeModelDataRole)) {
+ && (isPrimaryRole(role) || isRangeModelRole(role))) {
return writeProperty(column, QRangeModelDetails::pointerTo(target), data);
}
} else if constexpr (multi_role::value) {
@@ -1464,14 +1465,20 @@ public:
return write(target[roleToSet], data);
else
return write(target[roleNames.value(roleToSet)], data);
- } else if (role == Qt::DisplayRole || role == Qt::EditRole
- || role == Qt::RangeModelDataRole) {
+ } else if (isPrimaryRole(role) || isRangeModelRole(role)) {
return write(target, data);
}
return false;
};
success = writeAt(index, writeData);
+
+ if constexpr (itemsAreQObjects) {
+ if (success && isRangeModelRole(role) && this->autoConnectPolicy() == AutoConnectPolicy::Full) {
+ if (QObject *item = data.value<QObject *>())
+ Self::connectProperties(index, item, m_data.context, m_data.properties);
+ }
+ }
}
return success;
}
@@ -1579,7 +1586,7 @@ public:
tried = true;
auto targetCopy = makeCopy(target);
for (auto &&[role, value] : data.asKeyValueRange()) {
- if (role == Qt::RangeModelDataRole)
+ if (isRangeModelRole(role))
continue;
if (!writeRole(role, QRangeModelDetails::pointerTo(targetCopy), value)) {
const QByteArray roleName = roleNames.value(role);
@@ -1656,6 +1663,13 @@ public:
return this->itemModel().QAbstractItemModel::roleNames();
}
+ template <typename Fn, std::size_t ...Is>
+ static bool forEachTupleElement(const row_type &row, Fn &&fn, std::index_sequence<Is...>)
+ {
+ using std::get;
+ return (std::forward<Fn>(fn)(QRangeModelDetails::pointerTo(get<Is>(row))) && ...);
+ }
+
template <typename Fn>
bool forEachColumn(const row_type &row, int rowIndex, const QModelIndex &parent, Fn &&fn) const
{
@@ -1664,24 +1678,23 @@ public:
const auto &model = this->itemModel();
if constexpr (one_dimensional_range) {
return fn(model.index(rowIndex, 0, parent), pointerTo(row));
- } else if constexpr (dynamicColumns()) {
+ } else if constexpr (dynamicColumns() || QRangeModelDetails::array_like_v<row_type>) {
int columnIndex = -1;
return std::all_of(begin(row), end(row), [&](const auto &item) {
return fn(model.index(rowIndex, ++columnIndex, parent), pointerTo(item));
});
- } else { // tuple-like
- int columnIndex = -1;
- return std::apply([fn = std::forward<Fn>(fn), &model, rowIndex, &columnIndex, parent]
- (const auto &...item) {
- return (fn(model.index(rowIndex, ++columnIndex, parent), pointerTo(item)) && ...);
- }, row);
+ } else { // tuple-like (but not necessarily std::tuple, so can't use std::apply)
+ int column = -1;
+ return forEachTupleElement(row, [&column, &fn, &model, &rowIndex, &parent](QObject *item){
+ return std::forward<Fn>(fn)(model.index(rowIndex, ++column, parent), item);
+ }, std::make_index_sequence<static_column_count>());
}
}
bool autoConnectPropertiesInRow(const row_type &row, int rowIndex, const QModelIndex &parent) const
{
if (!QRangeModelDetails::isValid(row))
- return false;
+ return true; // nothing to do
return forEachColumn(row, rowIndex, parent, [this](const QModelIndex &index, QObject *item) {
if constexpr (isMutable())
return Self::connectProperties(index, item, m_data.context, m_data.properties);
@@ -2370,6 +2383,7 @@ protected:
return that().childRangeImpl(index);
}
+ template <typename, typename, typename> friend class QRangeModelAdapter;
ModelData m_data;
};
@@ -2403,6 +2417,26 @@ public:
: Base(std::forward<Range>(model), std::forward<Protocol>(p), itemModel)
{};
+ void setParentRow(range_type &children, row_ptr parent)
+ {
+ for (auto &&child : children)
+ this->protocol().setParentRow(QRangeModelDetails::refTo(child), parent);
+ resetParentInChildren(&children);
+ }
+
+ void deleteRemovedRows(range_type &range)
+ {
+ deleteRemovedRows(QRangeModelDetails::begin(range), QRangeModelDetails::end(range));
+ }
+
+ bool autoConnectProperties(const QModelIndex &parent) const
+ {
+ auto *children = this->childRange(parent);
+ if (!children)
+ return true;
+ return autoConnectPropertiesRange(QRangeModelDetails::refTo(children), parent);
+ }
+
protected:
QModelIndex indexImpl(int row, int column, const QModelIndex &parent) const
{
@@ -2635,9 +2669,11 @@ protected:
return false;
Q_ASSERT(QRangeModelDetails::isValid(row));
const auto &children = this->protocol().childRows(QRangeModelDetails::refTo(row));
- if (!autoConnectPropertiesRange(children,
- this->itemModel().index(rowIndex, 0, parent))) {
- return false;
+ if (QRangeModelDetails::isValid(children)) {
+ if (!autoConnectPropertiesRange(QRangeModelDetails::refTo(children),
+ this->itemModel().index(rowIndex, 0, parent))) {
+ return false;
+ }
}
++rowIndex;
}
diff --git a/src/corelib/itemmodels/qrangemodeladapter.h b/src/corelib/itemmodels/qrangemodeladapter.h
new file mode 100644
index 00000000000..bd3342e6185
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter.h
@@ -0,0 +1,1671 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QRANGEMODELADAPTER_H
+#define QRANGEMODELADAPTER_H
+
+#include <QtCore/qrangemodeladapter_impl.h>
+
+QT_BEGIN_NAMESPACE
+
+template <typename Range, typename Protocol = void, typename Model = QRangeModel>
+class QT_TECH_PREVIEW_API QRangeModelAdapter
+{
+ using Impl = QRangeModelDetails::RangeImplementation<Range, Protocol>;
+ using Storage = QRangeModelDetails::AdapterStorage<Model, Impl>;
+
+ Storage storage;
+
+#ifdef Q_QDOC
+ using range_type = Range;
+ using const_row_reference = typename std::iterator_traits<Range>::const_reference;
+ using row_reference = typename std::iterator_traits<Range>::reference;
+#else
+ using range_type = QRangeModelDetails::wrapped_t<Range>;
+ using const_row_reference = typename Impl::const_row_reference;
+ using row_reference = typename Impl::row_reference;
+#endif
+ using range_features = typename QRangeModelDetails::range_traits<range_type>;
+ using row_type = std::remove_reference_t<row_reference>;
+ using row_features = QRangeModelDetails::range_traits<typename Impl::wrapped_row_type>;
+ using row_ptr = typename Impl::wrapped_row_type *;
+ using row_traits = typename Impl::row_traits;
+ using item_type = std::remove_reference_t<typename row_traits::item_type>;
+ using data_type = typename QRangeModelDetails::data_type<item_type>::type;
+ using const_data_type = QRangeModelDetails::asConst_t<data_type>;
+ using protocol_traits = typename Impl::protocol_traits;
+
+ template <typename I> static constexpr bool is_list = I::protocol_traits::is_list;
+ template <typename I> static constexpr bool is_table = I::protocol_traits::is_table;
+ template <typename I> static constexpr bool is_tree = I::protocol_traits::is_tree;
+ template <typename I> static constexpr bool canInsertColumns = I::dynamicColumns()
+ && I::isMutable()
+ && row_features::has_insert;
+ template <typename I> static constexpr bool canRemoveColumns = I::dynamicColumns()
+ && I::isMutable()
+ && row_features::has_erase;
+
+ template <typename I> using if_writable = std::enable_if_t<I::isMutable(), bool>;
+ template <typename I> using if_list = std::enable_if_t<is_list<I>, bool>;
+ template <typename I> using unless_list = std::enable_if_t<!is_list<I>, bool>;
+ template <typename I> using if_table = std::enable_if_t<is_table<I>, bool>;
+ template <typename I> using if_tree = std::enable_if_t<is_tree<I>, bool>;
+ template <typename I> using unless_tree = std::enable_if_t<!is_tree<I>, bool>;
+ template <typename I> using if_flat = std::enable_if_t<is_list<I> || is_table<I>, bool>;
+
+ template <typename I>
+ using if_canInsertRows = std::enable_if_t<I::canInsertRows(), bool>;
+ template <typename I>
+ using if_canRemoveRows = std::enable_if_t<I::canRemoveRows(), bool>;
+ template <typename F>
+ using if_canMoveItems = std::enable_if_t<F::has_rotate || F::has_splice, bool>;
+
+ template <typename I>
+ using if_canInsertColumns = std::enable_if_t<canInsertColumns<I>, bool>;
+ template <typename I>
+ using if_canRemoveColumns = std::enable_if_t<canRemoveColumns<I>, bool>;
+
+ template <typename Row>
+ static constexpr bool is_compatible_row = std::is_convertible_v<Row, const_row_reference>;
+ template <typename Row>
+ using if_compatible_row = std::enable_if_t<is_compatible_row<Row>, bool>;
+
+ template <typename C>
+ static constexpr bool is_compatible_row_range = is_compatible_row<
+ decltype(*std::begin(std::declval<C&>()))
+ >;
+ template <typename C>
+ using if_compatible_row_range = std::enable_if_t<is_compatible_row_range<C>, bool>;
+ template <typename Data>
+ static constexpr bool is_compatible_data = true;
+ // std::is_convertible_v<Data, decltype(*std::begin(std::declval<const_row_reference>()))>;
+ template <typename Data>
+ using if_compatible_data = std::enable_if_t<is_compatible_data<Data>, bool>;
+ template <typename C>
+ static constexpr bool is_compatible_data_range = is_compatible_data<
+ decltype(*std::begin(std::declval<C&>()))
+ >;
+ template <typename C>
+ using if_compatible_data_range = std::enable_if_t<is_compatible_data_range<C>, bool>;
+
+ template <typename R>
+ using if_assignable_range = std::enable_if_t<std::is_assignable_v<range_type, R>, bool>;
+
+ friend class QRangeModel;
+ template <typename T>
+ static constexpr bool is_adapter = QRangeModelDetails::is_any_of<q20::remove_cvref_t<T>,
+ QRangeModelAdapter>::value;
+ template <typename T>
+ using unless_adapter = std::enable_if_t<!is_adapter<T>, bool>;
+
+#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY)
+ // An adapter on a mutable range can make itself an adapter on a const
+ // version of that same range. To make the constructor for a sub-range
+ // accessible, befriend the mutable version. We can use more
+ // generic pattern matching here, as we only use as input what asConst
+ // might produce as output.
+ template <typename T> static constexpr T asMutable(const T &);
+ template <typename T> static constexpr T *asMutable(const T *);
+ template <template <typename, typename...> typename U, typename T, typename ...Args>
+ static constexpr U<T, Args...> asMutable(const U<const T, Args...> &);
+
+ template <typename T>
+ using asMutable_t = decltype(asMutable(std::declval<T>()));
+ friend class QRangeModelAdapter<asMutable_t<Range>, Protocol, Model>;
+#else
+ template <typename R, typename P, typename M>
+ friend class QRangeModelAdapter;
+#endif
+
+ explicit QRangeModelAdapter(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root,
+ std::in_place_t) // disambiguate from range/protocol c'tor
+ : storage{model, root}
+ {}
+
+ explicit QRangeModelAdapter(QRangeModel *model)
+ : storage(model)
+ {}
+
+public:
+ struct DataReference
+ {
+ using value_type = data_type;
+ using const_value_type = const_data_type;
+ using pointer = QRangeModelDetails::data_pointer_t<const_value_type>;
+
+ explicit DataReference(const QModelIndex &index) noexcept
+ : m_index(index)
+ {
+ Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid");
+ }
+
+ DataReference(const DataReference &other) = default;
+
+ // reference (not std::reference_wrapper) semantics
+ DataReference &operator=(const DataReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ ~DataReference() = default;
+
+ DataReference &operator=(const value_type &value)
+ {
+ constexpr Qt::ItemDataRole dataRole = Qt::RangeModelAdapterRole;
+
+ if (m_index.isValid()) {
+ auto model = const_cast<QAbstractItemModel *>(m_index.model());
+ [[maybe_unused]] bool couldWrite = false;
+ if constexpr (std::is_same_v<q20::remove_cvref_t<value_type>, QVariant>)
+ couldWrite = model->setData(m_index, value, dataRole);
+ else
+ couldWrite = model->setData(m_index, QVariant::fromValue(value), dataRole);
+#ifndef QT_NO_DEBUG
+ if (!couldWrite) {
+ qWarning() << "Writing value of type" << QMetaType::fromType<value_type>().name()
+ << "to role" << dataRole << "at index" << m_index
+ << "of the model failed";
+ }
+ } else {
+ qCritical("Data reference for invalid index, can't write to model");
+#endif
+ }
+ return *this;
+ }
+
+ const_value_type get() const
+ {
+ Q_ASSERT_X(m_index.isValid(), "QRangeModelAdapter::at", "Index at position is invalid");
+ return QRangeModelDetails::dataAtIndex<q20::remove_cvref_t<value_type>>(m_index);
+ }
+
+ operator const_value_type() const
+ {
+ return get();
+ }
+
+ pointer operator->() const
+ {
+ return {get()};
+ }
+
+ bool isValid() const { return m_index.isValid(); }
+
+ private:
+ QModelIndex m_index;
+
+ friend inline bool comparesEqual(const DataReference &lhs, const DataReference &rhs)
+ {
+ return lhs.m_index == rhs.m_index
+ || lhs.get() == rhs.get();
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference);
+
+ friend inline bool comparesEqual(const DataReference &lhs, const value_type &rhs)
+ {
+ return lhs.get() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(DataReference, value_type);
+
+ friend inline void swap(DataReference lhs, DataReference rhs)
+ {
+ const value_type lhsValue = lhs.get();
+ lhs = rhs;
+ rhs = lhsValue; // no point in moving, we have to go through QVariant anyway
+ }
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const DataReference &ref)
+ {
+ return dbg << ref.get();
+ }
+#endif
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator<<(QDataStream &ds, const DataReference &ref)
+ {
+ return ds << ref.get();
+ }
+ friend inline QDataStream &operator>>(QDataStream &ds, DataReference &ref)
+ {
+ value_type value;
+ ds >> value;
+ ref = value;
+ return ds;
+ }
+#endif
+ };
+ template <typename Iterator, typename Adapter>
+ struct ColumnIteratorBase
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using difference_type = int;
+
+ ColumnIteratorBase() = default;
+ ColumnIteratorBase(const ColumnIteratorBase &other) = default;
+ ColumnIteratorBase(ColumnIteratorBase &&other) = default;
+ ColumnIteratorBase &operator=(const ColumnIteratorBase &other) = default;
+ ColumnIteratorBase &operator=(ColumnIteratorBase &&other) = default;
+
+ ColumnIteratorBase(const QModelIndex &rowIndex, int column, Adapter *adapter) noexcept
+ : m_rowIndex(rowIndex), m_column(column), m_adapter(adapter)
+ {
+ }
+
+ void swap(ColumnIteratorBase &other) noexcept
+ {
+ using std::swap;
+ swap(m_rowIndex, other.m_rowIndex);
+ swap(m_column, other.m_column);
+ q_ptr_swap(m_adapter, other.m_adapter);
+ }
+
+ friend Iterator &operator++(Iterator &that) noexcept
+ {
+ ++that.m_column;
+ return that;
+ }
+ friend Iterator operator++(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ ++that;
+ return copy;
+ }
+ friend Iterator operator+(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_rowIndex, that.m_column + n, that.m_adapter};
+ }
+ friend Iterator operator+(difference_type n, const Iterator &that) noexcept
+ {
+ return that + n;
+ }
+ friend Iterator &operator+=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_column += n;
+ return that;
+ }
+
+ friend Iterator &operator--(Iterator &that) noexcept
+ {
+ --that.m_column;
+ return that;
+ }
+ friend Iterator operator--(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ --that;
+ return copy;
+ }
+ friend Iterator operator-(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_rowIndex, that.m_column - n, that.m_adapter};
+ }
+ friend Iterator operator-(difference_type n, const Iterator &that) noexcept
+ {
+ return that - n;
+ }
+ friend Iterator &operator-=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_column -= n;
+ return that;
+ }
+
+ friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ Q_PRE(lhs.m_rowIndex == rhs.m_rowIndex);
+ Q_PRE(lhs.m_adapter == rhs.m_adapter);
+ return lhs.m_column - rhs.m_column;
+ }
+
+ protected:
+ ~ColumnIteratorBase() = default;
+ QModelIndex m_rowIndex;
+ int m_column = -1;
+ Adapter *m_adapter = nullptr;
+
+ private:
+ friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs)
+ {
+ Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex);
+ return lhs.m_column == rhs.m_column;
+ }
+ friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs)
+ {
+ Q_ASSERT(lhs.m_rowIndex == rhs.m_rowIndex);
+ return qCompareThreeWay(lhs.m_column, rhs.m_column);
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Iterator)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Iterator &it)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "ColumnIterator(" << it.m_rowIndex.siblingAtColumn(it.m_column) << ")";
+ }
+#endif
+ };
+
+ struct ConstColumnIterator : ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter>
+ {
+ using Base = ColumnIteratorBase<ConstColumnIterator, const QRangeModelAdapter>;
+ using difference_type = typename Base::difference_type;
+ using value_type = data_type;
+ using reference = const_data_type;
+ using pointer = QRangeModelDetails::data_pointer_t<value_type>;
+
+ using Base::Base;
+ using Base::operator=;
+ ~ConstColumnIterator() = default;
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ return std::as_const(this->m_adapter)->at(this->m_rowIndex.row(), this->m_column);
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ struct ColumnIterator : ColumnIteratorBase<ColumnIterator, QRangeModelAdapter>
+ {
+ using Base = ColumnIteratorBase<ColumnIterator, QRangeModelAdapter>;
+ using difference_type = typename Base::difference_type;
+ using value_type = DataReference;
+ using reference = DataReference;
+ using pointer = reference;
+
+ using Base::Base;
+ using Base::operator=;
+ ~ColumnIterator() = default;
+
+ operator ConstColumnIterator() const
+ {
+ return ConstColumnIterator{this->m_rowIndex, this->m_column, this->m_adapter};
+ }
+
+ pointer operator->() const
+ {
+ return operator*();
+ }
+
+ reference operator*() const
+ {
+ return reference{this->m_rowIndex.siblingAtColumn(this->m_column)};
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ template <typename Reference, typename const_row_type, typename = void>
+ struct RowGetter
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ auto *childRange = impl->childRange(that->m_index.parent());
+ if constexpr (std::is_convertible_v<const row_type &, const_row_type>) {
+ return *std::next(QRangeModelDetails::begin(childRange), that->m_index.row());
+ } else {
+ const auto &row = *std::next(QRangeModelDetails::begin(childRange),
+ that->m_index.row());
+ return const_row_type{QRangeModelDetails::begin(row), QRangeModelDetails::end(row)};
+ }
+ }
+
+ const_row_type operator->() const
+ {
+ return {get()};
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename const_row_type>
+ struct RowGetter<Reference, const_row_type,
+ std::enable_if_t<std::is_reference_v<const_row_type>>>
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ using QRangeModelDetails::begin;
+
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))),
+ that->m_index.row());
+ }
+
+ auto operator->() const
+ {
+ return std::addressof(get());
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename const_row_type>
+ struct RowGetter<Reference, const_row_type,
+ std::enable_if_t<std::is_pointer_v<const_row_type>>>
+ {
+ const_row_type get() const
+ {
+ using namespace QRangeModelDetails;
+ using QRangeModelDetails::begin;
+
+ const Reference *that = static_cast<const Reference *>(this);
+ const auto *impl = that->m_adapter->storage.implementation();
+ return *std::next(begin(refTo(impl->childRange(that->m_index.parent()))),
+ that->m_index.row());
+ }
+
+ const_row_type operator->() const
+ {
+ return get();
+ }
+
+ operator const_row_type() const
+ {
+ return get();
+ }
+ };
+
+ template <typename Reference, typename Adapter>
+ struct RowReferenceBase : RowGetter<Reference, QRangeModelDetails::asConstRow_t<row_type>>
+ {
+ using const_iterator = ConstColumnIterator;
+ using size_type = int;
+ using difference_type = int;
+ using const_row_type = QRangeModelDetails::asConstRow_t<row_type>;
+
+ RowReferenceBase(const QModelIndex &index, Adapter *adapter) noexcept
+ : m_index(index), m_adapter(adapter)
+ {}
+
+ template <typename I = Impl, if_tree<I> = true>
+ bool hasChildren() const
+ {
+ return m_adapter->model()->hasChildren(m_index);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ auto children() const
+ {
+ using ConstRange = QRangeModelDetails::asConst_t<Range>;
+ return QRangeModelAdapter<ConstRange, Protocol, Model>(m_adapter->storage.m_model,
+ m_index, std::in_place);
+ }
+
+ ConstColumnIterator cbegin() const
+ {
+ return ConstColumnIterator{m_index, 0, m_adapter};
+ }
+ ConstColumnIterator cend() const
+ {
+ return ConstColumnIterator{m_index, m_adapter->columnCount(), m_adapter};
+ }
+
+ ConstColumnIterator begin() const { return cbegin(); }
+ ConstColumnIterator end() const { return cend(); }
+
+ size_type size() const
+ {
+ return m_adapter->columnCount();
+ }
+
+ auto at(int column) const
+ {
+ Q_ASSERT(column >= 0 && column < m_adapter->columnCount());
+ return *ConstColumnIterator{m_index, column, m_adapter};
+ }
+
+ auto operator[](int column) const
+ {
+ return at(column);
+ }
+
+ protected:
+ friend struct RowGetter<Reference, const_row_type>;
+ ~RowReferenceBase() = default;
+ QModelIndex m_index;
+ Adapter *m_adapter;
+
+ private:
+ friend bool comparesEqual(const Reference &lhs, const Reference &rhs)
+ {
+ Q_ASSERT(lhs.m_adapter == rhs.m_adapter);
+ return lhs.m_index == rhs.m_index;
+ }
+ friend Qt::strong_ordering compareThreeWay(const Reference &lhs, const Reference &rhs)
+ {
+ Q_ASSERT(lhs.m_adapter == rhs.m_adapter);
+ return qCompareThreeWay(lhs.m_index, rhs.m_index);
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(Reference)
+
+ friend bool comparesEqual(const Reference &lhs, const row_type &rhs)
+ {
+ return lhs.get() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(Reference, row_type)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Reference &ref)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "RowReference(" << ref.m_index << ")";
+ }
+#endif
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator<<(QDataStream &ds, const Reference &ref)
+ {
+ return ds << ref.get();
+ }
+#endif
+ };
+
+ struct ConstRowReference : RowReferenceBase<ConstRowReference, const QRangeModelAdapter>
+ {
+ using Base = RowReferenceBase<ConstRowReference, const QRangeModelAdapter>;
+ using Base::Base;
+
+ ConstRowReference() = default;
+ ConstRowReference(const ConstRowReference &) = default;
+ ConstRowReference(ConstRowReference &&) = default;
+ ConstRowReference &operator=(const ConstRowReference &) = default;
+ ConstRowReference &operator=(ConstRowReference &&) = default;
+ ~ConstRowReference() = default;
+ };
+
+ struct RowReference : RowReferenceBase<RowReference, QRangeModelAdapter>
+ {
+ using Base = RowReferenceBase<RowReference, QRangeModelAdapter>;
+ using iterator = ColumnIterator;
+ using const_iterator = typename Base::const_iterator;
+ using size_type = typename Base::size_type;
+ using difference_type = typename Base::difference_type;
+ using const_row_type = typename Base::const_row_type;
+
+ using Base::Base;
+ RowReference() = delete;
+ ~RowReference() = default;
+ RowReference(const RowReference &other) = default;
+ RowReference(RowReference &&other) = default;
+
+ // assignment has reference (std::reference_wrapper) semantics
+ RowReference &operator=(const ConstRowReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ RowReference &operator=(const RowReference &other)
+ {
+ *this = other.get();
+ return *this;
+ }
+
+ RowReference &operator=(const row_type &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ RowReference &operator=(row_type &&other)
+ {
+ assign(std::move(other));
+ return *this;
+ }
+
+ operator ConstRowReference() const
+ {
+ return ConstRowReference{this->m_index, this->m_adapter};
+ }
+
+ template <typename ConstRowType = const_row_type,
+ std::enable_if_t<!std::is_same_v<ConstRowType, const row_type &>, bool> = true>
+ RowReference &operator=(const ConstRowType &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ template <typename T, typename It, typename Sentinel>
+ RowReference &operator=(const QRangeModelDetails::RowView<T, It, Sentinel> &other)
+ {
+ *this = row_type{other.begin(), other.end()};
+ return *this;
+ }
+
+ friend inline void swap(RowReference lhs, RowReference rhs)
+ {
+ auto lhsRow = lhs.get();
+ lhs = rhs.get();
+ rhs = std::move(lhsRow);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ auto children()
+ {
+ return QRangeModelAdapter(this->m_adapter->storage.m_model, this->m_index,
+ std::in_place);
+ }
+
+ using Base::begin;
+ ColumnIterator begin()
+ {
+ return ColumnIterator{this->m_index, 0, this->m_adapter};
+ }
+
+ using Base::end;
+ ColumnIterator end()
+ {
+ return ColumnIterator{this->m_index, this->m_adapter->columnCount(), this->m_adapter};
+ }
+
+ using Base::at;
+ auto at(int column)
+ {
+ Q_ASSERT(column >= 0 && column < this->m_adapter->columnCount());
+ return *ColumnIterator{this->m_index, column, this->m_adapter};
+ }
+
+ using Base::operator[];
+ auto operator[](int column)
+ {
+ return at(column);
+ }
+
+ private:
+ template <typename RHS>
+ void verifyRows(const row_type &oldRow, const RHS &newRow)
+ {
+ using namespace QRangeModelDetails;
+ if constexpr (test_size<row_type>::value) {
+ // prevent that tables get populated with wrongly sized rows
+ Q_ASSERT_X(Impl::size(newRow) == Impl::size(oldRow),
+ "RowReference::operator=()",
+ "The new row has the wrong size!");
+ }
+
+ if constexpr (is_tree<Impl>) {
+ // we cannot hook invalid rows up to the tree hierarchy
+ Q_ASSERT_X(isValid(newRow),
+ "RowReference::operator=()",
+ "An invalid row can not inserted into a tree!");
+ }
+ }
+
+ template <typename R>
+ void assign(R &&other)
+ {
+ auto *impl = this->m_adapter->storage.implementation();
+ decltype(auto) oldRow = impl->rowData(this->m_index);
+
+ verifyRows(oldRow, other);
+
+ if constexpr (is_tree<Impl>) {
+ using namespace QRangeModelDetails;
+ auto &protocol = impl->protocol();
+ auto *oldParent = protocol.parentRow(refTo(oldRow));
+
+ // the old children will be removed; we don't try to overwrite
+ // them with the new children, we replace them completely
+ if (decltype(auto) oldChildren = protocol.childRows(refTo(oldRow));
+ isValid(oldChildren)) {
+ if (int oldChildCount = this->m_adapter->model()->rowCount(this->m_index)) {
+ impl->beginRemoveRows(this->m_index, 0, oldChildCount - 1);
+ impl->deleteRemovedRows(refTo(oldChildren));
+ // make sure the list is empty before we emit rowsRemoved
+ refTo(oldChildren) = range_type{};
+ impl->endRemoveRows();
+ }
+ }
+
+ if constexpr (protocol_traits::has_deleteRow)
+ protocol.deleteRow(oldRow);
+ oldRow = std::forward<R>(other);
+ if constexpr (protocol_traits::has_setParentRow) {
+ protocol.setParentRow(refTo(oldRow), oldParent);
+ if (decltype(auto) newChildren = protocol.childRows(refTo(oldRow));
+ isValid(newChildren)) {
+ impl->beginInsertRows(this->m_index, 0, Impl::size(refTo(newChildren)) - 1);
+ impl->setParentRow(refTo(newChildren), pointerTo(oldRow));
+ impl->endInsertRows();
+ }
+ }
+ } else {
+ oldRow = std::forward<R>(other);
+ }
+ this->m_adapter->emitDataChanged(this->m_index,
+ this->m_index.siblingAtColumn(this->m_adapter->columnCount() - 1));
+ if constexpr (Impl::itemsAreQObjects) {
+ if (this->m_adapter->model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) {
+ impl->autoConnectPropertiesInRow(oldRow, this->m_index.row(), this->m_index.parent());
+ if constexpr (is_tree<Impl>)
+ impl->autoConnectProperties(this->m_index);
+ }
+
+ }
+ }
+
+#ifndef QT_NO_DATASTREAM
+ friend inline QDataStream &operator>>(QDataStream &ds, RowReference &ref)
+ {
+ row_type value;
+ ds >> value;
+ ref = value;
+ return ds;
+ }
+#endif
+ };
+
+ template <typename Iterator, typename Adapter>
+ struct RowIteratorBase : QRangeModelDetails::ParentIndex<is_tree<Impl>>
+ {
+ using iterator_category = std::random_access_iterator_tag;
+ using difference_type = int;
+
+ RowIteratorBase() = default;
+ RowIteratorBase(const RowIteratorBase &) = default;
+ RowIteratorBase(RowIteratorBase &&) = default;
+ RowIteratorBase &operator=(const RowIteratorBase &) = default;
+ RowIteratorBase &operator=(RowIteratorBase &&) = default;
+
+ RowIteratorBase(int row, const QModelIndex &parent, Adapter *adapter)
+ : QRangeModelDetails::ParentIndex<is_tree<Impl>>{parent}
+ , m_row(row), m_adapter(adapter)
+ {}
+
+ void swap(RowIteratorBase &other) noexcept
+ {
+ using std::swap;
+ swap(m_row, other.m_row);
+ swap(this->m_rootIndex, other.m_rootIndex);
+ q_ptr_swap(m_adapter, other.m_adapter);
+ }
+
+ friend Iterator &operator++(Iterator &that) noexcept
+ {
+ ++that.m_row;
+ return that;
+ }
+ friend Iterator operator++(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ ++that;
+ return copy;
+ }
+ friend Iterator operator+(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_row + n, that.root(), that.m_adapter};
+ }
+ friend Iterator operator+(difference_type n, const Iterator &that) noexcept
+ {
+ return that + n;
+ }
+ friend Iterator &operator+=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_row += n;
+ return that;
+ }
+
+ friend Iterator &operator--(Iterator &that) noexcept
+ {
+ --that.m_row;
+ return that;
+ }
+ friend Iterator operator--(Iterator &that, int) noexcept
+ {
+ auto copy = that;
+ --that;
+ return copy;
+ }
+ friend Iterator operator-(const Iterator &that, difference_type n) noexcept
+ {
+ return {that.m_row - n, that.root(), that.m_adapter};
+ }
+ friend Iterator operator-(difference_type n, const Iterator &that) noexcept
+ {
+ return that - n;
+ }
+ friend Iterator &operator-=(Iterator &that, difference_type n) noexcept
+ {
+ that.m_row -= n;
+ return that;
+ }
+
+ friend difference_type operator-(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ return lhs.m_row - rhs.m_row;
+ }
+
+ protected:
+ ~RowIteratorBase() = default;
+ int m_row = -1;
+ Adapter *m_adapter = nullptr;
+
+ private:
+ friend bool comparesEqual(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ return lhs.m_row == rhs.m_row && lhs.root() == rhs.root();
+ }
+ friend Qt::strong_ordering compareThreeWay(const Iterator &lhs, const Iterator &rhs) noexcept
+ {
+ if (lhs.root() == rhs.root())
+ return qCompareThreeWay(lhs.m_row, rhs.m_row);
+ return qCompareThreeWay(lhs.root(), rhs.root());
+ }
+
+ Q_DECLARE_STRONGLY_ORDERED(Iterator)
+
+#ifndef QT_NO_DEBUG_STREAM
+ friend inline QDebug operator<<(QDebug dbg, const Iterator &it)
+ {
+ QDebugStateSaver saver(dbg);
+ dbg.nospace();
+ return dbg << "RowIterator(" << it.m_row << it.root() << ")";
+ }
+#endif
+ };
+
+public:
+ struct ConstRowIterator : public RowIteratorBase<ConstRowIterator, const QRangeModelAdapter>
+ {
+ using Base = RowIteratorBase<ConstRowIterator, const QRangeModelAdapter>;
+ using Base::Base;
+
+ using difference_type = typename Base::difference_type;
+ using value_type = std::conditional_t<is_list<Impl>,
+ const_data_type,
+ ConstRowReference>;
+ using reference = std::conditional_t<is_list<Impl>,
+ const_data_type,
+ ConstRowReference>;
+ using pointer = std::conditional_t<is_list<Impl>,
+ QRangeModelDetails::data_pointer_t<const_data_type>,
+ ConstRowReference>;
+
+ ConstRowIterator(const ConstRowIterator &other) = default;
+ ConstRowIterator(ConstRowIterator &&other) = default;
+ ConstRowIterator &operator=(const ConstRowIterator &other) = default;
+ ConstRowIterator &operator=(ConstRowIterator &&other) = default;
+ ~ConstRowIterator() = default;
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ if constexpr (is_list<Impl>) {
+ return this->m_adapter->at(this->m_row);
+ } else {
+ const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0,
+ this->root());
+ return ConstRowReference{index, this->m_adapter};
+ }
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ struct RowIterator : public RowIteratorBase<RowIterator, QRangeModelAdapter>
+ {
+ using Base = RowIteratorBase<RowIterator, QRangeModelAdapter>;
+ using Base::Base;
+
+ using difference_type = typename Base::difference_type;
+ using value_type = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+ using reference = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+ using pointer = std::conditional_t<is_list<Impl>,
+ DataReference,
+ RowReference>;
+
+ RowIterator(const RowIterator &other) = default;
+ RowIterator(RowIterator &&other) = default;
+ RowIterator &operator=(const RowIterator &other) = default;
+ RowIterator &operator=(RowIterator &&other) = default;
+ ~RowIterator() = default;
+
+ operator ConstRowIterator() const
+ {
+ return ConstRowIterator{this->m_row, this->root(), this->m_adapter};
+ }
+
+ pointer operator->() const
+ {
+ return pointer{operator*()};
+ }
+
+ reference operator*() const
+ {
+ const QModelIndex index = this->m_adapter->model()->index(this->m_row, 0, this->root());
+ if constexpr (is_list<Impl>) {
+ return reference{index};
+ } else {
+ return reference{index, this->m_adapter};
+ }
+ }
+
+ reference operator[](difference_type n) const
+ {
+ return *(*this + n);
+ }
+ };
+
+ using const_iterator = ConstRowIterator;
+ using iterator = RowIterator;
+
+ template <typename R, typename P,
+ std::enable_if_t<!std::is_void_v<P>, bool> = true>
+ explicit QRangeModelAdapter(R &&range, P &&protocol)
+ : QRangeModelAdapter(new Model(std::forward<R>(range), std::forward<P>(protocol)))
+ {}
+
+ template <typename R, typename P = void,
+ unless_adapter<R> = true,
+ std::enable_if_t<std::is_void_v<P>, bool> = true>
+ explicit QRangeModelAdapter(R &&range)
+ : QRangeModelAdapter(new Model(std::forward<R>(range)))
+ {}
+
+ // compiler-generated copy/move SMF are fine!
+
+ Model *model() const
+ {
+ return storage.m_model.get();
+ }
+
+ const range_type &range() const
+ {
+ return QRangeModelDetails::refTo(storage.implementation()->childRange(storage.root()));
+ }
+
+ Q_IMPLICIT operator const range_type &() const
+ {
+ return range();
+ }
+
+ template <typename NewRange = range_type, if_assignable_range<NewRange> = true>
+ void setRange(NewRange &&newRange)
+ {
+ using namespace QRangeModelDetails;
+
+ auto *impl = storage.implementation();
+ const QModelIndex root = storage.root();
+ const qsizetype newLastRow = qsizetype(Impl::size(refTo(newRange))) - 1;
+ auto *oldRange = impl->childRange(root);
+ const qsizetype oldLastRow = qsizetype(Impl::size(oldRange)) - 1;
+
+ if (!root.isValid()) {
+ impl->beginResetModel();
+ impl->deleteOwnedRows();
+ } else if constexpr (is_tree<Impl>) {
+ if (oldLastRow > 0) {
+ impl->beginRemoveRows(root, 0, model()->rowCount(root) - 1);
+ impl->deleteRemovedRows(refTo(oldRange));
+ impl->endRemoveRows();
+ }
+ if (newLastRow > 0)
+ impl->beginInsertRows(root, 0, newLastRow);
+ } else {
+ Q_ASSERT_X(false, "QRangeModelAdapter::setRange",
+ "Internal error: The root index in a table or list must be invalid.");
+ }
+ refTo(oldRange) = std::forward<NewRange>(newRange);
+ if (!root.isValid()) {
+ impl->endResetModel();
+ } else if constexpr (is_tree<Impl>) {
+ if (newLastRow > 0) {
+ Q_ASSERT(model()->hasChildren(root));
+ // if it was moved, then newRange is now likely to be empty. Get
+ // the inserted row.
+ impl->setParentRow(refTo(impl->childRange(storage.root())),
+ pointerTo(impl->rowData(root)));
+ impl->endInsertRows();
+ }
+ }
+ if constexpr (Impl::itemsAreQObjects) {
+ if (model()->autoConnectPolicy() == QRangeModel::AutoConnectPolicy::Full) {
+ const auto begin = QRangeModelDetails::begin(refTo(oldRange));
+ const auto end = QRangeModelDetails::end(refTo(oldRange));
+ int rowIndex = 0;
+ for (auto it = begin; it != end; ++it, ++rowIndex)
+ impl->autoConnectPropertiesInRow(*it, rowIndex, root);
+ }
+ }
+ }
+
+ template <typename NewRange = range_type, if_assignable_range<NewRange> = true>
+ QRangeModelAdapter &operator=(NewRange &&newRange)
+ {
+ setRange(std::forward<NewRange>(newRange));
+ return *this;
+ }
+
+ // iterator API
+ ConstRowIterator cbegin() const
+ {
+ return ConstRowIterator{ 0, storage.root(), this };
+ }
+ ConstRowIterator begin() const { return cbegin(); }
+
+ ConstRowIterator cend() const
+ {
+ return ConstRowIterator{ rowCount(), storage.root(), this };
+ }
+ ConstRowIterator end() const { return cend(); }
+
+ template <typename I = Impl, if_writable<I> = true>
+ RowIterator begin()
+ {
+ return RowIterator{ 0, storage.root(), this };
+ }
+
+ template <typename I = Impl, if_writable<I> = true>
+ RowIterator end()
+ {
+ return RowIterator{ rowCount(), storage.root(), this };
+ }
+
+ int size() const
+ {
+ return rowCount();
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QModelIndex index(int row) const
+ {
+ return storage->index(row, 0, storage.root());
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QModelIndex index(int row, int column) const
+ {
+ return storage->index(row, column, storage.root());
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QModelIndex index(QSpan<const int> path, int col) const
+ {
+ Q_PRE(path.size());
+ QModelIndex result = storage.root();
+ auto count = path.size();
+ for (const int r : path) {
+ if (--count)
+ result = storage->index(r, 0, result);
+ else
+ result = storage->index(r, col, result);
+ }
+ return result;
+ }
+
+ int columnCount() const
+ {
+ // all rows and tree branches have the same column count
+ return storage->columnCount({});
+ }
+
+ int rowCount() const
+ {
+ return storage->rowCount(storage.root());
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ int rowCount(int row) const
+ {
+ return storage->rowCount(index(row, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ int rowCount(QSpan<const int> path) const
+ {
+ return storage->rowCount(index(path, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ constexpr bool hasChildren(int row) const
+ {
+ return storage.m_model->hasChildren(index(row, 0));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ constexpr bool hasChildren(QSpan<const int> path) const
+ {
+ return storage.m_model->hasChildren(index(path, 0));
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QVariant data(int row) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row));
+ }
+
+ template <typename I = Impl, if_list<I> = true>
+ QVariant data(int row, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row), role);
+ }
+
+ template <typename I = Impl, if_list<I> = true, if_writable<I> = true>
+ bool setData(int row, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(row), value, role);
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QVariant data(int row, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column));
+ }
+
+ template <typename I = Impl, unless_list<I> = true>
+ QVariant data(int row, int column, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(row, column), role);
+ }
+
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ bool setData(int row, int column, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(row, column), value, role);
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QVariant data(QSpan<const int> path, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column));
+ }
+
+ template <typename I = Impl, if_tree<I> = true>
+ QVariant data(QSpan<const int> path, int column, int role) const
+ {
+ return QRangeModelDetails::dataAtIndex<QVariant>(index(path, column), role);
+ }
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ bool setData(QSpan<const int> path, int column, const QVariant &value, int role = Qt::EditRole)
+ {
+ return storage->setData(index(path, column), value, role);
+ }
+
+ // at/operator[int] for list: returns value at row
+ // if multi-role value: return the entire object
+ template <typename I= Impl, if_list<I> = true>
+ const_data_type at(int row) const
+ {
+ return QRangeModelDetails::dataAtIndex<data_type>(index(row));
+ }
+ template <typename I = Impl, if_list<I> = true>
+ const_data_type operator[](int row) const { return at(row); }
+
+ template <typename I= Impl, if_list<I> = true, if_writable<I> = true>
+ auto at(int row) { return DataReference{this->index(row)}; }
+ template <typename I = Impl, if_list<I> = true, if_writable<I> = true>
+ auto operator[](int row) { return DataReference{this->index(row)}; }
+
+ // at/operator[int] for table or tree: a reference or view of the row
+ template <typename I = Impl, unless_list<I> = true>
+ decltype(auto) at(int row) const
+ {
+ return ConstRowReference{index(row, 0), this}.get();
+ }
+ template <typename I = Impl, unless_list<I> = true>
+ decltype(auto) operator[](int row) const { return at(row); }
+
+ template <typename I = Impl, if_table<I> = true, if_writable<I> = true>
+ decltype(auto) at(int row)
+ {
+ return RowReference{index(row, 0), this};
+ }
+ template <typename I = Impl, if_table<I> = true, if_writable<I> = true>
+ decltype(auto) operator[](int row) { return at(row); }
+
+ // at/operator[int, int] for table: returns value at row/column
+ template <typename I = Impl, unless_list<I> = true>
+ const_data_type at(int row, int column) const
+ {
+ return QRangeModelDetails::dataAtIndex<data_type>(index(row, column));
+ }
+
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, unless_list<I> = true>
+ const_data_type operator[](int row, int column) const { return at(row, column); }
+#endif
+
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ auto at(int row, int column)
+ {
+ return DataReference{this->index(row, column)};
+ }
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, unless_list<I> = true, if_writable<I> = true>
+ auto operator[](int row, int column) { return at(row, column); }
+#endif
+
+ // at/operator[int] for tree: return a wrapper that maintains reference to
+ // parent.
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(int row)
+ {
+ return RowReference{index(row, 0), this};
+ }
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](int row) { return at(row); }
+
+ // at/operator[path] for tree: a reference or view of the row
+ template <typename I = Impl, if_tree<I> = true>
+ decltype(auto) at(QSpan<const int> path) const
+ {
+ return ConstRowReference{index(path, 0), this}.get();
+ }
+ template <typename I = Impl, if_tree<I> = true>
+ decltype(auto) operator[](QSpan<const int> path) const { return at(path); }
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(QSpan<const int> path)
+ {
+ return RowReference{index(path, 0), this};
+ }
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](QSpan<const int> path) { return at(path); }
+
+ // at/operator[path, column] for tree: return value
+ template <typename I = Impl, if_tree<I> = true>
+ const_data_type at(QSpan<const int> path, int column) const
+ {
+ Q_PRE(path.size());
+ return QRangeModelDetails::dataAtIndex<data_type>(index(path, column));
+ }
+
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, if_tree<I> = true>
+ const_data_type operator[](QSpan<const int> path, int column) const { return at(path, column); }
+#endif
+
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto at(QSpan<const int> path, int column)
+ {
+ Q_PRE(path.size());
+ return DataReference{this->index(path, column)};
+ }
+#ifdef __cpp_multidimensional_subscript
+ template <typename I = Impl, if_tree<I> = true, if_writable<I> = true>
+ auto operator[](QSpan<const int> path, int column) { return at(path, column); }
+#endif
+
+ template <typename I = Impl, if_canInsertRows<I> = true>
+ bool insertRow(int before)
+ {
+ return storage.m_model->insertRow(before);
+ }
+
+ template <typename I = Impl, if_canInsertRows<I> = true, if_tree<I> = true>
+ bool insertRow(QSpan<const int> before)
+ {
+ Q_PRE(before.size());
+ return storage.m_model->insertRow(before.back(), this->index(before.first(before.size() - 1), 0));
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row<D> = true>
+ bool insertRow(int before, D &&data)
+ {
+ return insertRowImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row<D> = true, if_tree<I> = true>
+ bool insertRow(QSpan<const int> before, D &&data)
+ {
+ return insertRowImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row_range<C> = true>
+ bool insertRows(int before, C &&data)
+ {
+ return insertRowsImpl(before, storage.root(), std::forward<C>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertRows<I> = true, if_compatible_row_range<C> = true, if_tree<I> = true>
+ bool insertRows(QSpan<const int> before, C &&data)
+ {
+ return insertRowsImpl(before.back(), this->index(before.first(before.size() - 1), 0),
+ std::forward<C>(data));
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true>
+ bool removeRow(int row)
+ {
+ return removeRows(row, 1);
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true>
+ bool removeRow(QSpan<const int> path)
+ {
+ return removeRows(path, 1);
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true>
+ bool removeRows(int row, int count)
+ {
+ return storage->removeRows(row, count, storage.root());
+ }
+
+ template <typename I = Impl, if_canRemoveRows<I> = true, if_tree<I> = true>
+ bool removeRows(QSpan<const int> path, int count)
+ {
+ return storage->removeRows(path.back(), count,
+ this->index(path.first(path.size() - 1), 0));
+ }
+
+ template <typename F = range_features, if_canMoveItems<F> = true>
+ bool moveRow(int source, int destination)
+ {
+ return moveRows(source, 1, destination);
+ }
+
+ template <typename F = range_features, if_canMoveItems<F> = true>
+ bool moveRows(int source, int count, int destination)
+ {
+ return storage->moveRows(storage.root(), source, count, storage.root(), destination);
+ }
+
+ template <typename I = Impl, typename F = range_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveRow(QSpan<const int> source, QSpan<const int> destination)
+ {
+ return moveRows(source, 1, destination);
+ }
+
+ template <typename I = Impl, typename F = range_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveRows(QSpan<const int> source, int count, QSpan<const int> destination)
+ {
+ return storage->moveRows(this->index(source.first(source.size() - 1), 0),
+ source.back(),
+ count,
+ this->index(destination.first(destination.size() - 1), 0),
+ destination.back());
+ }
+
+ template <typename I = Impl, if_canInsertColumns<I> = true>
+ bool insertColumn(int before)
+ {
+ return storage.m_model->insertColumn(before);
+ }
+
+ template <typename D = row_type, typename I = Impl,
+ if_canInsertColumns<I> = true, if_compatible_data<D> = true>
+ bool insertColumn(int before, D &&data)
+ {
+ return insertColumnImpl(before, storage.root(), std::forward<D>(data));
+ }
+
+ template <typename C, typename I = Impl,
+ if_canInsertColumns<I> = true, if_compatible_data_range<C> = true>
+ bool insertColumns(int before, C &&data)
+ {
+ return insertColumnsImpl(before, storage.root(), std::forward<C>(data));
+ }
+
+ template <typename I = Impl, if_canRemoveColumns<I> = true>
+ bool removeColumn(int column)
+ {
+ return storage.m_model->removeColumn(column);
+ }
+
+ template <typename I = Impl, if_canRemoveColumns<I> = true>
+ bool removeColumns(int column, int count)
+ {
+ return storage->removeColumns(column, count, {});
+ }
+
+ template <typename F = row_features, if_canMoveItems<F> = true>
+ bool moveColumn(int from, int to)
+ {
+ return moveColumns(from, 1, to);
+ }
+
+ template <typename F = row_features, if_canMoveItems<F> = true>
+ bool moveColumns(int from, int count, int to)
+ {
+ return storage->moveColumns(storage.root(), from, count, storage.root(), to);
+ }
+
+ template <typename I = Impl, typename F = row_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveColumn(QSpan<const int> source, int to)
+ {
+ const QModelIndex parent = this->index(source.first(source.size() - 1), 0);
+ return storage->moveColumns(parent, source.back(), 1, parent, to);
+ }
+
+ template <typename I = Impl, typename F = row_features,
+ if_canMoveItems<F> = true, if_tree<I> = true>
+ bool moveColumns(QSpan<const int> source, int count, int destination)
+ {
+ const QModelIndex parent = this->index(source.first(source.size() - 1), 0);
+ return storage->moveColumns(parent, source.back(), count, parent, destination);
+ }
+
+private:
+ friend inline
+ bool comparesEqual(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs) noexcept
+ {
+ return lhs.storage.m_model == rhs.storage.m_model;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(QRangeModelAdapter)
+
+ friend inline
+ bool comparesEqual(const QRangeModelAdapter &lhs, const range_type &rhs)
+ {
+ return lhs.range() == rhs;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(QRangeModelAdapter, range_type)
+
+
+ void emitDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
+ {
+ Q_EMIT storage.implementation()->dataChanged(topLeft, bottomRight, {});
+ }
+
+ template <typename P>
+ static auto setParentRow(P protocol, row_type &newRow, row_ptr parentRow)
+ -> decltype(protocol.setParentRow(std::declval<row_type&>(), std::declval<row_ptr>()))
+ {
+ return protocol.setParentRow(newRow, parentRow);
+ }
+
+ template <typename ...Args> static constexpr void setParentRow(Args &&...) {}
+
+ template <typename D>
+ bool insertRowImpl(int before, const QModelIndex &parent, D &&data)
+ {
+ return storage.implementation()->doInsertRows(before, 1, parent, [&data, this]
+ (range_type &range, auto parentRow, int row, int count) {
+ Q_UNUSED(this);
+ const auto oldSize = range.size();
+ auto newRow = range.emplace(QRangeModelDetails::pos(range, row), std::forward<D>(data));
+ setParentRow(storage.implementation()->protocol(), *newRow, parentRow);
+ return range.size() == oldSize + count;
+ });
+ }
+
+ template <typename LHS>
+ static auto selfInsertion(LHS *lhs, LHS *rhs) -> decltype(lhs == rhs)
+ {
+ if (lhs == rhs) {
+#ifndef QT_NO_DEBUG
+ qCritical("Inserting data into itself is not supported");
+#endif
+ return true;
+ }
+ return false;
+ }
+ template <typename LHS, typename RHS>
+ static constexpr bool selfInsertion(LHS *, RHS *) { return false; }
+
+ template <typename C>
+ bool insertRowsImpl(int before, const QModelIndex &parent, C &&data)
+ {
+ bool result = false;
+ result = storage->doInsertRows(before, int(std::size(data)), parent, [&data, this]
+ (range_type &range, auto parentRow, int row, int count){
+ Q_UNUSED(parentRow);
+ Q_UNUSED(this);
+ const auto pos = QRangeModelDetails::pos(range, row);
+ const auto oldSize = range.size();
+
+ auto dataRange = [&data]{
+ if constexpr (std::is_rvalue_reference_v<C&&>) {
+ return std::make_pair(
+ std::move_iterator(std::begin(data)),
+ std::move_iterator(std::end(data))
+ );
+ } else {
+ return std::make_pair(std::begin(data), std::end(data));
+ }
+ }();
+
+ if constexpr (range_features::has_insert_range) {
+ if (selfInsertion(&range, &data))
+ return false;
+ auto start = range.insert(pos, dataRange.first, dataRange.second);
+ if constexpr (protocol_traits::has_setParentRow) {
+ while (count) {
+ setParentRow(storage->protocol(), *start, parentRow);
+ ++start;
+ --count;
+ }
+ } else {
+ Q_UNUSED(start);
+ }
+ } else {
+ auto newRow = range.insert(pos, count, row_type{});
+ while (dataRange.first != dataRange.second) {
+ *newRow = *dataRange.first;
+ setParentRow(storage->protocol(), *newRow, parentRow);
+ ++dataRange.first;
+ ++newRow;
+ }
+ }
+ return range.size() == oldSize + count;
+ });
+ return result;
+ }
+
+ template <typename D, typename = void>
+ struct DataFromList {
+ static constexpr auto first(D &data) { return &data; }
+ static constexpr auto next(D &, D *entry) { return entry; }
+ };
+
+ template <typename D>
+ struct DataFromList<D, std::enable_if_t<QRangeModelDetails::range_traits<D>::value>>
+ {
+ static constexpr auto first(D &data) { return std::begin(data); }
+ static constexpr auto next(D &data, typename D::iterator entry)
+ {
+ ++entry;
+ if (entry == std::end(data))
+ entry = first(data);
+ return entry;
+ }
+ };
+
+ template <typename D, typename = void> struct RowFromTable
+ {
+ static constexpr auto first(D &data) { return &data; }
+ static constexpr auto next(D &, D *entry) { return entry; }
+ };
+
+ template <typename D>
+ struct RowFromTable<D, std::enable_if_t<std::conjunction_v<
+ QRangeModelDetails::range_traits<D>,
+ QRangeModelDetails::range_traits<typename D::value_type>
+ >>
+ > : DataFromList<D>
+ {};
+
+ template <typename D>
+ bool insertColumnImpl(int before, const QModelIndex &parent, D data)
+ {
+ auto entry = DataFromList<D>::first(data);
+
+ return storage->doInsertColumns(before, 1, parent, [&entry, &data]
+ (auto &range, auto pos, int count) {
+ const auto oldSize = range.size();
+ range.insert(pos, *entry);
+ entry = DataFromList<D>::next(data, entry);
+ return range.size() == oldSize + count;
+ });
+ }
+
+ template <typename C>
+ bool insertColumnsImpl(int before, const QModelIndex &parent, C data)
+ {
+ bool result = false;
+ auto entries = RowFromTable<C>::first(data);
+ auto begin = std::begin(*entries);
+ auto end = std::end(*entries);
+ result = storage->doInsertColumns(before, int(std::size(*entries)), parent,
+ [&begin, &end, &entries, &data](auto &range, auto pos, int count) {
+ const auto oldSize = range.size();
+ if constexpr (row_features::has_insert_range) {
+ range.insert(pos, begin, end);
+ } else {
+ auto start = range.insert(pos, count, {});
+ std::copy(begin, end, start);
+ }
+ entries = RowFromTable<C>::next(data, entries);
+ begin = std::begin(*entries);
+ end = std::end(*entries);
+ return range.size() == oldSize + count;
+ });
+ return result;
+ }
+};
+
+template <typename Range, typename Protocol>
+QRangeModelAdapter(Range &&, Protocol &&) -> QRangeModelAdapter<Range, Protocol>;
+
+template <typename Range>
+QRangeModelAdapter(Range &&) -> QRangeModelAdapter<Range, void>;
+
+QT_END_NAMESPACE
+
+#endif // QRANGEMODELADAPTER_H
diff --git a/src/corelib/itemmodels/qrangemodeladapter.qdoc b/src/corelib/itemmodels/qrangemodeladapter.qdoc
new file mode 100644
index 00000000000..263bff0dd0c
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter.qdoc
@@ -0,0 +1,920 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+// Qt-Security score:insignificant reason:default
+
+#include <QtCore/qrangemodeladapter.h>
+
+/*!
+ \class QRangeModelAdapter
+ \inmodule QtCore
+ \since 6.11
+ \preliminary
+ \ingroup model-view
+ \brief QRangeModelAdapter provides QAbstractItemModel-compliant access to any C++ range.
+ \compares equality
+ \compareswith equality Range
+ \endcompareswith
+
+ QRangeModelAdapter provides a type-safe and structure-aware C++ API around
+ a C++ range and a QRangeModel. Modifications made to the C++ range using
+ the adapter will inform clients of the QRangeModel about the changes. This
+ makes sure that item views are updated, caches are cleaned, and persistent
+ item indexes are invalidated and adapted correctly.
+
+ \section1 Construction and model ownership
+
+ QRangeModelAdapter has to be constructed from a C++ range. As with
+ QRangeModel, the range can be provided by lvalue or rvalue reference, as a
+ reference wrapper, and as a raw or smart pointer.
+
+ \snippet qrangemodeladapter/main.cpp construct
+
+ Constructing the adapter from a range implicitly constructs a QRangeModel
+ instance from that same range. Use model() to get it, and pass it to Qt
+ Widgets or Qt Quick item views as usual.
+
+ \snippet qrangemodeladapter/main.cpp use-model
+
+ The adapter owns the model. QRangeModelAdapter is a value type, so it can be
+ copied and moved. All copies share the same QRangeModel, which will be
+ destroyed when the last copy of the adapter is destroyed.
+
+ If the adapter was created from an lvalue or rvalue reference, then the
+ adapter and model will operate on a copy of the original range object.
+ Otherwise, modifications made through the adapter or model will be written
+ to the original range object. To get the updated range, use the range()
+ function explicitly, or use the implicit conversion of the adapter to a
+ the range.
+
+ \snippet qrangemodeladapter/main.cpp get-range
+
+ To replace the entire range data with data from another (compatible) range,
+ use the setRange() function or the assignment operator.
+
+ \snippet qrangemodeladapter/main.cpp set-range
+
+ \section1 Accessing item data
+
+ The QRangeModelAdapter API provides type-safe read and write access to the
+ range that the model operates on. The adapter API is based on the typical
+ API for C++ containers and ranges, including iterators. To access
+ individual rows and items, use at(), the corresponding subscript
+ \c{operator[]}, or data(). Which overloads of those functions are available
+ depends on the range for which the adapter was constructed.
+
+ \section2 Reading item data as a QVariant
+
+ The data() function always returns a QVariant with the value stored at the
+ specified position and role. In a list, an item can be accessed by a single
+ integer value specifying the row:
+
+ \snippet qrangemodeladapter/main.cpp list-data
+
+ If the range is a table, then items are specified by row and column:
+
+ \snippet qrangemodeladapter/main.cpp table-data
+
+ If the range is a tree, then items are located using a path of rows, and a
+ single column value:
+
+ \snippet qrangemodeladapter/main.cpp tree-data
+
+ Using a single integer as the row provides access to the toplevel tree
+ items.
+
+ \snippet qrangemodeladapter/main.cpp multirole-data
+
+ If no role is specified, then the QVariant will hold the entire item at the
+ position. Use the \l{QVariant::fromValue()} template function to retrieve a
+ copy of the item.
+
+ \section2 Reading and writing using at()
+
+ That the data() function returns a QVariant makes it flexible, but removes
+ type safety. For ranges where all items are of the same type, the at()
+ function provides a type-safe alternative that is more compatible with
+ regular C++ containers. As with data(), at() overloads exist to access an
+ item at a row for lists, at a row/column pair for table, and a path/column
+ pair for trees. However, at() always returns the whole item at the
+ specified position; it's not possible to read an individual role values for
+ an item.
+
+ As expected from a C++ container API, the const overloads of at() (and the
+ corresponding subscript \c{operator[]}) provide immutable access to the
+ value, while the mutable overloads return a reference object that a new
+ value can be assigned to. Note that a QRangeModelAdapter operating on a
+ const range behaves in that respect like a const QRangeModelAdapter. The
+ mutable overloads are removed from the overload set, so the compiler will
+ always select the const version. Trying to call a function that modifies a
+ range will result in a compiler error, even if the adapter itself is
+ mutable:
+
+ \snippet qrangemodeladapter/main.cpp read-only
+
+ The returned reference objects are wrappers that convert implicitly to the
+ underlying type, have a \c{get()} function to explicitly access the
+ underlying value, and an \c{operator->()} that provides direct access to
+ const member functions. However, to prevent accidental data changes that
+ would bypass the QAbstractItemModel notification protocol, those reference
+ objects prevent direct modifications of the items.
+
+ \note Accessing the reference object always makes a call to the model to get
+ a copy of the value. This can be expensive; for performance critical access
+ to data, store a copy.
+
+ \section3 Item access
+
+ If the range is represented as a list, then only the overloads taking a row
+ are available.
+
+ \snippet qrangemodeladapter/main.cpp list-access
+
+ The const overload returns the item at that row, while the mutable overload
+ returns a wrapper that implicitly converts to and from the value type of the
+ list.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole
+
+ Assign a value to the wrapper to modify the data in the list. The model will
+ emit \l{QAbstractItemModel::}{dataChanged()} for all roles.
+
+ When using the mutable overloads, you can also access the item type's const
+ members using the overloaded arrow operator.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole-member-access
+
+ It is not possible to access non-const members of the item. Such
+ modifications would bypass the adapter, which couldn't notify the model
+ about the changes. To modify the value stored in the model, make a copy,
+ modify the properties of the copy, and then write that copy back.
+
+ \snippet qrangemodeladapter/main.cpp list-access-multirole-write-back
+
+ This will make the model emit \l{QAbstractItemModel::}{dataChanged()} for
+ this item, and for all roles.
+
+ If the range is represented as a table, then you can access an individual
+ item by row and columns, using the \l{at(int, int)}{at(row, column)}
+ overload. For trees, that overload gives access to top-level items, while
+ the \l{at(QSpan<const int>, int)}{at(path, column)} overload provides
+ access to items nested within the tree.
+
+ Accessing an individual item in a table or tree is equivalent to accessing
+ an item in a list.
+
+ \snippet qrangemodeladapter/main.cpp table-item-access
+
+ If the range doesn't store all columns using the same data type, then
+ \c{at(row,column)} returns a (a reference wrapper with a) QVariant holding
+ the item.
+
+ \snippet qrangemodeladapter/main.cpp table-mixed-type-access
+
+ \section2 Accessing rows in tables and trees
+
+ For tables and trees, the overloads of at() and subscript \c{operator[]}
+ without the column parameter provide access to the entire row. The value
+ returned by the const overloads will be a reference type that gives access
+ to the row data. If that row holds pointers, then that reference type will
+ be a view of the row, giving access to pointers to const items.
+
+ \section3 Table row access
+
+ The \l{at(int)} overload is still available, but it returns the entire table
+ wor. This makes it possible to work with all the values in the row at once.
+
+ \snippet qrangemodeladapter/main.cpp table-row-const-access
+
+ As with items, the const overload provides direct access to the row type,
+ while the mutable overload returns a wrapper that acts as a reference to the
+ row. The wrapper provides access to const member functions using the
+ overloaded arrow operator. To modify the values, write a modified row type
+ back.
+
+ \snippet qrangemodeladapter/main.cpp table-row-access
+
+ When assigning a new value to the row, then the model emits the
+ \l{QAbstractItemModel::}{dataChanged()} signal for all items in the row,
+ and for all roles.
+
+ \note When using a row type with runtime sizes, such as a \c{std::vector} or
+ a QList, make sure that the new row has the correct size.
+
+ \section3 Tree row access
+
+ Rows in trees are specified by a sequence of integers, one entry for each
+ level in the tree. Note that in the following snippets, the Tree is a range
+ holding raw row pointers, and that the adapter is created with an rvalue
+ reference of that range. This gives the QRangeModel ownership over the row
+ data.
+
+ \snippet qrangemodeladapter/main.cpp tree-row-access
+
+ The overload of \l{at(int)}{at()} taking a single row value provides
+ access to the top-level rows, or items in the top-level rows.
+
+ \snippet qrangemodeladapter/main.cpp tree-item-access
+
+ The basic pattern for accessing rows and items in a tree is identical to
+ accessing rows and items in a table. However, the adapter will make sure
+ that the tree structure is maintained when modifying entire rows.
+
+ \snippet qrangemodeladapter/main.cpp tree-row-write
+
+ In this example, a new row object is created, and assigned to the first
+ child of the first top-level item.
+
+ \section1 Iterator API
+
+ Use begin() and end() to get iterators over the rows of the model, or use
+ ranged-for. If the range is a list of items, dereferencing the iterator
+ will give access to item data.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-list
+
+ As with the const and mutable overloads of at(), a mutable iterator will
+ dereference to a wrapper.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-list
+
+ Use the overloaded arrow operator to access const members of the item type,
+ and assign to the wrapper to replace the value.
+
+ It the range is a table or tree, then iterating over the model will give
+ access to the rows.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-table
+
+ Both the const and the mutable iterator will dereference to a wrapper type
+ for the row. This make sure that we can consistently iterate over each
+ column, even if the underlying row type is not a range (e.g. it might be a
+ tuple or gadget).
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-const-table-items
+
+ When iterating over a mutable table we can overwrite the entire row.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table
+
+ The model emits the \l{QAbstractItemModel::}{dataChanged()} signal for
+ all items in all row, and for all roles.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-mutable-table-items
+
+ Iterating over the mutable rows allows us to modify individual items.
+
+ When iterating over a tree, the row wrapper has two additional member
+ functions, hasChildren() and children(), that allow us to traverse the
+ entire tree using iterators.
+
+ \snippet qrangemodeladapter/main.cpp ranged-for-tree
+
+ The object returned by children() is a QRangeModelAdapter operating on
+ the same model as the callee, but all operations will use the source row
+ index as the parent index.
+
+ \sa QRangeModel
+*/
+
+/*!
+ \typedef QRangeModelAdapter::const_row_reference
+*/
+
+/*!
+ \typedef QRangeModelAdapter::row_reference
+*/
+
+/*!
+ \typedef QRangeModelAdapter::range_type
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range, Protocol &&protocol)
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::QRangeModelAdapter(Range &&range)
+
+ Constructs a QRangeModelAdapter that operates on \a range. For tree ranges,
+ the optional \a protocol will be used for tree traversal.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator==(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs)
+
+ \return whether \a lhs is equal to \a rhs. Two adapters are equal if they
+ both hold the same \l{model()}{model} instance.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> bool QRangeModelAdapter<Range, Protocol, Model>::operator!=(const QRangeModelAdapter &lhs, const QRangeModelAdapter &rhs)
+
+ \return whether \a lhs is not equal to \a rhs. Two adapters are equal if
+ they both hold the same \l{model()}{model} instance.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> Model *QRangeModelAdapter<Range, Protocol, Model>::model() const
+
+ \return the QRangeModel instance created by this adapter.
+
+ \sa range(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> const QRangeModelAdapter<Range, Protocol, Model>::range_type &QRangeModelAdapter<Range, Protocol, Model>::range() const
+ \fn template <typename Range, typename Protocol, typename Model> QRangeModelAdapter<Range, Protocol, Model>::operator const QRangeModelAdapter<Range, Protocol, Model>::range_type &() const
+
+ \return a const reference to the range that the model adapter operates on.
+
+ \sa setRange(), at(), model()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> void QRangeModelAdapter<Range, Protocol, Model>::setRange(NewRange &&newRange)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename NewRange, QRangeModelAdapter<Range, Protocol, Model>::if_assignable_range<NewRange>> QRangeModelAdapter &QRangeModelAdapter<Range, Protocol, Model>::operator=(NewRange &&newRange)
+
+ Assigns \a newRange to the stored range, possibly using move semantics.
+ This function makes the model() emit the \l{QAbstractItemModel::}{modelAboutToBeReset()}
+ and \l{QAbstractItemModel::}{modelReset()} signals.
+
+ \constraints \c Range is mutable, and \a newRange is of a type that can be
+ assigned to \c Range.
+
+ \sa range(), at(), model()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row) const
+ \overload
+
+ Returns the QModelIndex for \a row.
+
+ \constraints \c Range is a one-dimensional list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(int row, int column) const
+ \overload
+
+ Returns the QModelIndex for the item at \a row, \a column.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QModelIndex QRangeModelAdapter<Range, Protocol, Model>::index(QSpan<const int> path, int column) const
+ \overload
+
+ Returns the QModelIndex for the item at \a column for the row in the tree
+ specified by \a path.
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::columnCount() const
+
+ \return the number of columns. This will be one if the \c Range represents a
+ list, otherwise this returns be the number of elements in each row.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> int QRangeModelAdapter<Range, Protocol, Model>::rowCount() const
+ \overload
+
+ \return the number of rows. If the \c Range represents a list or table, then
+ this is the number of rows. For trees, this is the number of top-level rows.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::rowCount(QSpan<const int> row) const
+ \overload
+
+ \return the number of rows under \a row.
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> int QRangeModelAdapter<Range, Protocol, Model>::hasChildren(QSpan<const int> row) const
+ \overload
+
+ \return whether there are any rows under \a row.
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item at \a row, or an invalid QVariant if there is no item. If \a role is
+ not specified, then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a list.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, const QVariant &value, int role)
+
+ Sets the \a role data for the item at \a row to \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is a mutable list.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(int row, int column, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item referred to by a \a row and \a column, or an invalid QVariant if there
+ is no data stored for that position or role. If \a role is not specified,
+ then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a table or tree.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(int row, int column, const QVariant &value, int role)
+
+ Sets the \a role data for the item referred to by \a row and \a column to
+ \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is mutable, and not a list.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> QVariant QRangeModelAdapter<Range, Protocol, Model>::data(QSpan<const int> path, int column, int role) const
+
+ \return a QVariant holding the data stored under the given \a role for the
+ item referred to by \a path and \a column, or an invalid QVariant if there
+ is no data stored for that position or role. If \a role is not specified,
+ then returns a QVariant holding the complete item.
+
+ \constraints \c Range is a tree.
+
+ \sa setData(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> bool QRangeModelAdapter<Range, Protocol, Model>::setData(QSpan<const int> path, int column, const QVariant &value, int role)
+
+ Sets the \a role data for the item referred to by \a path and \a column to
+ \a value.
+
+ Returns \c{true} if successful; otherwise returns \c{false}.
+
+ \constraints \c Range is a mutable tree.
+
+ \sa data(), at()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const
+
+ \return the value at \a row as the type stored in \c Range.
+
+ \constraints \c Range is a list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return the value at \a row wrapped into a mutable reference to the type
+ stored in \c Range.
+
+//! [data-ref]
+ \note Modifications to the range will invalidate that reference. To modify
+ the reference, assign a new value to it. Unless the value stored in the
+ \c Range is a pointer, it is not possible to access individual members of
+ the stored value.
+//! [data-ref]
+
+ \constraints \c Range is a mutable list.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row) const
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> QRangeModelAdapter<Range, Protocol, Model>::const_row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row) const
+
+ \return a constant reference to the row at \a row, as stored in \c Range.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_table<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> QRangeModelAdapter<Range, Protocol, Model>::row_reference QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return a mutable reference to the row at \a row, as stored in \c Range.
+
+ \constraints \c Range is a mutable table, but not a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row)
+
+ \return a mutable wrapper holding a reference to the tree row specified by \a row.
+
+//! [treerow-ref]
+ To modify the tree row, assign a new value to it. Assigning a new tree row
+ will set the parent the new tree row to be the parent of the old tree row.
+ However, neither the old nor the new tree row must have any child rows. To
+ access the tree row, dereferencing the wrapper using \c{operator*()}, or use
+ \c{operator->()} to access tree row members.
+
+ \note Modifications to the range will invalidate the wrapper.
+//! [treerow-ref]
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column) const
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column) const
+\endomit
+
+ \return a copy of the value stored as the item specified by \a row and
+ \a column. If the item is a multi-role item, then this returns a copy of
+ the entire item. If the rows in the \c Range store different types at
+ different columns, then the return type will be a QVariant.
+
+ \constraints \c Range is a table or tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(int row, int column)
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::unless_list<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](int row, int column)
+\endomit
+
+ \return a mutable reference to the value stored as the item specified by
+ \a row and \a column. If the item is a multi-role item, then this will be
+ a reference to the entire item.
+
+ \include qrangemodeladapter.qdoc data-ref
+
+ \constraints \c Range is a mutable table.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path)
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path)
+
+ \return a mutable wrapper holding a reference to the tree row specified by \a path.
+
+ \include qrangemodeladapter.qdoc treerow-ref
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column) const
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column) const
+\endomit
+
+ \return a copy of the value stored as the item specified by \a path and
+ \a column. If the item is a multi-role item, then this returns a copy of
+ the entire item. If the rows in the \c Range store different types at
+ different columns, then the return type will be a QVariant.
+
+ \constraints \c Range is a tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::at(QSpan<const int> path, int column)
+\omit
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>, QRangeModelAdapter<Range, Protocol, Model>::if_writable<I>> auto QRangeModelAdapter<Range, Protocol, Model>::operator[](QSpan<const int> path, int column)
+\endomit
+
+ \return a mutable reference to the value stored as the item specified by
+ \a path and \a column. If the item is a multi-role item, then this will be
+ a reference to the entire item. If the rows in the \c Range store different
+ types at different columns, then the return type will be a QVariant.
+
+ \include qrangemodeladapter.qdoc data-ref
+
+ \constraints \c Range is a mutable tree.
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before)
+ \overload
+
+ Inserts a single empty row before the row at \a before, and returns whether
+ the insertion was successful.
+//! [insert-row-appends]
+ If \a before is the same value as rowCount(), then the new row will be appended.
+//! [insert-row-appends]
+
+ \constraints \c Range supports insertion of elements.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before)
+ \overload
+
+ Inserts a single empty row before the row at the path specified by \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(int before, D &&data)
+ \overload
+
+ Inserts a single row constructed from \a data before the row at \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range supports insertion of elements, and if
+ a row can be constructed from \a data.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row<D>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRow(QSpan<const int> before, D &&data)
+ \overload
+
+ Inserts a single row constructed from \a data before the row at \a before,
+ and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements, and if
+ a row can be constructed from \a data.
+
+ \sa insertRows(), removeRow(), insertColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(int before, C &&data)
+ \overload
+
+ Inserts rows constructed from the elements in \a data before the row at
+ \a before, and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range supports insertion of elemnets, and if
+ rows can be constructed from the elements in \a data.
+
+ \sa insertRow(), removeRows(), insertColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_row_range<C>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertRows(QSpan<const int> before, C &&data)
+ \overload
+
+ Inserts rows constructed from the elements in \a data before the row at
+ \a before, and returns whether the insertion was successful.
+ \include qrangemodeladapter.qdoc insert-row-appends
+
+ \constraints \c Range is a tree that supports insertion of elements, and if
+ rows can be constructed from the elements in \a data.
+
+ \sa insertRow(), removeRows(), insertColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(int row)
+ \overload
+
+ Removes the given \a row and returns whether the removal was successful.
+
+ \constraints \c Range supports the removal of elements.
+
+ \sa removeRows(), removeColumn(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRow(QSpan<const int> path)
+ \overload
+
+ Removes the row at the given \a path, including all children of that row,
+ and returns whether the removal was successful.
+
+ \constraints \c Range is a tree that supports the removal of elements.
+
+ \sa removeRows(), removeColumn(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(int row, int count)
+ \overload
+
+ Removes \a count rows starting at \a row, and returns whether the removal
+ was successful.
+
+ \constraints \c Range supports the removal of elements.
+
+ \sa removeRow(), removeColumns(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveRows<I>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeRows(QSpan<const int> path, int count)
+ \overload
+
+ Removes \a count rows starting at the row specified by \a path, and returns
+ whether the removal was successful.
+
+ \constraints \c Range is a tree that supports the removal of elements.
+
+ \sa removeRow(), removeColumns(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(int source, int destination)
+ \overload
+
+ Moves the row at \a source to the position at \a destination, and returns
+ whether the row was successfully moved.
+
+ \constraints \c Range supports moving of elements.
+
+ \sa insertRow(), removeRow(), moveColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRow(QSpan<const int> source, QSpan<const int> destination)
+ \overload
+
+ Moves the tree branch at \a source to the position at \a destination, and
+ returns whether the branch was successfully moved.
+
+ \constraints \c Range is a tree that supports moving of elements.
+
+ \sa insertRow(), removeRow(), moveColumn()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(int source, int count, int destination)
+ \overload
+
+ Moves \a count rows starting at \a source to the position at \a destination,
+ and returns whether the rows were successfully moved.
+
+ \constraints \c Range supports moving of elements.
+
+ \sa insertRows(), removeRows(), moveColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveRows(QSpan<const int> source, int count, QSpan<const int> destination)
+ \overload
+
+ Moves \a count tree branches starting at \a source to the position at
+ \a destination, and returns whether the rows were successfully moved.
+
+ \constraints \c Range is a tree that supports moving of elements.
+
+ \sa insertRows(), removeRows(), moveColumns()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before)
+ \overload
+
+ Inserts a single empty column before the column specified by \a before into
+ all rows, and returns whether the insertion was successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ \constraints \c Range has rows that support insertion of elements.
+
+ \sa removeColumn(), insertColumns(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename D, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data<D>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumn(int before, D &&data)
+ \overload
+
+ Inserts a single column constructed from \a data before the column specified
+ by \a before into all rows, and returns whether the insertion was successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ If \a data is a single value, then the new entry in all rows will be constructed
+ from that single value.
+
+ If \a data is a container, then the elements in that container will be used
+ sequentially to construct the column for each subsequent row. If there are
+ fewer elements in \a data than there are rows, then function wraps around
+ and starts again from the first element.
+
+ \code
+ \endcode
+
+ \constraints \c Range has rows that support insertion of elements, and the
+ elements can be constructed from the entries in \a data.
+
+ \sa removeColumn(), insertColumns(), insertRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename C, typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canInsertColumns<I>, QRangeModelAdapter<Range, Protocol, Model>::if_compatible_data_range<C>> bool QRangeModelAdapter<Range, Protocol, Model>::insertColumns(int before, C &&data)
+
+ Inserts columns constructed from the elements in \a data before the column
+ specified by \a before into all rows, and returns whether the insertion was
+ successful.
+//! [insert-column-appends]
+ If \a before is the same value as columnCount(), then the column will be
+ appended to each row.
+//! [insert-column-appends]
+
+ If the elements in \a data are values, then the new entries in all rows will
+ be constructed from those values.
+
+ If the elements in \a data are containers, then the entries in the outer
+ container will be used sequentially to construct the new entries for each
+ subsequent row. If there are fewer elements in \a data than there are rows,
+ then the function wraps around and starts again from the first element.
+
+ \code
+ \endcode
+
+ \constraints \c Range has rows that support insertion of elements, and the
+ elements can be constructed from the entries in \a data.
+
+ \sa removeColumns(), insertColumn(), insertRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumn(int column)
+
+ Removes the given \a column from each row, and returns whether the removal
+ was successful.
+
+ \constraints \c Range has rows that support removal of elements.
+
+ \sa insertColumn(), removeColumns(), removeRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, QRangeModelAdapter<Range, Protocol, Model>::if_canRemoveColumns<I>> bool QRangeModelAdapter<Range, Protocol, Model>::removeColumns(int column, int count)
+
+ Removes \a count columns starting by the given \a column from each row, and
+ returns whether the removal was successful.
+
+ \constraints \c Range has rows that support removal of elements.
+
+ \sa insertColumns(), removeColumn(), removeRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(int from, int to)
+
+ Moves the column at \a from to the column at \a to, and returns whether the
+ column was successfully moved.
+
+ \constraints \c Range has rows that support moving of elements.
+
+ \sa insertColumn(), removeColumn(), moveColumns(), moveRow()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(int from, int count, int to)
+
+ Moves \a count columns starting at \a from to the position at \a to, and
+ returns whether the columns were successfully moved.
+
+ \constraints \c Range has rows that support moving of elements.
+
+ \sa insertColumns(), removeColumns(), moveColumn(), moveRows()
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumn(QSpan<const int> source, int to)
+ \internal Not possible to create a tree from a row type that can rotate/splice?
+*/
+
+/*!
+ \fn template <typename Range, typename Protocol, typename Model> template <typename I, typename F, QRangeModelAdapter<Range, Protocol, Model>::if_canMoveItems<F>, QRangeModelAdapter<Range, Protocol, Model>::if_tree<I>> bool QRangeModelAdapter<Range, Protocol, Model>::moveColumns(QSpan<const int> source, int count, int to)
+ \internal Not possible to create a tree from a row type that can rotate/splice?
+*/
diff --git a/src/corelib/itemmodels/qrangemodeladapter_impl.h b/src/corelib/itemmodels/qrangemodeladapter_impl.h
new file mode 100644
index 00000000000..ad1dd152b22
--- /dev/null
+++ b/src/corelib/itemmodels/qrangemodeladapter_impl.h
@@ -0,0 +1,402 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QRANGEMODELADAPTER_IMPL_H
+#define QRANGEMODELADAPTER_IMPL_H
+
+#ifndef Q_QDOC
+
+#ifndef QRANGEMODELADAPTER_H
+#error Do not include qrangemodeladapter_impl.h directly
+#endif
+
+#if 0
+#pragma qt_sync_skip_header_check
+#pragma qt_sync_stop_processing
+#endif
+
+#include <QtCore/qrangemodel.h>
+#include <QtCore/qspan.h>
+#include <set>
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace QRangeModelDetails
+{
+template <typename Range, typename Protocol>
+using RangeImplementation = std::conditional_t<std::is_void_v<Protocol>,
+ std::conditional_t<is_tree_range<Range>::value,
+ QGenericTreeItemModelImpl<Range, DefaultTreeProtocol<Range>>,
+ QGenericTableItemModelImpl<Range>
+ >,
+ QGenericTreeItemModelImpl<Range, Protocol>
+ >;
+
+// we can't use wrapped_t, we only want to unpack smart pointers, and maintain
+// the pointer nature of the type.
+template <typename T, typename = void>
+struct data_type { using type = T; };
+template <>
+struct data_type<void> { using type = QVariant; };
+
+// pointer types of iterators use QtPrivate::ArrowProxy if the type does not
+// provide operator->() (or is a pointer).
+template <typename T, typename = void> struct test_pointerAccess : std::false_type {};
+template <typename T> struct test_pointerAccess<T *> : std::true_type {};
+template <typename T>
+struct test_pointerAccess<T, std::void_t<decltype(std::declval<T>().operator->())>>
+ : std::true_type
+{};
+
+template <typename T>
+using data_pointer_t = std::conditional_t<test_pointerAccess<T>::value,
+ T, QtPrivate::ArrowProxy<T>>;
+
+// Helpers to make a type const "in depth", taking into account raw pointers
+// and wrapping types, like smart pointers and std::reference_wrapper.
+
+// We need to return data by value, not by reference, as we might only have
+// temporary values (i.e. a QVariant returned by QAIM::data).
+template <typename T, typename = void> struct AsConstData { using type = T; };
+template <typename T> struct AsConstData<const T &> { using type = T; };
+template <typename T> struct AsConstData<T *> { using type = const T *; };
+template <template <typename> typename U, typename T>
+struct AsConstData<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>>
+{ using type = U<const T>; };
+template <typename T> struct AsConstData<std::reference_wrapper<T>>
+{ using type = std::reference_wrapper<const T>; };
+
+template <typename T> using asConst_t = typename AsConstData<T>::type;
+
+// Rows get wrapped into a "view", as a begin/end iterator/sentinel pair.
+// The iterator dereferences to the const version of the value returned by
+// the underlying iterator.
+// Could be replaced with std::views::sub_range in C++ 20.
+template <typename const_row_type, typename Iterator, typename Sentinel>
+struct RowView
+{
+ // this is similar to C++23's std::basic_const_iterator, but we don't want
+ // to convert to the underlying const_iterator.
+ struct iterator
+ {
+ using value_type = asConst_t<typename Iterator::value_type>;
+ using difference_type = typename Iterator::difference_type;
+ using pointer = QRangeModelDetails::data_pointer_t<value_type>;
+ using reference = value_type;
+ using const_reference = value_type;
+ using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
+
+ template <typename I, typename Category>
+ static constexpr bool is_atLeast = std::is_base_of_v<Category,
+ typename std::iterator_traits<I>::iterator_category>;
+ template <typename I, typename Category>
+ using if_atLeast = std::enable_if_t<is_atLeast<I, Category>, bool>;
+
+ reference operator*() const { return *m_it; }
+ pointer operator->() const { return operator*(); }
+
+ // QRM requires at least forward_iterator, so we provide both post- and
+ // prefix increment unconditionally
+ friend constexpr iterator &operator++(iterator &it)
+ noexcept(noexcept(++std::declval<Iterator&>()))
+ {
+ ++it.m_it;
+ return it;
+ }
+ friend constexpr iterator operator++(iterator &it, int)
+ noexcept(noexcept(std::declval<Iterator&>()++))
+ {
+ iterator copy = it;
+ ++copy.m_it;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true>
+ friend constexpr iterator &operator--(iterator &it)
+ noexcept(noexcept(--std::declval<I&>()))
+ {
+ --it.m_it;
+ return it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::bidirectional_iterator_tag> = true>
+ friend constexpr iterator operator--(iterator &it, int)
+ noexcept(noexcept(std::declval<I&>()--))
+ {
+ iterator copy = it;
+ --it.m_it;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator &operator+=(iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() += 1))
+ {
+ it.m_it += n;
+ return it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator &operator-=(iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() -= 1))
+ {
+ it.m_it -= n;
+ return it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator+(const iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() + 1))
+ {
+ iterator copy = it;
+ copy.m_it += n;
+ return copy;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator+(difference_type n, const iterator &it)
+ noexcept(noexcept(1 + std::declval<I&>()))
+ {
+ return it + n;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr iterator operator-(const iterator &it, difference_type n)
+ noexcept(noexcept(std::declval<I&>() - 1))
+ {
+ iterator copy = it;
+ copy.m_it = it.m_it - n;
+ return copy;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ constexpr reference operator[](difference_type n) const
+ noexcept(noexcept(I::operator[]()))
+ {
+ return m_it[n];
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr difference_type operator-(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() - std::declval<I&>()))
+ {
+ return lhs.m_it - rhs.m_it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator<(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() < std::declval<I&>()))
+ {
+ return lhs.m_it < rhs.m_it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator<=(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() <= std::declval<I&>()))
+ {
+ return lhs.m_it <= rhs.m_it;
+ }
+
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator>(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() > std::declval<I&>()))
+ {
+ return lhs.m_it > rhs.m_it;
+ }
+ template <typename I = Iterator, if_atLeast<I, std::random_access_iterator_tag> = true>
+ friend constexpr bool operator>=(const iterator &lhs, const iterator &rhs)
+ noexcept(noexcept(std::declval<I&>() >= std::declval<I&>()))
+ {
+ return lhs.m_it >= rhs.m_it;
+ }
+
+ // This would implement the P2836R1 fix from std::basic_const_iterator,
+ // but a const_iterator on a range<pointer> would again allow us to
+ // mutate the pointed-to object, which is exactly what we want to
+ // prevent.
+ /*
+ template <typename CI, std::enable_if_t<std::is_convertible_v<const Iterator &, CI>, bool> = true>
+ operator CI() const
+ {
+ return CI{m_it};
+ }
+
+ template <typename CI, std::enable_if_t<std::is_convertible_v<Iterator, CI>, bool> = true>
+ operator CI() &&
+ {
+ return CI{std::move(m_it)};
+ }
+ */
+
+ friend bool comparesEqual(const iterator &lhs, const iterator &rhs) noexcept
+ {
+ return lhs.m_it == rhs.m_it;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(iterator)
+
+ Iterator m_it;
+ };
+
+ using value_type = typename iterator::value_type;
+ using difference_type = typename iterator::difference_type;
+
+ friend bool comparesEqual(const RowView &lhs, const RowView &rhs) noexcept
+ {
+ return lhs.m_begin == rhs.m_begin;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(RowView)
+
+ template <typename RHS>
+ bool operator==(const RHS &rhs) const noexcept
+ {
+ return m_begin == QRangeModelDetails::begin(rhs);
+ }
+ template <typename RHS>
+ bool operator!=(const RHS &rhs) const noexcept
+ {
+ return !operator==(rhs);
+ }
+
+ value_type at(difference_type n) const { return *std::next(m_begin, n); }
+
+ iterator begin() const { return iterator{m_begin}; }
+ iterator end() const { return iterator{m_end}; }
+
+ Iterator m_begin;
+ Sentinel m_end;
+};
+
+// Const-in-depth mapping for row types. We do store row types, and they might
+// be move-only, so we return them by const reference.
+template <typename T, typename = void> struct AsConstRow { using type = const T &; };
+// Otherwise the mapping for basic row types is the same as for data.
+template <typename T> struct AsConstRow<T *> : AsConstData<T *> {};
+template <template <typename> typename U, typename T>
+struct AsConstRow<U<T>, std::enable_if_t<is_any_shared_ptr<U<T>>::value>> : AsConstData<U<T>> {};
+template <typename T> struct AsConstRow<std::reference_wrapper<T>>
+ : AsConstData<std::reference_wrapper<T>> {};
+
+template <typename T> using if_range = std::enable_if_t<is_range_v<T>, bool>;
+// If the row type is a range, then we assume that the first type is the
+// element type.
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T, Args...>, if_range<R<T, Args...>>>
+{
+ using type = const R<T, Args...> &;
+};
+
+// specialize for range of pointers and smart pointers
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T *, Args...>, if_range<R<T *, Args...>>>
+{
+ using row_type = R<T, Args...>;
+ using const_iterator = typename row_type::const_iterator;
+ using const_row_type = R<asConst_t<T>>;
+ using type = RowView<const_row_type, const_iterator, const_iterator>;
+};
+
+template <template <typename, typename ...> typename R, typename T, typename ...Args>
+struct AsConstRow<R<T, Args...>,
+ std::enable_if_t<std::conjunction_v<is_range<R<T, Args...>>, is_any_shared_ptr<T>>>
+>
+{
+ using row_type = R<T, Args...>;
+ using const_iterator = typename row_type::const_iterator;
+ using const_row_type = R<asConst_t<T>>;
+ using type = RowView<const_row_type, const_iterator, const_iterator>;
+};
+
+template <typename T>
+using asConstRow_t = typename AsConstRow<T>::type;
+
+Q_CORE_EXPORT QVariant qVariantAtIndex(const QModelIndex &index);
+
+template <typename Type>
+static inline Type dataAtIndex(const QModelIndex &index)
+{
+ Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid");
+ QVariant variant = qVariantAtIndex(index);
+
+ if constexpr (std::is_same_v<QVariant, Type>)
+ return variant;
+ else
+ return variant.value<Type>();
+}
+
+template <typename Type>
+static inline Type dataAtIndex(const QModelIndex &index, int role)
+{
+ Q_ASSERT_X(index.isValid(), "QRangeModelAdapter::dataAtIndex", "Index at position is invalid");
+ QVariant variant = index.data(role);
+
+ if constexpr (std::is_same_v<QVariant, Type>)
+ return variant;
+ else
+ return variant.value<Type>();
+}
+
+template <bool isTree = false>
+struct ParentIndex
+{
+ ParentIndex(const QModelIndex &dummy = {}) { Q_ASSERT(!dummy.isValid()); }
+ QModelIndex root() const { return {}; }
+};
+
+template <>
+struct ParentIndex<true>
+{
+ const QModelIndex m_rootIndex;
+ QModelIndex root() const { return m_rootIndex; }
+};
+
+template <typename Model, typename Impl>
+struct AdapterStorage : ParentIndex<Impl::protocol_traits::is_tree>
+{
+ // If it is, then we can shortcut the model and operate on the container.
+ // Otherwise we have to go through the model's vtable. For now, this is always
+ // the case.
+ static constexpr bool isRangeModel = std::is_same_v<Model, QRangeModel>;
+ static_assert(isRangeModel, "The model must be a QRangeModel (not a subclass).");
+ std::shared_ptr<QRangeModel> m_model;
+
+ template <typename I = Impl, std::enable_if_t<I::protocol_traits::is_tree, bool> = true>
+ explicit AdapterStorage(const std::shared_ptr<QRangeModel> &model, const QModelIndex &root)
+ : ParentIndex<Impl::protocol_traits::is_tree>{root}, m_model(model)
+ {
+ }
+
+ explicit AdapterStorage(Model *model)
+ : m_model{model}
+ {}
+
+ const Impl *implementation() const
+ {
+ return static_cast<const Impl *>(QRangeModelImplBase::getImplementation(m_model.get()));
+ }
+
+ Impl *implementation()
+ {
+ return static_cast<Impl *>(QRangeModelImplBase::getImplementation(m_model.get()));
+ }
+
+ auto *operator->()
+ {
+ if constexpr (isRangeModel)
+ return implementation();
+ else
+ return m_model.get();
+ }
+
+ const auto *operator->() const
+ {
+ if constexpr (isRangeModel)
+ return implementation();
+ else
+ return m_model.get();
+ }
+};
+
+} // QRangeModelDetails
+
+QT_END_NAMESPACE
+
+#endif // Q_QDOC
+
+#endif // QRANGEMODELADAPTER_IMPL_H
diff --git a/src/corelib/kernel/qassociativeiterable.cpp b/src/corelib/kernel/qassociativeiterable.cpp
index 8e3072169dd..8a2fc63c441 100644
--- a/src/corelib/kernel/qassociativeiterable.cpp
+++ b/src/corelib/kernel/qassociativeiterable.cpp
@@ -7,6 +7,10 @@
QT_BEGIN_NAMESPACE
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
/*!
\class QAssociativeIterator
\internal
@@ -103,6 +107,7 @@ QVariantConstPointer QAssociativeConstIterator::operator->() const
/*!
\class QAssociativeIterable
+ \deprecated [6.15] Use QMetaAssociation::Iterable instead.
\since 5.2
\inmodule QtCore
\brief The QAssociativeIterable class is an iterable interface for an associative container in a QVariant.
@@ -111,8 +116,6 @@ QVariantConstPointer QAssociativeConstIterator::operator->() const
a QVariant. An instance of QAssociativeIterable can be extracted from a QVariant if it can
be converted to a QVariantHash or QVariantMap or if a custom mutable view has been registered.
- \snippet code/src_corelib_kernel_qvariant.cpp 10
-
The container itself is not copied before iterating over it.
\sa QVariant
@@ -270,20 +273,20 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped)
/*!
\typealias QAssociativeIterable::const_iterator
+ \deprecated [6.15] Use QMetaAssociation::Iterable::ConstIterator instead.
\inmodule QtCore
\brief The QAssociativeIterable::const_iterator allows iteration over a container in a QVariant.
A QAssociativeIterable::const_iterator can only be created by a QAssociativeIterable instance,
and can be used in a way similar to other stl-style iterators.
- \snippet code/src_corelib_kernel_qvariant.cpp 10
-
\sa QAssociativeIterable
*/
/*!
\typealias QAssociativeIterable::iterator
\since 6.0
+ \deprecated [6.15] Use QMetaAssociation::Iterable::Iterator instead.
\inmodule QtCore
\brief The QAssociativeIterable::iterator allows iteration over a container in a QVariant.
@@ -293,4 +296,7 @@ void QAssociativeIterable::setValue(const QVariant &key, const QVariant &mapped)
\sa QAssociativeIterable
*/
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qassociativeiterable.h b/src/corelib/kernel/qassociativeiterable.h
index f3963d350ea..39f66d45fa0 100644
--- a/src/corelib/kernel/qassociativeiterable.h
+++ b/src/corelib/kernel/qassociativeiterable.h
@@ -9,7 +9,21 @@
QT_BEGIN_NAMESPACE
-class Q_CORE_EXPORT QAssociativeIterator : public QIterator<QMetaAssociation>
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
+#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300
+ // GCC < 13 doesn't accept both deprecation and visibility on the same class
+ #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT
+#else
+ #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \
+ Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text)
+#endif
+
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::Iterator instead.")
+QAssociativeIterator : public QIterator<QMetaAssociation>
{
public:
using key_type = QVariant;
@@ -28,7 +42,9 @@ public:
QVariantPointer<QAssociativeIterator> operator->() const;
};
-class Q_CORE_EXPORT QAssociativeConstIterator : public QConstIterator<QMetaAssociation>
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable::ConstIterator instead.")
+QAssociativeConstIterator : public QConstIterator<QMetaAssociation>
{
public:
using key_type = QVariant;
@@ -47,7 +63,9 @@ public:
QVariantConstPointer operator->() const;
};
-class Q_CORE_EXPORT QAssociativeIterable : public QIterable<QMetaAssociation>
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaAssociation::Iterable instead.")
+QAssociativeIterable : public QIterable<QMetaAssociation>
{
public:
using iterator = QTaggedIterator<QAssociativeIterator, void>;
@@ -86,14 +104,12 @@ public:
{
}
- // ### Qt7: Pass QMetaType as value rather than const ref.
QAssociativeIterable(const QMetaAssociation &metaAssociation, const QMetaType &metaType,
void *iterable)
: QIterable(metaAssociation, metaType.alignOf(), iterable)
{
}
- // ### Qt7: Pass QMetaType as value rather than const ref.
QAssociativeIterable(const QMetaAssociation &metaAssociation, const QMetaType &metaType,
const void *iterable)
: QIterable(metaAssociation, metaType.alignOf(), iterable)
@@ -168,6 +184,11 @@ Q_DECLARE_TYPEINFO(QAssociativeIterable, Q_RELOCATABLE_TYPE);
Q_DECLARE_TYPEINFO(QAssociativeIterable::iterator, Q_RELOCATABLE_TYPE);
Q_DECLARE_TYPEINFO(QAssociativeIterable::const_iterator, Q_RELOCATABLE_TYPE);
+#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15
+
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
QT_END_NAMESPACE
#endif // QASSOCIATIVEITERABLE_H
diff --git a/src/corelib/kernel/qiterable.cpp b/src/corelib/kernel/qiterable.cpp
index 976aafd13e5..ca2893e1090 100644
--- a/src/corelib/kernel/qiterable.cpp
+++ b/src/corelib/kernel/qiterable.cpp
@@ -2,9 +2,12 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QtCore/qiterable.h>
+#include <QtCore/qloggingcategory.h>
QT_BEGIN_NAMESPACE
+Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg);
+
/*!
\class QBaseIterator
\inmodule QtCore
@@ -119,7 +122,7 @@ QT_BEGIN_NAMESPACE
A QIterator can only be created by a QIterable instance, and can be used
in a way similar to other stl-style iterators. Generally, QIterator should
not be used directly, but through its derived classes provided by
- QSequentialIterable and QAssociativeIterable.
+ QMetaSequence::Iterable and QMetaAssociation::Iterable.
\sa QIterable
*/
@@ -155,7 +158,7 @@ QT_BEGIN_NAMESPACE
next item in the container and returns an iterator to the new current
item.
- Calling this function on QSequentialIterable::constEnd() leads to undefined results.
+ Calling this function on QMetaSequence::Iterable::constEnd() leads to undefined results.
\sa operator--()
*/
@@ -176,7 +179,7 @@ QT_BEGIN_NAMESPACE
The prefix \c{--} operator (\c{--it}) makes the preceding item
current and returns an iterator to the new current item.
- Calling this function on QSequentialIterable::constBegin() leads to undefined results.
+ Calling this function on QMetaSequence::Iterable::constBegin() leads to undefined results.
If the container in the QVariant does not support bi-directional iteration, calling this function
leads to undefined results.
@@ -389,7 +392,7 @@ QT_BEGIN_NAMESPACE
\class QIterable
\inmodule QtCore
\since 6.0
- \brief QIterable is a template class that is the base class for QSequentialIterable and QAssociativeIterable.
+ \brief QIterable is a template class that is the base class for QMetaSequence::Iterable and QMetaAssociation::Iterable.
*/
/*!
@@ -454,7 +457,7 @@ QT_BEGIN_NAMESPACE
/*!
\fn template<class Container> QIterator<Container> QIterable<Container>::mutableEnd()
- Returns a QSequentialIterable::iterator for the end of the container. This
+ Returns a QMetaSequence::Iterable::iterator for the end of the container. This
can be used in stl-style iteration.
\sa mutableBegin(), constEnd()
@@ -464,6 +467,17 @@ QT_BEGIN_NAMESPACE
\fn template<class Container> qsizetype QIterable<Container>::size() const
Returns the number of values in the container.
+
+ \note If the underlying container does not provide a native way to query
+ the size, this method will synthesize the access using iterators.
+ This behavior is deprecated and will be removed in a future version
+ of Qt.
+*/
+
+/*!
+ \fn template<class Container> void QIterable<Container>::clear()
+
+ Clears the container.
*/
/*!
@@ -473,7 +487,7 @@ QT_BEGIN_NAMESPACE
\brief QTaggedIterator is a template class that wraps an iterator and exposes standard iterator traits.
In order to use an iterator any of the standard algorithms, its iterator
- traits need to be known. As QSequentialIterable can work with many different
+ traits need to be known. As QMetaSequence::Iterable can work with many different
kinds of containers, we cannot declare the traits in the iterator classes
themselves. A QTaggedIterator gives you a way to explicitly declare a trait for
a concrete instance of an iterator or QConstIterator.
@@ -512,7 +526,7 @@ QT_BEGIN_NAMESPACE
next item in the container and returns an iterator to the new current
item.
- Calling this function on QSequentialIterable::constEnd() leads to undefined results.
+ Calling this function on QMetaSequence::Iterable::constEnd() leads to undefined results.
\sa operator--()
*/
@@ -533,7 +547,7 @@ QT_BEGIN_NAMESPACE
The prefix \c{--} operator (\c{--it}) makes the preceding item
current and returns an iterator to the new current item.
- Calling this function on QSequentialIterable::constBegin() leads to undefined results.
+ Calling this function on QMetaSequence::Iterable::constBegin() leads to undefined results.
If the container in the QVariant does not support bi-directional iteration, calling this function
leads to undefined results.
@@ -609,4 +623,12 @@ QT_BEGIN_NAMESPACE
\sa operator+(), operator-=(), QIterable::canReverseIterate()
*/
+/*!
+ \internal
+ */
+void QtPrivate::warnSynthesizedAccess(const char *text)
+{
+ qCWarning(lcSynthesizedIterableAccess, "%s", text);
+}
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qiterable.h b/src/corelib/kernel/qiterable.h
index 5e25dd1c0a5..baab2897967 100644
--- a/src/corelib/kernel/qiterable.h
+++ b/src/corelib/kernel/qiterable.h
@@ -64,6 +64,8 @@ namespace QtPrivate {
return m_pointer.tag() == Mutable ? reinterpret_cast<Type *>(m_pointer.data()) : nullptr;
}
};
+
+ Q_CORE_EXPORT void warnSynthesizedAccess(const char *text);
}
template<class Iterator, typename IteratorCategory>
@@ -499,6 +501,11 @@ public:
const void *container = constIterable();
if (m_metaContainer.hasSize())
return m_metaContainer.size(container);
+
+ // ### Qt7: Return -1 here. We shouldn't second-guess the underlying container
+ QtPrivate::warnSynthesizedAccess(
+ "size() called on an iterable without native size accessor. This is slow");
+
if (!m_metaContainer.hasConstIterator())
return -1;
@@ -510,6 +517,11 @@ public:
return size;
}
+ void clear()
+ {
+ m_metaContainer.clear(mutableIterable());
+ }
+
Container metaContainer() const
{
return m_metaContainer;
diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp
index 59117bd01d4..abef9fdd663 100644
--- a/src/corelib/kernel/qjniobject.cpp
+++ b/src/corelib/kernel/qjniobject.cpp
@@ -1527,4 +1527,179 @@ jobject QJniObject::javaObject() const
return d->m_jobject;
}
+/*!
+ \class QtJniTypes::JObjectBase
+ \brief The JObjectBase in the QtJniTypes namespace is the base of all declared Java types.
+ \inmodule QtCore
+ \internal
+*/
+
+/*!
+ \class QtJniTypes::JObject
+ \inmodule QtCore
+ \brief The JObject template in the QtJniTypes namespace is the base of declared Java types.
+ \since Qt 6.8
+
+ This template gets specialized when using the Q_DECLARE_JNI_CLASS macro. The
+ specialization produces a unique type in the QtJniTypes namespace. This
+ allows the type system to deduce the correct signature in JNI calls when an
+ instance of the specialized type is passed as a parameter.
+
+ Instances can be implicitly converted to and from QJniObject and jobject,
+ and provide the same template API as QJniObject to call methods and access
+ properties. Since instances of JObject know about the Java type they hold,
+ APIs to access static methods or fields do not require the class name as an
+ explicit parameter.
+
+ \sa Q_DECLARE_JNI_CLASS
+*/
+
+/*!
+ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject()
+
+ Default-constructs the JObject instance. This also default-constructs an
+ instance of the represented Java type.
+*/
+
+/*!
+ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(const QJniObject &other)
+
+ Constructs a JObject instance that holds a reference to the same jobject as \a other.
+*/
+
+/*!
+ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(jobject other)
+
+ Constructs a JObject instance that holds a reference to \a other.
+*/
+
+/*!
+ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(QJniObject &&other)
+
+ Move-constructs a JObject instance from \a other.
+*/
+
+/*!
+ \fn template <typename Type> bool QtJniTypes::JObject<Type>::isValid() const
+
+ Returns whether the JObject instance holds a valid reference to a jobject.
+
+ \sa QJniObject::isValid()
+*/
+
+/*!
+ \fn template <typename Type> jclass QtJniTypes::JObject<Type>::objectClass() const
+
+ Returns the Java class that this JObject is an instance of as a jclass.
+
+ \sa className(), QJniObject::objectClass()
+*/
+
+/*!
+ \fn template <typename Type> QString QtJniTypes::JObject<Type>::toString() const
+
+ Returns a QString with a string representation of the Java object.
+
+ \sa QJniObject::toString()
+*/
+
+/*!
+ \fn template <typename Type> QByteArray QtJniTypes::JObject<Type>::className() const
+
+ Returns the name of the Java class that this object is an instance of.
+
+ \sa objectClass(), QJniObject::className()
+*/
+
+/*!
+ \fn template <typename Type> bool QtJniTypes::JObject<Type>::isClassAvailable()
+
+ Returns whether the class that this JObject specialization represents is
+ available.
+
+ \sa QJniObject::isClassAvailable()
+*/
+
+/*!
+ \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromJObject(jobject object)
+
+ Constructs a JObject instance from \a object and returns that instance.
+*/
+
+/*!
+ \fn template <typename Type> template <typename ...Args> JObject QtJniTypes::JObject<Type>::construct(Args &&...args)
+
+ Constructs a Java object from \a args and returns a JObject instance that
+ holds a reference to that Java object.
+*/
+
+/*!
+ \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromLocalRef(jobject ref)
+
+ Constructs a JObject that holds a local reference to \a ref, and returns
+ that object.
+*/
+
+/*!
+ \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callStaticMethod(const char *methodName, Args &&...args)
+
+ Calls the static method \a methodName with arguments \a args, and returns
+ the result of type \c Ret (unless \c Ret is \c void). If \c Ret is a
+ jobject type, then the returned value will be a QJniObject.
+
+ \sa QJniObject::callStaticMethod()
+*/
+
+/*!
+ \fn template <typename Type> bool QtJniTypes::JObject<Type>::registerNativeMethods(std::initializer_list<JNINativeMethod> methods)
+
+ Registers the Java methods in \a methods with the Java class represented by
+ the JObject specialization, and returns whether the registration was successful.
+
+ \sa QJniEnvironment::registerNativeMethods()
+*/
+
+/*!
+ \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getStaticField(const char *field)
+
+ Returns the value of the static field \a field.
+
+ \sa QJniObject::getStaticField()
+*/
+
+/*!
+ \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setStaticField(const char *field, T &&value)
+
+ Sets the static field \a field to \a value.
+
+ \sa QJniObject::setStaticField()
+*/
+
+/*!
+ \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callMethod(const char *method, Args &&...args) const
+
+ Calls the instance method \a method with arguments \a args, and returns
+ the result of type \c Ret (unless \c Ret is \c void). If \c Ret is a
+ jobject type, then the returned value will be a QJniObject.
+
+ \sa QJniObject::callMethod()
+*/
+
+/*!
+ \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getField(const char *field) const
+
+ Returns the value of the instance field \a field.
+
+ \sa QJniObject::getField()
+*/
+
+/*!
+ \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setField(const char *field, T &&value)
+
+ Sets the value of the instance field \a field to \a value.
+
+ \sa QJniObject::setField()
+*/
+
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h
index 06dfc328b4b..c38bf60e051 100644
--- a/src/corelib/kernel/qjniobject.h
+++ b/src/corelib/kernel/qjniobject.h
@@ -812,7 +812,7 @@ inline bool operator!=(const QJniObject &obj1, const QJniObject &obj2)
}
namespace QtJniTypes {
-struct QT_TECH_PREVIEW_API JObjectBase
+struct JObjectBase
{
operator QJniObject() const { return m_object; }
@@ -841,7 +841,7 @@ protected:
};
template<typename Type>
-class QT_TECH_PREVIEW_API JObject : public JObjectBase
+class JObject : public JObjectBase
{
public:
using Class = Type;
@@ -885,6 +885,13 @@ public:
return JObject(QJniObject::fromLocalRef(lref));
}
+#ifdef Q_QDOC // from JObjectBase, which we don't document
+ bool isValid() const;
+ jclass objectClass() const;
+ QString toString() const;
+ template <typename T = jobject> object() const;
+#endif
+
static bool registerNativeMethods(std::initializer_list<JNINativeMethod> methods)
{
QJniEnvironment env;
diff --git a/src/corelib/kernel/qjnitypes.h b/src/corelib/kernel/qjnitypes.h
index 935388311a5..8ee367d188f 100644
--- a/src/corelib/kernel/qjnitypes.h
+++ b/src/corelib/kernel/qjnitypes.h
@@ -19,12 +19,11 @@
QT_BEGIN_NAMESPACE
-// QT_TECH_PREVIEW_API
#define Q_DECLARE_JNI_TYPE_HELPER(Type) \
struct Type##Tag { explicit Type##Tag() = default; }; \
using Type = JObject<Type##Tag>; \
-// QT_TECH_PREVIEW_API
+// internal - Q_DECLARE_JNI_CLASS is the public macro
#define Q_DECLARE_JNI_TYPE(Type, Signature) \
namespace QtJniTypes { \
Q_DECLARE_JNI_TYPE_HELPER(Type) \
diff --git a/src/corelib/kernel/qmetaassociation.cpp b/src/corelib/kernel/qmetaassociation.cpp
index dc239424e6d..5eae6658a57 100644
--- a/src/corelib/kernel/qmetaassociation.cpp
+++ b/src/corelib/kernel/qmetaassociation.cpp
@@ -1,7 +1,7 @@
// 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
-#include <QtCore/qmetacontainer.h>
+#include <QtCore/qmetaassociation.h>
#include <QtCore/qmetatype.h>
QT_BEGIN_NAMESPACE
@@ -287,7 +287,6 @@ QMetaType QMetaAssociation::mappedMetaType() const
Returns \c true if the QMetaAssociation \a lhs represents the same container type
as the QMetaAssociation \a rhs, otherwise returns \c false.
*/
-
/*!
\fn bool QMetaAssociation::operator!=(const QMetaAssociation &lhs, const QMetaAssociation &rhs)
@@ -295,5 +294,197 @@ QMetaType QMetaAssociation::mappedMetaType() const
type than the QMetaAssociation \a rhs, otherwise returns \c false.
*/
+/*!
+ \class QMetaAssociation::Iterable
+ \inherits QIterable
+ \since 6.11
+ \inmodule QtCore
+ \brief QMetaAssociation::Iterable is an iterable interface for an associative container in a QVariant.
+
+ This class allows several methods of accessing the elements of an
+ associative container held within a QVariant. An instance of
+ QMetaAssociation::Iterable can be extracted from a QVariant if it can be
+ converted to a QVariantHash or QVariantMap or if a custom mutable view has
+ been registered.
+
+ \snippet code/src_corelib_kernel_qvariant.cpp 10
+
+ The container itself is not copied before iterating over it.
+
+ \sa QVariant
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::RandomAccessIterator
+ Exposes an iterator using std::random_access_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::BidirectionalIterator
+ Exposes an iterator using std::bidirectional_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::ForwardIterator
+ Exposes an iterator using std::forward_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::InputIterator
+ Exposes an iterator using std::input_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::RandomAccessConstIterator
+ Exposes a const_iterator using std::random_access_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::BidirectionalConstIterator
+ Exposes a const_iterator using std::bidirectional_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::ForwardConstIterator
+ Exposes a const_iterator using std::forward_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaAssociation::Iterable::InputConstIterator
+ Exposes a const_iterator using std::input_iterator_tag.
+*/
+
+/*!
+ \class QMetaAssociation::Iterable::ConstIterator
+ \inherits QConstIterator
+ \since 6.11
+ \inmodule QtCore
+ \brief QMetaAssociation::Iterable::ConstIterator allows iteration over a container in a QVariant.
+
+ A QMetaAssociation::Iterable::ConstIterator can only be created by a
+ QMetaAssociation::Iterable instance, and can be used in a way similar to
+ other stl-style iterators.
+
+ \snippet code/src_corelib_kernel_qvariant.cpp 10
+
+ \sa QMetaAssociation::Iterable
+*/
+
+/*!
+ \class QMetaAssociation::Iterable::Iterator
+ \inherits QIterator
+ \since 6.11
+ \inmodule QtCore
+ \brief The QMetaAssociation::Iterable::Iterator allows iteration over a container in a QVariant.
+
+ A QMetaAssociation::Iterable::Iterator can only be created by a
+ QMetaAssociation::Iterable instance, and can be used in a way similar to
+ other stl-style iterators.
+
+ \sa QMetaAssociation::Iterable
+*/
+
+/*!
+ \fn QMetaAssociation::Iterable::ConstIterator QMetaAssociation::Iterable::find(const QVariant &key) const
+ Retrieves a ConstIterator pointing to the element at the given \a key, or
+ the end of the container if that key does not exist. If the \a key isn't
+ convertible to the expected type, the end of the container is returned.
+ */
+
+/*!
+ \fn QMetaAssociation::Iterable::Iterator QMetaAssociation::Iterable::mutableFind(const QVariant &key)
+ Retrieves an iterator pointing to the element at the given \a key, or
+ the end of the container if that key does not exist. If the \a key isn't
+ convertible to the expected type, the end of the container is returned.
+ */
+
+/*!
+ \fn bool QMetaAssociation::Iterable::containsKey(const QVariant &key) const
+ Returns \c true if the container has an entry with the given \a key, or
+ \c false otherwise. If the \a key isn't convertible to the expected type,
+ \c false is returned.
+ */
+
+/*!
+ \fn void QMetaAssociation::Iterable::insertKey(const QVariant &key)
+ Inserts a new entry with the given \a key, or resets the mapped value of
+ any existing entry with the given \a key to the default constructed
+ mapped value. The \a key is coerced to the expected type: If it isn't
+ convertible, a default value is inserted.
+ */
+
+/*!
+ \fn void QMetaAssociation::Iterable::removeKey(const QVariant &key)
+ Removes the entry with the given \a key from the container. The \a key is
+ coerced to the expected type: If it isn't convertible, the default value
+ is removed.
+ */
+
+/*!
+ \fn QVariant QMetaAssociation::Iterable::value(const QVariant &key) const
+ Retrieves the mapped value at the given \a key, or a QVariant of a
+ default-constructed instance of the mapped type, if the key does not
+ exist. If the \a key is not convertible to the key type, the mapped value
+ associated with the default-constructed key is returned.
+ */
+
+/*!
+ \fn void QMetaAssociation::Iterable::setValue(const QVariant &key, const QVariant &mapped)
+ Sets the mapped value associated with \a key to \a mapped, if possible.
+ Inserts a new entry if none exists yet, for the given \a key. If the
+ \a key is not convertible to the key type, the value for the
+ default-constructed key type is overwritten.
+ */
+
+
+/*!
+ \fn QVariant QMetaAssociation::Iterable::Iterator::key() const
+ Returns the key this iterator points to.
+*/
+
+/*!
+ \fn QVariant::Reference<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::value() const
+ Returns the mapped value this iterator points to. If the container does not
+ provide a mapped value (for example a set), returns an invalid
+ QVariant::Reference.
+*/
+
+/*!
+ \fn QVariant::Reference<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::operator*() const
+ Returns the current item, converted to a QVariant::Reference. The resulting
+ QVariant::Reference resolves to the mapped value if there is one, or to the
+ key value if not.
+*/
+
+/*!
+ \fn QVariant::Pointer<QMetaAssociation::Iterable::Iterator> QMetaAssociation::Iterable::Iterator::operator->() const
+ Returns the current item, converted to a QVariant::Pointer. The resulting
+ QVariant::Pointer resolves to the mapped value if there is one, or to the
+ key value if not.
+*/
+
+/*!
+ \fn QVariant QMetaAssociation::Iterable::ConstIterator::key() const
+ Returns the key this iterator points to.
+*/
+
+/*!
+ \fn QVariant QMetaAssociation::Iterable::ConstIterator::value() const
+ Returns the mapped value this iterator points to, or an invalid QVariant if
+ there is no mapped value.
+*/
+
+/*!
+ \fn QVariant QMetaAssociation::Iterable::ConstIterator::operator*() const
+ Returns the current item, converted to a QVariant. The returned value is the
+ mapped value at the current iterator if there is one, or otherwise the key.
+*/
+
+/*!
+ \fn QVariant::ConstPointer<QMetaAssociation::Iterable::ConstIterator> QMetaAssociation::Iterable::ConstIterator::operator->() const
+ Returns the current item, converted to a QVariant::ConstPointer. The
+ QVariant::ConstPointer will resolve to the mapped value at the current
+ iterator if there is one, or otherwise the key.
+*/
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qmetaassociation.h b/src/corelib/kernel/qmetaassociation.h
new file mode 100644
index 00000000000..6d8de13e90a
--- /dev/null
+++ b/src/corelib/kernel/qmetaassociation.h
@@ -0,0 +1,266 @@
+// 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 QMETAASSOCIATION_H
+#define QMETAASSOCIATION_H
+
+#if 0
+#pragma qt_class(QMetaAssociation)
+#endif
+
+#include <QtCore/qiterable.h>
+#include <QtCore/qiterable_impl.h>
+#include <QtCore/qmetacontainer.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtMetaContainerPrivate {
+
+class AssociativeIterator : public QIterator<QMetaAssociation>
+{
+public:
+ using key_type = QVariant;
+ using mapped_type = QVariant;
+ using reference = QVariant::Reference<AssociativeIterator>;
+ using pointer = QVariant::Pointer<AssociativeIterator>;
+
+ static constexpr bool canNoexceptAssignQVariant = false;
+ static constexpr bool canNoexceptConvertToQVariant = false;
+
+ AssociativeIterator(QIterator &&it) : QIterator(std::move(it)) {}
+
+ key_type key() const
+ {
+ const QMetaAssociation meta = metaContainer();
+ return QIterablePrivate::retrieveElement(meta.keyMetaType(), [&](void *dataPtr) {
+ meta.keyAtIterator(constIterator(), dataPtr);
+ });
+ }
+ reference value() const { return operator*(); }
+
+ reference operator*() const { return reference(*this); }
+ pointer operator->() const { return pointer(*this); }
+};
+
+class AssociativeConstIterator : public QConstIterator<QMetaAssociation>
+{
+public:
+ using key_type = QVariant;
+ using mapped_type = QVariant;
+ using reference = QVariant::ConstReference<AssociativeConstIterator>;
+ using pointer = QVariant::ConstPointer<AssociativeConstIterator>;
+
+ static constexpr bool canNoexceptConvertToQVariant = false;
+
+ AssociativeConstIterator(QConstIterator &&it) : QConstIterator(std::move(it)) {}
+
+ key_type key() const
+ {
+ const QMetaAssociation meta = metaContainer();
+ return QIterablePrivate::retrieveElement(meta.keyMetaType(), [&](void *dataPtr) {
+ meta.keyAtConstIterator(constIterator(), dataPtr);
+ });
+ }
+
+ mapped_type value() const { return operator*(); }
+
+ mapped_type operator*() const;
+ pointer operator->() const { return pointer(*this); }
+};
+
+} // namespace QtMetaContainerPrivate
+
+namespace QtPrivate {
+
+template<typename Referred>
+QVariant associativeIteratorToVariant(const Referred &referred)
+{
+ const auto metaAssociation = referred.metaContainer();
+ const QMetaType metaType(metaAssociation.mappedMetaType());
+ if (metaType.isValid(QT6_CALL_NEW_OVERLOAD)) {
+ return QIterablePrivate::retrieveElement(metaType, [&](void *dataPtr) {
+ metaAssociation.mappedAtConstIterator(referred.constIterator(), dataPtr);
+ });
+ }
+
+ return QIterablePrivate::retrieveElement(metaType, [&](void *dataPtr) {
+ metaAssociation.keyAtConstIterator(referred.constIterator(), dataPtr);
+ });
+}
+
+} // namespace QtPrivate
+
+template<>
+inline QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator>::operator QVariant() const
+{
+ return QtPrivate::associativeIteratorToVariant(m_referred);
+}
+
+template<>
+inline QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator> &
+QVariant::Reference<QtMetaContainerPrivate::AssociativeIterator>::operator=(const QVariant &value)
+{
+ const auto metaAssociation = m_referred.metaContainer();
+ const QMetaType metaType(metaAssociation.mappedMetaType());
+ if (!metaType.isValid(QT6_CALL_NEW_OVERLOAD))
+ return *this;
+
+ QtPrivate::QVariantTypeCoercer coercer;
+ metaAssociation.setMappedAtIterator(
+ m_referred.constIterator(), coercer.coerce(value, metaType));
+ return *this;
+}
+
+template<>
+inline QVariant::ConstReference<QtMetaContainerPrivate::AssociativeConstIterator>::operator QVariant() const
+{
+ return QtPrivate::associativeIteratorToVariant(m_referred);
+}
+
+namespace QtMetaContainerPrivate {
+inline AssociativeConstIterator::mapped_type AssociativeConstIterator::operator*() const
+{
+ return reference(*this);
+}
+
+class Association : public QIterable<QMetaAssociation>
+{
+public:
+ using Iterator
+ = QTaggedIterator<AssociativeIterator, void>;
+ using RandomAccessIterator
+ = QTaggedIterator<AssociativeIterator, std::random_access_iterator_tag>;
+ using BidirectionalIterator
+ = QTaggedIterator<AssociativeIterator, std::bidirectional_iterator_tag>;
+ using ForwardIterator
+ = QTaggedIterator<AssociativeIterator, std::forward_iterator_tag>;
+ using InputIterator
+ = QTaggedIterator<AssociativeIterator, std::input_iterator_tag>;
+
+ using ConstIterator
+ = QTaggedIterator<AssociativeConstIterator, void>;
+ using RandomAccessConstIterator
+ = QTaggedIterator<AssociativeConstIterator, std::random_access_iterator_tag>;
+ using BidirectionalConstIterator
+ = QTaggedIterator<AssociativeConstIterator, std::bidirectional_iterator_tag>;
+ using ForwardConstIterator
+ = QTaggedIterator<AssociativeConstIterator, std::forward_iterator_tag>;
+ using InputConstIterator
+ = QTaggedIterator<AssociativeConstIterator, std::input_iterator_tag>;
+
+ using iterator = Iterator;
+ using const_iterator = ConstIterator;
+
+ template<class T>
+ Association(const T *p) : QIterable(QMetaAssociation::fromContainer<T>(), p) {}
+
+ template<class T>
+ Association(T *p) : QIterable(QMetaAssociation::fromContainer<T>(), p) {}
+
+ Association() : QIterable(QMetaAssociation(), nullptr) {}
+
+ template<typename Pointer>
+ Association(const QMetaAssociation &metaAssociation, Pointer iterable)
+ : QIterable(metaAssociation, iterable)
+ {
+ }
+
+ Association(const QMetaAssociation &metaAssociation, QMetaType metaType, void *iterable)
+ : QIterable(metaAssociation, metaType.alignOf(), iterable)
+ {
+ }
+
+ Association(const QMetaAssociation &metaAssociation, QMetaType metaType, const void *iterable)
+ : QIterable(metaAssociation, metaType.alignOf(), iterable)
+ {
+ }
+
+ Association(QIterable<QMetaAssociation> &&other)
+ : QIterable(std::move(other))
+ {}
+
+ Association &operator=(QIterable<QMetaAssociation> &&other)
+ {
+ QIterable::operator=(std::move(other));
+ return *this;
+ }
+
+ ConstIterator begin() const { return constBegin(); }
+ ConstIterator end() const { return constEnd(); }
+
+ ConstIterator constBegin() const { return ConstIterator(QIterable::constBegin()); }
+ ConstIterator constEnd() const { return ConstIterator(QIterable::constEnd()); }
+
+ Iterator mutableBegin() { return Iterator(QIterable::mutableBegin()); }
+ Iterator mutableEnd() { return Iterator(QIterable::mutableEnd()); }
+
+ ConstIterator find(const QVariant &key) const
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ if (const void *keyData = coercer.convert(key, meta.keyMetaType())) {
+ return ConstIterator(QConstIterator<QMetaAssociation>(
+ this, meta.createConstIteratorAtKey(constIterable(), keyData)));
+ }
+ return constEnd();
+ }
+
+ ConstIterator constFind(const QVariant &key) const { return find(key); }
+
+ Iterator mutableFind(const QVariant &key)
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ if (const void *keyData = coercer.convert(key, meta.keyMetaType()))
+ return Iterator(QIterator(this, meta.createIteratorAtKey(mutableIterable(), keyData)));
+ return mutableEnd();
+ }
+
+ bool containsKey(const QVariant &key) const
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer keyCoercer;
+ if (const void *keyData = keyCoercer.convert(key, meta.keyMetaType()))
+ return meta.containsKey(constIterable(), keyData);
+ return false;
+ }
+
+ void insertKey(const QVariant &key)
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer keyCoercer;
+ meta.insertKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType()));
+ }
+
+ void removeKey(const QVariant &key)
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer keyCoercer;
+ meta.removeKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType()));
+ }
+
+ QVariant value(const QVariant &key) const
+ {
+ const QMetaAssociation meta = metaContainer();
+ return QIterablePrivate::retrieveElement(meta.mappedMetaType(), [&](void *dataPtr) {
+ QtPrivate::QVariantTypeCoercer coercer;
+ meta.mappedAtKey(constIterable(), coercer.coerce(key, meta.keyMetaType()), dataPtr);
+ });
+ }
+
+ void setValue(const QVariant &key, const QVariant &mapped)
+ {
+ const QMetaAssociation meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer keyCoercer;
+ QtPrivate::QVariantTypeCoercer mappedCoercer;
+ meta.setMappedAtKey(mutableIterable(), keyCoercer.coerce(key, meta.keyMetaType()),
+ mappedCoercer.coerce(mapped, meta.mappedMetaType()));
+ }
+};
+
+} // namespace QtMetaContainerPrivate
+
+QT_END_NAMESPACE
+
+#endif // QMETAASSOCIATION_H
diff --git a/src/corelib/kernel/qmetacontainer.h b/src/corelib/kernel/qmetacontainer.h
index 1bed7f9f7b3..c9d3a6bf9c6 100644
--- a/src/corelib/kernel/qmetacontainer.h
+++ b/src/corelib/kernel/qmetacontainer.h
@@ -22,6 +22,11 @@ constexpr const QMetaTypeInterface *qMetaTypeInterfaceForType();
namespace QtMetaContainerPrivate {
+class Sequence;
+class SequentialIterator;
+class Association;
+class AssociativeIterator;
+
enum IteratorCapability : quint8 {
InputCapability = 1 << 0,
ForwardCapability = 1 << 1,
@@ -922,9 +927,67 @@ protected:
const QtMetaContainerPrivate::QMetaContainerInterface *d_ptr = nullptr;
};
+// ### Qt7: Move this to qmetasequence.h, including QtMetaContainerPrivate parts above.
class Q_CORE_EXPORT QMetaSequence : public QMetaContainer
{
public:
+#ifdef Q_QDOC
+ class Iterable : public QIterable<QMetaSequence>
+ {
+ public:
+ class Iterator : public QIterator<QMetaSequence>
+ {
+ public:
+ QVariant::Reference<Iterator> operator*() const;
+ QVariant::Pointer<Iterator> operator->() const;
+ QVariant::Reference<Iterator> operator[](qsizetype n) const;
+ };
+
+ class ConstIterator : public QConstIterator<QMetaSequence>
+ {
+ public:
+ QVariant operator*() const;
+ QVariant::ConstPointer<ConstIterator> operator->() const;
+ QVariant operator[](qsizetype n) const;
+ };
+
+ using RandomAccessIterator = Iterator;
+ using BidirectionalIterator = Iterator;
+ using ForwardIterator = Iterator;
+ using InputIterator = Iterator;
+
+ using RandomAccessConstIterator = ConstIterator;
+ using BidirectionalConstIterator = ConstIterator;
+ using ForwardConstIterator = ConstIterator;
+ using InputConstIterator = ConstIterator;
+
+ ConstIterator begin() const;
+ ConstIterator end() const;
+
+ ConstIterator constBegin() const;
+ ConstIterator constEnd() const;
+
+ Iterator mutableBegin();
+ Iterator mutableEnd();
+
+ QVariant at(qsizetype idx) const;
+ void set(qsizetype idx, const QVariant &value);
+ void append(const QVariant &value);
+ void prepend(const QVariant &value);
+ void removeLast();
+ void removeFirst();
+
+#if QT_DEPRECATED_SINCE(6, 11)
+ enum Position: quint8 { Unspecified, AtBegin, AtEnd };
+ void addValue(const QVariant &value, Position position = Unspecified);
+ void removeValue(Position position = Unspecified);
+ QMetaType valueMetaType() const;
+#endif // QT_DEPRECATED_SINCE(6, 11)
+ };
+#else
+ using Iterable = QtMetaContainerPrivate::Sequence;
+#endif
+
QMetaSequence() = default;
explicit QMetaSequence(const QtMetaContainerPrivate::QMetaSequenceInterface *d) : QMetaContainer(d) {}
@@ -999,9 +1062,67 @@ private:
}
};
+// ### Qt7: Move this to qmetaassociation.h, including QtMetaContainerPrivate parts above.
class Q_CORE_EXPORT QMetaAssociation : public QMetaContainer
{
public:
+#ifdef Q_QDOC
+ class Iterable : public QIterable<QMetaAssociation>
+ {
+ public:
+ class Iterator : public QIterator<QMetaAssociation>
+ {
+ public:
+ QVariant key() const;
+ QVariant value() const;
+
+ QVariant::Reference<Iterator> operator*() const;
+ QVariant::Pointer<Iterator> operator->() const;
+ };
+
+ class ConstIterator : public QConstIterator<QMetaAssociation>
+ {
+ public:
+ QVariant key() const;
+ QVariant value() const;
+
+ QVariant operator*() const;
+ QVariant::ConstPointer<ConstIterator> operator->() const;
+ };
+
+ using RandomAccessIterator = Iterator;
+ using BidirectionalIterator = Iterator;
+ using ForwardIterator = Iterator;
+ using InputIterator = Iterator;
+
+ using RandomAccessConstIterator = ConstIterator;
+ using BidirectionalConstIterator = ConstIterator;
+ using ForwardConstIterator = ConstIterator;
+ using InputConstIterator = ConstIterator;
+
+ ConstIterator begin() const;
+ ConstIterator end() const;
+
+ ConstIterator constBegin() const;
+ ConstIterator constEnd() const;
+
+ Iterator mutableBegin();
+ Iterator mutableEnd();
+
+ ConstIterator find(const QVariant &key) const;
+ ConstIterator constFind(const QVariant &key) const;
+ Iterator mutableFind(const QVariant &key);
+
+ bool containsKey(const QVariant &key) const;
+ void insertKey(const QVariant &key);
+ void removeKey(const QVariant &key);
+ QVariant value(const QVariant &key) const;
+ void setValue(const QVariant &key, const QVariant &mapped);
+ };
+#else
+ using Iterable = QtMetaContainerPrivate::Association;
+#endif
+
QMetaAssociation() = default;
explicit QMetaAssociation(const QtMetaContainerPrivate::QMetaAssociationInterface *d) : QMetaContainer(d) {}
diff --git a/src/corelib/kernel/qmetaobject.cpp b/src/corelib/kernel/qmetaobject.cpp
index a5d34eac707..c7e50788b45 100644
--- a/src/corelib/kernel/qmetaobject.cpp
+++ b/src/corelib/kernel/qmetaobject.cpp
@@ -469,6 +469,33 @@ QMetaType QMetaObject::metaType() const
}
}
+static inline QByteArrayView objectMetaObjectHash(const QMetaObject *m)
+{
+ // metaObjectHash didn't exist before revision 14
+ if (QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && priv(m->d.data)->revision < 14)
+ return {};
+ const auto index = priv(m->d.data)->metaObjectHashIndex;
+ if (index == -1)
+ return {};
+ return stringDataView(m, index);
+}
+
+/*!
+ \since 6.11
+
+ Returns the revisioned hash of the contents of this QMetaObject or nullptr.
+
+ The hash has the following format <hash_revision>$<hash_b64>, where
+ hash_revision is an integer and hash_b64 is the base64 encoding of the
+ hash.
+
+ Note that only hashes of the same revision should be compared.
+*/
+const char *QMetaObject::metaObjectHash() const
+{
+ return objectMetaObjectHash(this).constData();
+}
+
/*!
Returns the method offset for this class; i.e. the index position
of this class's first member function.
@@ -4405,6 +4432,34 @@ bool QMetaProperty::isFinal() const
}
/*!
+ \since 6.11
+ Returns \c true if the property is virtual; otherwise returns \c false.
+
+ A property is virtual if the \c{Q_PROPERTY()}'s \c VIRTUAL attribute
+ is set.
+*/
+bool QMetaProperty::isVirtual() const
+{
+ if (!mobj)
+ return false;
+ return data.flags() & Virtual;
+}
+
+/*!
+ \since 6.11
+ Returns \c true if the property does override; otherwise returns \c false.
+
+ A property does override if the \c{Q_PROPERTY()}'s \c OVERRIDE attribute
+ is set.
+*/
+bool QMetaProperty::isOverride() const
+{
+ if (!mobj)
+ return false;
+ return data.flags() & Override;
+}
+
+/*!
\since 5.15
Returns \c true if the property is required; otherwise returns \c false.
diff --git a/src/corelib/kernel/qmetaobject.h b/src/corelib/kernel/qmetaobject.h
index 0f793ca753b..ff3cc751c3a 100644
--- a/src/corelib/kernel/qmetaobject.h
+++ b/src/corelib/kernel/qmetaobject.h
@@ -365,6 +365,8 @@ public:
bool isUser() const;
bool isConstant() const;
bool isFinal() const;
+ bool isVirtual() const;
+ bool isOverride() const;
bool isRequired() const;
bool isBindable() const;
diff --git a/src/corelib/kernel/qmetaobject_p.h b/src/corelib/kernel/qmetaobject_p.h
index bfda30fda28..7264d2a956f 100644
--- a/src/corelib/kernel/qmetaobject_p.h
+++ b/src/corelib/kernel/qmetaobject_p.h
@@ -124,6 +124,7 @@ struct QMetaObjectPrivate
int constructorCount, constructorData;
int flags;
int signalCount;
+ int metaObjectHashIndex;
static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject)
{ return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); }
diff --git a/src/corelib/kernel/qmetaobjectbuilder.cpp b/src/corelib/kernel/qmetaobjectbuilder.cpp
index 6065bf2baea..9af6de73680 100644
--- a/src/corelib/kernel/qmetaobjectbuilder.cpp
+++ b/src/corelib/kernel/qmetaobjectbuilder.cpp
@@ -558,6 +558,8 @@ QMetaPropertyBuilder QMetaObjectBuilder::addProperty(const QMetaProperty &protot
property.setEnumOrFlag(prototype.isEnumType());
property.setConstant(prototype.isConstant());
property.setFinal(prototype.isFinal());
+ property.setVirtual(prototype.isVirtual());
+ property.setOverride(prototype.isOverride());
property.setRevision(prototype.revision());
if (prototype.hasNotifySignal()) {
// Find an existing method for the notify signal, or add a new one.
@@ -1177,10 +1179,11 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
int methodParametersDataSize = aggregateParameterCount(d->methods)
+ aggregateParameterCount(d->constructors);
if constexpr (mode == Construct) {
- static_assert(QMetaObjectPrivate::OutputRevision == 13, "QMetaObjectBuilder should generate the same version as moc");
+ static_assert(QMetaObjectPrivate::OutputRevision == 14, "QMetaObjectBuilder should generate the same version as moc");
pmeta->revision = QMetaObjectPrivate::OutputRevision;
pmeta->flags = d->flags.toInt() | AllocatedMetaObject;
pmeta->className = 0; // Class name is always the first string.
+ pmeta->metaObjectHashIndex = -1; // TODO support hash in the builder too
//pmeta->signalCount is handled in the "output method loop" as an optimization.
pmeta->classInfoCount = d->classInfoNames.size();
@@ -2068,6 +2071,32 @@ bool QMetaPropertyBuilder::isFinal() const
}
/*!
+ Returns \c true if the property is virtual; otherwise returns \c false.
+ The default value is false.
+*/
+bool QMetaPropertyBuilder::isVirtual() const
+{
+ QMetaPropertyBuilderPrivate *d = d_func();
+ if (d)
+ return d->flag(Virtual);
+ else
+ return false;
+}
+
+/*!
+ Returns \c true if the property does override; otherwise returns \c false.
+ The default value is false.
+*/
+bool QMetaPropertyBuilder::isOverride() const
+{
+ QMetaPropertyBuilderPrivate *d = d_func();
+ if (d)
+ return d->flag(Override);
+ else
+ return false;
+}
+
+/*!
* Returns \c true if the property is an alias.
* The default value is false
*/
@@ -2239,6 +2268,30 @@ void QMetaPropertyBuilder::setFinal(bool value)
}
/*!
+ Sets the \c VIRTUAL flag on this property to \a value.
+
+ \sa isFinal()
+*/
+void QMetaPropertyBuilder::setVirtual(bool value)
+{
+ QMetaPropertyBuilderPrivate *d = d_func();
+ if (d)
+ d->setFlag(Virtual, value);
+}
+
+/*!
+ Sets the \c OVERRIDE flag on this property to \a value.
+
+ \sa isOverride()
+*/
+void QMetaPropertyBuilder::setOverride(bool value)
+{
+ QMetaPropertyBuilderPrivate *d = d_func();
+ if (d)
+ d->setFlag(Override, value);
+}
+
+/*!
Sets the \c ALIAS flag on this property to \a value
*/
void QMetaPropertyBuilder::setAlias(bool value)
diff --git a/src/corelib/kernel/qmetaobjectbuilder_p.h b/src/corelib/kernel/qmetaobjectbuilder_p.h
index 563704d60e6..9591944602a 100644
--- a/src/corelib/kernel/qmetaobjectbuilder_p.h
+++ b/src/corelib/kernel/qmetaobjectbuilder_p.h
@@ -214,6 +214,8 @@ public:
bool isEnumOrFlag() const;
bool isConstant() const;
bool isFinal() const;
+ bool isVirtual() const;
+ bool isOverride() const;
bool isAlias() const;
bool isBindable() const;
bool isRequired() const;
@@ -229,6 +231,8 @@ public:
void setEnumOrFlag(bool value);
void setConstant(bool value);
void setFinal(bool value);
+ void setVirtual(bool value);
+ void setOverride(bool value);
void setAlias(bool value);
void setBindable(bool value);
void setRequired(bool value);
diff --git a/src/corelib/kernel/qmetasequence.cpp b/src/corelib/kernel/qmetasequence.cpp
index 1d3f3dfd080..2a3a923d5ca 100644
--- a/src/corelib/kernel/qmetasequence.cpp
+++ b/src/corelib/kernel/qmetasequence.cpp
@@ -1,8 +1,8 @@
// 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
-#include "qmetacontainer.h"
-#include "qmetatype.h"
+#include <QtCore/qmetasequence.h>
+#include <QtCore/qmetatype.h>
QT_BEGIN_NAMESPACE
@@ -468,4 +468,176 @@ void QMetaSequence::valueAtConstIterator(const void *iterator, void *result) con
type than the QMetaSequence \a rhs, otherwise returns \c false.
*/
+/*!
+ \class QMetaSequence::Iterable
+ \inherits QIterable
+ \since 6.11
+ \inmodule QtCore
+ \brief The QMetaSequence::Iterable class is an iterable interface for a container in a QVariant.
+
+ This class allows several methods of accessing the values of a container
+ held within a QVariant. An instance of QMetaSequence::Iterable can be
+ extracted from a QVariant if it can be converted to a QVariantList, or if
+ the container it contains is registered using
+ Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE. Most sequential containers found
+ in Qt and some found in the C++ standard library are automatically
+ registered.
+
+ \snippet code/src_corelib_kernel_qvariant.cpp 9
+
+ The container itself is not copied before iterating over it.
+
+ \sa QVariant
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::RandomAccessIterator
+ Exposes an iterator using std::random_access_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::BidirectionalIterator
+ Exposes an iterator using std::bidirectional_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::ForwardIterator
+ Exposes an iterator using std::forward_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::InputIterator
+ Exposes an iterator using std::input_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::RandomAccessConstIterator
+ Exposes a const_iterator using std::random_access_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::BidirectionalConstIterator
+ Exposes a const_iterator using std::bidirectional_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::ForwardConstIterator
+ Exposes a const_iterator using std::forward_iterator_tag.
+*/
+
+/*!
+ \typealias QMetaSequence::Iterable::InputConstIterator
+ Exposes a const_iterator using std::input_iterator_tag.
+*/
+
+/*!
+ \enum QMetaSequence::Iterable::Position
+ \deprecated [6.11] Use append(), prepend(), removeFirst(), or removeLast()
+
+ Specifies the position at which an element shall be added to or removed from
+ the iterable.
+
+ \value AtBegin
+ Add or remove at the beginning of the iterable.
+ \value AtEnd
+ Add or remove at the end of the iterable.
+ \value Unspecified
+ Add or remove at an unspecified position in the iterable.
+ */
+
+/*!
+ \fn void QMetaSequence::Iterable::addValue(const QVariant &value, Position position)
+ \deprecated [6.11] Use append() or prepend()
+ Adds \a value to the container, at \a position, if possible.
+ */
+
+/*!
+ \deprecated [6.11] Use removeFirst() or removeLast()
+ \fn void QMetaSequence::Iterable::removeValue(Position position)
+ Removes a value from the container, at \a position, if possible.
+ */
+
+/*!
+ \deprecated [6.11] Use QMetaSequence::valueMetaType()
+ \fn QMetaType QMetaSequence::Iterable::valueMetaType() const
+ Returns the meta type for values stored in the underlying container.
+ */
+
+/*!
+ \fn QVariant QMetaSequence::Iterable::at(qsizetype idx) const
+ Returns the value at position \a idx in the container.
+
+ \note If the underlying container does not provide a native way to retrieve
+ an element at an index, this method will synthesize the access using
+ iterators. This behavior is deprecated and will be removed in a future
+ version of Qt.
+*/
+
+/*!
+ \fn void QMetaSequence::Iterable::set(qsizetype idx, const QVariant &value)
+ Sets the element at position \a idx in the container to \a value.
+
+ \note If the underlying container does not provide a native way to assign
+ an element at an index, this method will synthesize the assignment
+ using iterators. This behavior is deprecated and will be removed in a
+ future version of Qt.
+*/
+
+/*!
+ \class QMetaSequence::Iterable::ConstIterator
+ \inmodule QtCore
+ \inherits QConstIterator
+ \since 6.11
+ \brief QMetaSequence::Iterable::ConstIterator allows iteration over a container in a QVariant.
+
+ A QMetaSequence::Iterable::ConstIterator can only be created by a
+ QMetaSequence::Iterable instance, and can be used in a way similar to other
+ stl-style iterators.
+
+ \snippet code/src_corelib_kernel_qvariant.cpp 9
+*/
+
+/*!
+ \class QMetaSequence::Iterable::Iterator
+ \inmodule QtCore
+ \inherits QIterator
+ \since 6.11
+ \brief QMetaSequence::Iterable::Iterator allows iteration over a container in a QVariant.
+
+ A QMetaSequence::Iterable::Iterator can only be created by a QMetaSequence::Iterable
+ instance, and can be used in a way similar to other stl-style iterators.
+*/
+
+/*!
+ \fn QVariant::Reference<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator*() const
+ Returns the current item, converted to a QVariant::Reference.
+*/
+
+/*!
+ \fn QVariant::Pointer<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator->() const
+ Returns the current item, converted to a QVariant::Pointer.
+*/
+
+/*!
+ \fn QVariant::Reference<QMetaSequence::Iterable::Iterator> QMetaSequence::Iterable::Iterator::operator[](qsizetype n) const
+ Returns the item offset from the current one by \a n, converted to a
+ QVariant::Reference.
+*/
+
+/*!
+ \fn QVariant QMetaSequence::Iterable::ConstIterator::operator*() const
+ Returns the current item, converted to a QVariant.
+*/
+
+/*!
+ \fn QVariant::ConstPointer<QMetaSequence::Iterable::ConstIterator> QMetaSequence::Iterable::ConstIterator::operator->() const
+ Returns the current item, converted to a QVariant::ConstPointer.
+*/
+
+/*!
+ \fn QVariant QMetaSequence::Iterable::ConstIterator::operator[](qsizetype n) const
+ Returns the item offset from the current one by \a n, converted to a
+ QVariant.
+*/
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qmetasequence.h b/src/corelib/kernel/qmetasequence.h
new file mode 100644
index 00000000000..e9505054159
--- /dev/null
+++ b/src/corelib/kernel/qmetasequence.h
@@ -0,0 +1,308 @@
+// 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 QMETASEQUENCE_H
+#define QMETASEQUENCE_H
+
+#if 0
+#pragma qt_class(QMetaSequence)
+#endif
+
+#include <QtCore/qiterable.h>
+#include <QtCore/qiterable_impl.h>
+#include <QtCore/qmetacontainer.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtMetaContainerPrivate {
+
+class SequentialIterator : public QIterator<QMetaSequence>
+{
+public:
+ using value_type = QVariant;
+ using reference = QVariant::Reference<SequentialIterator>;
+ using pointer = QVariant::Pointer<SequentialIterator>;
+
+ static constexpr bool canNoexceptAssignQVariant = false;
+ static constexpr bool canNoexceptConvertToQVariant = false;
+
+ SequentialIterator(QIterator &&it) : QIterator(std::move(it)) {}
+
+ reference operator*() const { return reference(*this); }
+ pointer operator->() const { return pointer(*this); }
+ reference operator[](qsizetype n) const { return reference(*this + n); }
+};
+
+class SequentialConstIterator : public QConstIterator<QMetaSequence>
+{
+public:
+ using value_type = QVariant;
+ using reference = QVariant::ConstReference<SequentialConstIterator>;
+ using pointer = QVariant::ConstPointer<SequentialConstIterator>;
+
+ static constexpr bool canNoexceptConvertToQVariant = false;
+
+ SequentialConstIterator(QConstIterator &&it) : QConstIterator(std::move(it)) {}
+
+ value_type operator*() const;
+ pointer operator->() const { return pointer(*this); }
+ value_type operator[](qsizetype n) const;
+};
+
+} // namespace QtMetaContainerPrivate
+
+namespace QtPrivate {
+template<typename Referred>
+QVariant sequentialIteratorToVariant(const Referred &referred)
+{
+ const auto metaSequence = referred.metaContainer();
+ return QIterablePrivate::retrieveElement(metaSequence.valueMetaType(), [&](void *dataPtr) {
+ metaSequence.valueAtConstIterator(referred.constIterator(), dataPtr);
+ });
+}
+} // namespace QtPrivate
+
+template<>
+inline QVariant::Reference<QtMetaContainerPrivate::SequentialIterator>::operator QVariant() const
+{
+ return QtPrivate::sequentialIteratorToVariant(m_referred);
+}
+
+template<>
+inline QVariant::Reference<QtMetaContainerPrivate::SequentialIterator> &
+QVariant::Reference<QtMetaContainerPrivate::SequentialIterator>::operator=(const QVariant &value)
+{
+ QtPrivate::QVariantTypeCoercer coercer;
+ m_referred.metaContainer().setValueAtIterator(
+ m_referred.mutableIterator(),
+ coercer.coerce(value, m_referred.metaContainer().valueMetaType()));
+ return *this;
+}
+
+template<>
+inline QVariant::ConstReference<QtMetaContainerPrivate::SequentialConstIterator>::operator QVariant() const
+{
+ return QtPrivate::sequentialIteratorToVariant(m_referred);
+}
+
+namespace QtMetaContainerPrivate {
+inline SequentialConstIterator::value_type SequentialConstIterator::operator*() const
+{
+ return reference(*this);
+}
+
+inline SequentialConstIterator::value_type SequentialConstIterator::operator[](qsizetype n) const
+{
+ return reference(*this + n);
+}
+
+class Sequence : public QIterable<QMetaSequence>
+{
+public:
+ using Iterator = QTaggedIterator<SequentialIterator, void>;
+ using RandomAccessIterator
+ = QTaggedIterator<SequentialIterator, std::random_access_iterator_tag>;
+ using BidirectionalIterator
+ = QTaggedIterator<SequentialIterator, std::bidirectional_iterator_tag>;
+ using ForwardIterator
+ = QTaggedIterator<SequentialIterator, std::forward_iterator_tag>;
+ using InputIterator
+ = QTaggedIterator<SequentialIterator, std::input_iterator_tag>;
+
+ using ConstIterator
+ = QTaggedIterator<SequentialConstIterator, void>;
+ using RandomAccessConstIterator
+ = QTaggedIterator<SequentialConstIterator, std::random_access_iterator_tag>;
+ using BidirectionalConstIterator
+ = QTaggedIterator<SequentialConstIterator, std::bidirectional_iterator_tag>;
+ using ForwardConstIterator
+ = QTaggedIterator<SequentialConstIterator, std::forward_iterator_tag>;
+ using InputConstIterator
+ = QTaggedIterator<SequentialConstIterator, std::input_iterator_tag>;
+
+ using iterator = Iterator;
+ using const_iterator = ConstIterator;
+
+ template<class T>
+ Sequence(const T *p)
+ : QIterable(QMetaSequence::fromContainer<T>(), p)
+ {
+ Q_UNUSED(m_revision);
+ }
+
+ template<class T>
+ Sequence(T *p)
+ : QIterable(QMetaSequence::fromContainer<T>(), p)
+ {
+ }
+
+ Sequence()
+ : QIterable(QMetaSequence(), nullptr)
+ {
+ }
+
+ template<typename Pointer>
+ Sequence(const QMetaSequence &metaSequence, Pointer iterable)
+ : QIterable(metaSequence, iterable)
+ {
+ }
+
+ Sequence(const QMetaSequence &metaSequence, QMetaType metaType, void *iterable)
+ : QIterable(metaSequence, metaType.alignOf(), iterable)
+ {
+ }
+
+ Sequence(const QMetaSequence &metaSequence, QMetaType metaType, const void *iterable)
+ : QIterable(metaSequence, metaType.alignOf(), iterable)
+ {
+ }
+
+ Sequence(QIterable<QMetaSequence> &&other) : QIterable(std::move(other)) {}
+
+ Sequence &operator=(QIterable<QMetaSequence> &&other)
+ {
+ QIterable::operator=(std::move(other));
+ return *this;
+ }
+
+ ConstIterator begin() const { return constBegin(); }
+ ConstIterator end() const { return constEnd(); }
+
+ ConstIterator constBegin() const { return ConstIterator(QIterable::constBegin()); }
+ ConstIterator constEnd() const { return ConstIterator(QIterable::constEnd()); }
+
+ Iterator mutableBegin() { return Iterator(QIterable::mutableBegin()); }
+ Iterator mutableEnd() { return Iterator(QIterable::mutableEnd()); }
+
+ QVariant at(qsizetype idx) const
+ {
+ const QMetaSequence meta = metaContainer();
+ return QIterablePrivate::retrieveElement(meta.valueMetaType(), [&](void *dataPtr) {
+ if (meta.canGetValueAtIndex()) {
+ meta.valueAtIndex(constIterable(), idx, dataPtr);
+ return;
+ }
+
+ // ### Qt7: Drop this code. We shouldn't second-guess the underlying container
+ QtPrivate::warnSynthesizedAccess(
+ "at() called on an iterable without native indexed accessors. This is slow");
+ void *it = meta.constBegin(m_iterable.constPointer());
+ meta.advanceConstIterator(it, idx);
+ meta.valueAtConstIterator(it, dataPtr);
+ meta.destroyConstIterator(it);
+ });
+ }
+
+ void set(qsizetype idx, const QVariant &value)
+ {
+ const QMetaSequence meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ const void *dataPtr = coercer.coerce(value, meta.valueMetaType());
+ if (meta.canSetValueAtIndex()) {
+ meta.setValueAtIndex(mutableIterable(), idx, dataPtr);
+ return;
+ }
+
+ // ### Qt7: Drop this code. We shouldn't second-guess the underlying container
+ QtPrivate::warnSynthesizedAccess(
+ "set() called on an iterable without native indexed accessors. This is slow");
+ void *it = meta.begin(m_iterable.mutablePointer());
+ meta.advanceIterator(it, idx);
+ meta.setValueAtIterator(it, dataPtr);
+ meta.destroyIterator(it);
+ }
+
+ void append(const QVariant &value)
+ {
+ const QMetaSequence meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ meta.addValueAtEnd(mutableIterable(), coercer.coerce(value, meta.valueMetaType()));
+ }
+
+ void prepend(const QVariant &value)
+ {
+ const QMetaSequence meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ meta.addValueAtBegin(mutableIterable(), coercer.coerce(value, meta.valueMetaType()));
+ }
+
+ void removeLast()
+ {
+ metaContainer().removeValueAtEnd(mutableIterable());
+ }
+
+ void removeFirst()
+ {
+ metaContainer().removeValueAtBegin(mutableIterable());
+ }
+
+#if QT_DEPRECATED_SINCE(6, 11)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+
+ enum
+ QT_DEPRECATED_VERSION_X_6_11("Use append(), prepend(), removeLast(), or removeFirst() instead.")
+ Position: quint8
+ {
+ Unspecified, AtBegin, AtEnd
+ };
+
+ QT_DEPRECATED_VERSION_X_6_11("Use append() or prepend() instead.")
+ void addValue(const QVariant &value, Position position = Unspecified)
+ {
+ const QMetaSequence meta = metaContainer();
+ QtPrivate::QVariantTypeCoercer coercer;
+ const void *valuePtr = coercer.coerce(value, meta.valueMetaType());
+
+ switch (position) {
+ case AtBegin:
+ if (meta.canAddValueAtBegin())
+ meta.addValueAtBegin(mutableIterable(), valuePtr);
+ break;
+ case AtEnd:
+ if (meta.canAddValueAtEnd())
+ meta.addValueAtEnd(mutableIterable(), valuePtr);
+ break;
+ case Unspecified:
+ if (meta.canAddValue())
+ meta.addValue(mutableIterable(), valuePtr);
+ break;
+ }
+ }
+
+ QT_DEPRECATED_VERSION_X_6_11("Use removeLast() or removeFirst() instead.")
+ void removeValue(Position position = Unspecified)
+ {
+ const QMetaSequence meta = metaContainer();
+
+ switch (position) {
+ case AtBegin:
+ if (meta.canRemoveValueAtBegin())
+ meta.removeValueAtBegin(mutableIterable());
+ break;
+ case AtEnd:
+ if (meta.canRemoveValueAtEnd())
+ meta.removeValueAtEnd(mutableIterable());
+ break;
+ case Unspecified:
+ if (meta.canRemoveValue())
+ meta.removeValue(mutableIterable());
+ break;
+ }
+ }
+
+ QT_DEPRECATED_VERSION_X_6_11("Use QMetaSequence::valueMetaType() instead.")
+ QMetaType valueMetaType() const
+ {
+ return metaContainer().valueMetaType();
+ }
+
+ QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 11)
+};
+} // namespace QtMetaContainerPrivate
+
+QT_END_NAMESPACE
+
+#endif // QMETASEQUENCE_H
diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp
index 1850a148d19..565f9182e68 100644
--- a/src/corelib/kernel/qmetatype.cpp
+++ b/src/corelib/kernel/qmetatype.cpp
@@ -43,7 +43,9 @@
# include "qjsonvalue.h"
# include "qline.h"
# include "qloggingcategory.h"
+# include "qmetaassociation.h"
# include "qmetaobject.h"
+# include "qmetasequence.h"
# include "qobject.h"
# include "qpoint.h"
# include "qrect.h"
@@ -2151,25 +2153,28 @@ static bool convertToEnum(QMetaType fromType, const void *from, QMetaType toType
}
}
-static bool convertIterableToVariantList(QMetaType fromType, const void *from, void *to)
+template<typename Iterable>
+bool convertIterableToVariantList(QMetaType fromType, const void *from, void *to)
{
- QSequentialIterable list;
- if (!QMetaType::convert(fromType, from, QMetaType::fromType<QSequentialIterable>(), &list))
+ Iterable list;
+ if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &list))
return false;
QVariantList &l = *static_cast<QVariantList *>(to);
l.clear();
- l.reserve(list.size());
+ if (list.metaContainer().hasSize())
+ l.reserve(list.size());
auto end = list.end();
for (auto it = list.begin(); it != end; ++it)
l << *it;
return true;
}
-static bool convertIterableToVariantMap(QMetaType fromType, const void *from, void *to)
+template<typename Iterable>
+bool convertIterableToVariantMap(QMetaType fromType, const void *from, void *to)
{
- QAssociativeIterable map;
- if (!QMetaType::convert(fromType, from, QMetaType::fromType<QAssociativeIterable>(), &map))
+ Iterable map;
+ if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &map))
return false;
QVariantMap &h = *static_cast<QVariantMap *>(to);
@@ -2180,10 +2185,11 @@ static bool convertIterableToVariantMap(QMetaType fromType, const void *from, vo
return true;
}
-static bool convertIterableToVariantHash(QMetaType fromType, const void *from, void *to)
+template<typename Iterable>
+bool convertIterableToVariantHash(QMetaType fromType, const void *from, void *to)
{
- QAssociativeIterable map;
- if (!QMetaType::convert(fromType, from, QMetaType::fromType<QAssociativeIterable>(), &map))
+ Iterable map;
+ if (!QMetaType::convert(fromType, from, QMetaType::fromType<Iterable>(), &map))
return false;
QVariantHash &h = *static_cast<QVariantHash *>(to);
@@ -2225,33 +2231,34 @@ static bool convertIterableToVariantPair(QMetaType fromType, const void *from, v
return true;
}
+template<typename Iterable>
static bool convertToSequentialIterable(QMetaType fromType, const void *from, void *to)
{
using namespace QtMetaTypePrivate;
const int fromTypeId = fromType.id();
- QSequentialIterable &i = *static_cast<QSequentialIterable *>(to);
+ Iterable &i = *static_cast<Iterable *>(to);
switch (fromTypeId) {
case QMetaType::QVariantList:
- i = QSequentialIterable(reinterpret_cast<const QVariantList *>(from));
+ i = Iterable(reinterpret_cast<const QVariantList *>(from));
return true;
case QMetaType::QStringList:
- i = QSequentialIterable(reinterpret_cast<const QStringList *>(from));
+ i = Iterable(reinterpret_cast<const QStringList *>(from));
return true;
case QMetaType::QByteArrayList:
- i = QSequentialIterable(reinterpret_cast<const QByteArrayList *>(from));
+ i = Iterable(reinterpret_cast<const QByteArrayList *>(from));
return true;
case QMetaType::QString:
- i = QSequentialIterable(reinterpret_cast<const QString *>(from));
+ i = Iterable(reinterpret_cast<const QString *>(from));
return true;
case QMetaType::QByteArray:
- i = QSequentialIterable(reinterpret_cast<const QByteArray *>(from));
+ i = Iterable(reinterpret_cast<const QByteArray *>(from));
return true;
default: {
- QSequentialIterable impl;
+ QIterable<QMetaSequence> j(QMetaSequence(), nullptr);
if (QMetaType::convert(
- fromType, from, QMetaType::fromType<QIterable<QMetaSequence>>(), &impl)) {
- i = std::move(impl);
+ fromType, from, QMetaType::fromType<QIterable<QMetaSequence>>(), &j)) {
+ i = std::move(j);
return true;
}
}
@@ -2289,27 +2296,28 @@ static bool canImplicitlyViewAsSequentialIterable(QMetaType fromType)
}
}
+template<typename Iterable>
static bool viewAsSequentialIterable(QMetaType fromType, void *from, void *to)
{
using namespace QtMetaTypePrivate;
const int fromTypeId = fromType.id();
- QSequentialIterable &i = *static_cast<QSequentialIterable *>(to);
+ Iterable &i = *static_cast<Iterable *>(to);
switch (fromTypeId) {
case QMetaType::QVariantList:
- i = QSequentialIterable(reinterpret_cast<QVariantList *>(from));
+ i = Iterable(reinterpret_cast<QVariantList *>(from));
return true;
case QMetaType::QStringList:
- i = QSequentialIterable(reinterpret_cast<QStringList *>(from));
+ i = Iterable(reinterpret_cast<QStringList *>(from));
return true;
case QMetaType::QByteArrayList:
- i = QSequentialIterable(reinterpret_cast<QByteArrayList *>(from));
+ i = Iterable(reinterpret_cast<QByteArrayList *>(from));
return true;
case QMetaType::QString:
- i = QSequentialIterable(reinterpret_cast<QString *>(from));
+ i = Iterable(reinterpret_cast<QString *>(from));
return true;
case QMetaType::QByteArray:
- i = QSequentialIterable(reinterpret_cast<QByteArray *>(from));
+ i = Iterable(reinterpret_cast<QByteArray *>(from));
return true;
default: {
QIterable<QMetaSequence> j(QMetaSequence(), nullptr);
@@ -2324,24 +2332,25 @@ static bool viewAsSequentialIterable(QMetaType fromType, void *from, void *to)
return false;
}
+template<typename Iterable>
static bool convertToAssociativeIterable(QMetaType fromType, const void *from, void *to)
{
using namespace QtMetaTypePrivate;
- QAssociativeIterable &i = *static_cast<QAssociativeIterable *>(to);
+ Iterable &i = *static_cast<Iterable *>(to);
if (fromType.id() == QMetaType::QVariantMap) {
- i = QAssociativeIterable(reinterpret_cast<const QVariantMap *>(from));
+ i = Iterable(reinterpret_cast<const QVariantMap *>(from));
return true;
}
if (fromType.id() == QMetaType::QVariantHash) {
- i = QAssociativeIterable(reinterpret_cast<const QVariantHash *>(from));
+ i = Iterable(reinterpret_cast<const QVariantHash *>(from));
return true;
}
- QAssociativeIterable impl;
+ QIterable<QMetaAssociation> j(QMetaAssociation(), nullptr);
if (QMetaType::convert(
- fromType, from, QMetaType::fromType<QIterable<QMetaAssociation>>(), &impl)) {
- i = std::move(impl);
+ fromType, from, QMetaType::fromType<QIterable<QMetaAssociation>>(), &j)) {
+ i = std::move(j);
return true;
}
@@ -2384,18 +2393,19 @@ static bool canImplicitlyViewAsAssociativeIterable(QMetaType fromType)
}
}
+template<typename Iterable>
static bool viewAsAssociativeIterable(QMetaType fromType, void *from, void *to)
{
using namespace QtMetaTypePrivate;
int fromTypeId = fromType.id();
- QAssociativeIterable &i = *static_cast<QAssociativeIterable *>(to);
+ Iterable &i = *static_cast<Iterable *>(to);
if (fromTypeId == QMetaType::QVariantMap) {
- i = QAssociativeIterable(reinterpret_cast<QVariantMap *>(from));
+ i = Iterable(reinterpret_cast<QVariantMap *>(from));
return true;
}
if (fromTypeId == QMetaType::QVariantHash) {
- i = QAssociativeIterable(reinterpret_cast<QVariantHash *>(from));
+ i = Iterable(reinterpret_cast<QVariantHash *>(from));
return true;
}
@@ -2493,20 +2503,54 @@ bool QMetaType::convert(QMetaType fromType, const void *from, QMetaType toType,
return true;
// handle iterables
- if (toTypeId == QVariantList && convertIterableToVariantList(fromType, from, to))
+ if (toTypeId == QVariantList
+ && convertIterableToVariantList<QMetaSequence::Iterable>(fromType, from, to)) {
return true;
+ }
- if (toTypeId == QVariantMap && convertIterableToVariantMap(fromType, from, to))
+ if (toTypeId == QVariantMap
+ && convertIterableToVariantMap<QMetaAssociation::Iterable>(fromType, from, to)) {
return true;
+ }
- if (toTypeId == QVariantHash && convertIterableToVariantHash(fromType, from, to))
+ if (toTypeId == QVariantHash
+ && convertIterableToVariantHash<QMetaAssociation::Iterable>(fromType, from, to)) {
return true;
+ }
+
+ if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>())
+ return convertToSequentialIterable<QMetaSequence::Iterable>(fromType, from, to);
+
+ if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>())
+ return convertToAssociativeIterable<QMetaAssociation::Iterable>(fromType, from, to);
+
+#if QT_DEPRECATED_SINCE(6, 13)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+
+ if (toTypeId == QVariantList
+ && convertIterableToVariantList<QSequentialIterable>(fromType, from, to)) {
+ return true;
+ }
+
+ if (toTypeId == QVariantMap
+ && convertIterableToVariantMap<QAssociativeIterable>(fromType, from, to)) {
+ return true;
+ }
+
+ if (toTypeId == QVariantHash
+ && convertIterableToVariantHash<QAssociativeIterable>(fromType, from, to)) {
+ return true;
+ }
if (toTypeId == qMetaTypeId<QSequentialIterable>())
- return convertToSequentialIterable(fromType, from, to);
+ return convertToSequentialIterable<QSequentialIterable>(fromType, from, to);
if (toTypeId == qMetaTypeId<QAssociativeIterable>())
- return convertToAssociativeIterable(fromType, from, to);
+ return convertToAssociativeIterable<QAssociativeIterable>(fromType, from, to);
+
+ QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 13)
return convertMetaObject(fromType, from, toType, to);
}
@@ -2528,11 +2572,24 @@ bool QMetaType::view(QMetaType fromType, void *from, QMetaType toType, void *to)
if (f)
return (*f)(from, to);
+ if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>())
+ return viewAsSequentialIterable<QMetaSequence::Iterable>(fromType, from, to);
+
+ if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>())
+ return viewAsAssociativeIterable<QMetaAssociation::Iterable>(fromType, from, to);
+
+#if QT_DEPRECATED_SINCE(6, 13)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+
if (toTypeId == qMetaTypeId<QSequentialIterable>())
- return viewAsSequentialIterable(fromType, from, to);
+ return viewAsSequentialIterable<QSequentialIterable>(fromType, from, to);
if (toTypeId == qMetaTypeId<QAssociativeIterable>())
- return viewAsAssociativeIterable(fromType, from, to);
+ return viewAsAssociativeIterable<QAssociativeIterable>(fromType, from, to);
+
+ QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 13)
return convertMetaObject(fromType, from, toType, to);
}
@@ -2545,14 +2602,14 @@ bool QMetaType::view(QMetaType fromType, void *from, QMetaType toType, void *to)
function if a qobject_cast from the type described by \a fromType to the type described
by \a toType would succeed.
- You can create a mutable view of type QSequentialIterable on any container registered with
+ You can create a mutable view of type QMetaSequence::Iterable on any container registered with
Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE().
- Similarly you can create a mutable view of type QAssociativeIterable on any container
+ Similarly you can create a mutable view of type QMetaAssociation::Iterable on any container
registered with Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE().
- \sa convert(), QSequentialIterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(),
- QAssociativeIterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE()
+ \sa convert(), QMetaSequence::Iterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(),
+ QMetaAssociation::Iterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE()
*/
bool QMetaType::canView(QMetaType fromType, QMetaType toType)
{
@@ -2566,12 +2623,25 @@ bool QMetaType::canView(QMetaType fromType, QMetaType toType)
if (f)
return true;
+ if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>())
+ return canImplicitlyViewAsSequentialIterable(fromType);
+
+ if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>())
+ return canImplicitlyViewAsAssociativeIterable(fromType);
+
+#if QT_DEPRECATED_SINCE(6, 13)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+
if (toTypeId == qMetaTypeId<QSequentialIterable>())
return canImplicitlyViewAsSequentialIterable(fromType);
if (toTypeId == qMetaTypeId<QAssociativeIterable>())
return canImplicitlyViewAsAssociativeIterable(fromType);
+ QT_WARNING_POP
+#endif
+
if (canConvertMetaObject(fromType, toType))
return true;
@@ -2660,8 +2730,8 @@ bool QMetaType::canView(QMetaType fromType, QMetaType toType)
Similarly, a cast from an associative container will also return true for this
function the \a toType is QVariantHash or QVariantMap.
- \sa convert(), QSequentialIterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(), QAssociativeIterable,
- Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE()
+ \sa convert(), QMetaSequence::Iterable, Q_DECLARE_SEQUENTIAL_CONTAINER_METATYPE(),
+ QMetaAssociation::Iterable, Q_DECLARE_ASSOCIATIVE_CONTAINER_METATYPE()
*/
bool QMetaType::canConvert(QMetaType fromType, QMetaType toType)
{
@@ -2682,11 +2752,32 @@ bool QMetaType::canConvert(QMetaType fromType, QMetaType toType)
if (f)
return true;
+ if (toTypeId == qMetaTypeId<QMetaSequence::Iterable>())
+ return canConvertToSequentialIterable(fromType);
+
+ if (toTypeId == qMetaTypeId<QMetaAssociation::Iterable>())
+ return canConvertToAssociativeIterable(fromType);
+
+ if (toTypeId == QVariantList
+ && canConvert(fromType, QMetaType::fromType<QMetaSequence::Iterable>())) {
+ return true;
+ }
+
+ if ((toTypeId == QVariantHash || toTypeId == QVariantMap)
+ && canConvert(fromType, QMetaType::fromType<QMetaAssociation::Iterable>())) {
+ return true;
+ }
+
+#if QT_DEPRECATED_SINCE(6, 13)
+ QT_WARNING_PUSH
+ QT_WARNING_DISABLE_DEPRECATED
+
if (toTypeId == qMetaTypeId<QSequentialIterable>())
return canConvertToSequentialIterable(fromType);
if (toTypeId == qMetaTypeId<QAssociativeIterable>())
return canConvertToAssociativeIterable(fromType);
+
if (toTypeId == QVariantList
&& canConvert(fromType, QMetaType::fromType<QSequentialIterable>())) {
return true;
@@ -2697,6 +2788,9 @@ bool QMetaType::canConvert(QMetaType fromType, QMetaType toType)
return true;
}
+ QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 13)
+
if (toTypeId == QVariantPair && hasRegisteredConverterFunction(
fromType, QMetaType::fromType<QtMetaTypePrivate::QPairVariantInterfaceImpl>()))
return true;
diff --git a/src/corelib/kernel/qobjectdefs.h b/src/corelib/kernel/qobjectdefs.h
index 848102cc57a..d3e761982f5 100644
--- a/src/corelib/kernel/qobjectdefs.h
+++ b/src/corelib/kernel/qobjectdefs.h
@@ -247,6 +247,8 @@ struct Q_CORE_EXPORT QMetaObject
QMetaType metaType() const;
+ const char *metaObjectHash() const;
+
int methodOffset() const;
int enumeratorOffset() const;
int propertyOffset() const;
diff --git a/src/corelib/kernel/qsequentialiterable.cpp b/src/corelib/kernel/qsequentialiterable.cpp
index 32c58266045..b256b129d2c 100644
--- a/src/corelib/kernel/qsequentialiterable.cpp
+++ b/src/corelib/kernel/qsequentialiterable.cpp
@@ -7,8 +7,13 @@
QT_BEGIN_NAMESPACE
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
/*!
\class QSequentialIterable
+ \deprecated [6.15] Use QMetaSequence::Iterable instead.
\since 5.2
\inmodule QtCore
\brief The QSequentialIterable class is an iterable interface for a container in a QVariant.
@@ -17,8 +22,6 @@ QT_BEGIN_NAMESPACE
a QVariant. An instance of QSequentialIterable can be extracted from a QVariant if it can
be converted to a QVariantList.
- \snippet code/src_corelib_kernel_qvariant.cpp 9
-
The container itself is not copied before iterating over it.
\sa QVariant
@@ -160,17 +163,17 @@ void QSequentialIterable::set(qsizetype idx, const QVariant &value)
/*!
\typealias QSequentialIterable::const_iterator
+ \deprecated [6.15] Use QMetaSequence::Iterable::ConstIterator instead.
\brief The QSequentialIterable::const_iterator allows iteration over a container in a QVariant.
A QSequentialIterable::const_iterator can only be created by a QSequentialIterable instance,
and can be used in a way similar to other stl-style iterators.
-
- \snippet code/src_corelib_kernel_qvariant.cpp 9
*/
/*!
\typealias QSequentialIterable::iterator
\since 6.0
+ \deprecated [6.15] Use QMetaSequence::Iterable::Iterator instead.
\brief The QSequentialIterable::iterator allows iteration over a container in a QVariant.
A QSequentialIterable::iterator can only be created by a QSequentialIterable instance,
@@ -221,4 +224,7 @@ QVariantConstPointer QSequentialConstIterator::operator->() const
return QVariantConstPointer(operator*());
}
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qsequentialiterable.h b/src/corelib/kernel/qsequentialiterable.h
index dac146d2ad3..92252cb19dd 100644
--- a/src/corelib/kernel/qsequentialiterable.h
+++ b/src/corelib/kernel/qsequentialiterable.h
@@ -9,7 +9,21 @@
QT_BEGIN_NAMESPACE
-class Q_CORE_EXPORT QSequentialIterator : public QIterator<QMetaSequence>
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
+#if defined(Q_CC_GNU_ONLY) && Q_CC_GNU < 1300
+ // GCC < 13 doesn't accept both deprecation and visibility on the same class
+ #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) Q_CORE_EXPORT
+#else
+ #define QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15(text) \
+ Q_CORE_EXPORT QT_DEPRECATED_VERSION_X_6_15(text)
+#endif
+
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::Iterator instead.")
+QSequentialIterator : public QIterator<QMetaSequence>
{
public:
using value_type = QVariant;
@@ -24,7 +38,9 @@ public:
QVariantPointer<QSequentialIterator> operator->() const;
};
-class Q_CORE_EXPORT QSequentialConstIterator : public QConstIterator<QMetaSequence>
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable::ConstIterator instead.")
+QSequentialConstIterator : public QConstIterator<QMetaSequence>
{
public:
using value_type = QVariant;
@@ -39,7 +55,9 @@ public:
QVariantConstPointer operator->() const;
};
-class Q_CORE_EXPORT QSequentialIterable : public QIterable<QMetaSequence>
+class
+QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15("Use QMetaSequence::Iterable instead.")
+QSequentialIterable : public QIterable<QMetaSequence>
{
public:
using iterator = QTaggedIterator<QSequentialIterator, void>;
@@ -79,14 +97,12 @@ public:
{
}
- // ### Qt7: Pass QMetaType as value rather than const ref.
QSequentialIterable(const QMetaSequence &metaSequence, const QMetaType &metaType,
void *iterable)
: QIterable(metaSequence, metaType.alignOf(), iterable)
{
}
- // ### Qt7: Pass QMetaType as value rather than const ref.
QSequentialIterable(const QMetaSequence &metaSequence, const QMetaType &metaType,
const void *iterable)
: QIterable(metaSequence, metaType.alignOf(), iterable)
@@ -150,6 +166,11 @@ Q_DECLARE_TYPEINFO(QSequentialIterable, Q_RELOCATABLE_TYPE);
Q_DECLARE_TYPEINFO(QSequentialIterable::iterator, Q_RELOCATABLE_TYPE);
Q_DECLARE_TYPEINFO(QSequentialIterable::const_iterator, Q_RELOCATABLE_TYPE);
+#undef QT_CORE_DEPRECATED_EXPORT_VERSION_X_6_15
+
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
QT_END_NAMESPACE
#endif // QSEQUENTIALITERABLE_H
diff --git a/src/corelib/kernel/qtmocconstants.h b/src/corelib/kernel/qtmocconstants.h
index 79c0138bb28..822e02e6c8e 100644
--- a/src/corelib/kernel/qtmocconstants.h
+++ b/src/corelib/kernel/qtmocconstants.h
@@ -30,7 +30,8 @@ namespace QtMocConstants {
// revision 11 is Qt 6.5: The metatype for void is stored in the metatypes array
// revision 12 is Qt 6.6: It adds the metatype for enums
// revision 13 is Qt 6.9: Adds support for 64-bit QFlags and moves the method revision
-enum { OutputRevision = 13 }; // Used by moc, qmetaobjectbuilder and qdbus
+// revision 14 is Qt 6.11: Adds a hash of meta object contents
+enum { OutputRevision = 14 }; // Used by moc, qmetaobjectbuilder and qdbus
enum PropertyFlags : uint {
Invalid = 0x00000000,
@@ -39,7 +40,8 @@ enum PropertyFlags : uint {
Resettable = 0x00000004,
EnumOrFlag = 0x00000008,
Alias = 0x00000010,
- // Reserved for future usage = 0x00000020,
+ Virtual = 0x00000020,
+ Override = 0x00000040,
StdCppSet = 0x00000100,
Constant = 0x00000400,
Final = 0x00000800,
diff --git a/src/corelib/kernel/qtmochelpers.h b/src/corelib/kernel/qtmochelpers.h
index 4c549e78ad5..3d2b59d2a73 100644
--- a/src/corelib/kernel/qtmochelpers.h
+++ b/src/corelib/kernel/qtmochelpers.h
@@ -511,7 +511,8 @@ template <typename ObjectType, typename Unique, typename Strings,
typename Constructors = UintData<>, typename ClassInfo = detail::UintDataBlock<0, 0>>
constexpr auto metaObjectData(uint flags, const Strings &strings,
const Methods &methods, const Properties &properties,
- const Enums &enums, const Constructors &constructors = {},
+ const Enums &enums, int qt_metaObjectHashIndex = -1,
+ const Constructors &constructors = {},
const ClassInfo &classInfo = {})
{
constexpr uint MetaTypeCount = Properties::metaTypeCount()
@@ -520,7 +521,7 @@ constexpr auto metaObjectData(uint flags, const Strings &strings,
+ Methods::metaTypeCount()
+ Constructors::metaTypeCount();
- constexpr uint HeaderSize = 14;
+ constexpr uint HeaderSize = 15;
constexpr uint TotalSize = HeaderSize
+ Properties::dataSize()
+ Enums::dataSize()
@@ -582,6 +583,8 @@ constexpr auto metaObjectData(uint flags, const Strings &strings,
}
}
+ data[14] = qt_metaObjectHashIndex;
+
return result;
}
diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp
index 57089f164b2..7cad20e9fd4 100644
--- a/src/corelib/kernel/qvariant.cpp
+++ b/src/corelib/kernel/qvariant.cpp
@@ -2852,9 +2852,14 @@ const void *QtPrivate::QVariantTypeCoercer::coerce(const QVariant &value, const
return converted.constData();
}
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
/*!
\class QVariantRef
\since 6.0
+ \deprecated [6.15] Use QVariant::Reference instead.
\inmodule QtCore
\brief The QVariantRef acts as a non-const reference to a QVariant.
@@ -2909,6 +2914,7 @@ const void *QtPrivate::QVariantTypeCoercer::coerce(const QVariant &value, const
/*!
\class QVariantConstPointer
\since 6.0
+ \deprecated [6.15] Use QVariant::ConstPointer instead.
\inmodule QtCore
\brief Emulated const pointer to QVariant based on a pointer.
@@ -2946,6 +2952,7 @@ const QVariant *QVariantConstPointer::operator->() const
/*!
\class QVariantPointer
\since 6.0
+ \deprecated [6.15] Use QVariant::Pointer instead.
\inmodule QtCore
\brief QVariantPointer is a template class that emulates a pointer to QVariant based on a pointer.
@@ -2974,6 +2981,9 @@ const QVariant *QVariantConstPointer::operator->() const
implement operator->().
*/
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
/*!
\class QVariant::ConstReference
\since 6.11
diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h
index 9117c827afe..82eec0693d6 100644
--- a/src/corelib/kernel/qvariant.h
+++ b/src/corelib/kernel/qvariant.h
@@ -983,8 +983,13 @@ private:
};
}
-template<typename Pointer>
-class QVariantRef
+#if QT_DEPRECATED_SINCE(6, 15)
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+
+template<typename Pointer> class
+QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Reference instead.")
+QVariantRef
{
private:
const Pointer *m_pointer = nullptr;
@@ -1008,20 +1013,23 @@ public:
}
};
-class Q_CORE_EXPORT QVariantConstPointer
+class
+QT_DEPRECATED_VERSION_X_6_15("Use QVariant::ConstPointer instead.")
+QVariantConstPointer
{
private:
QVariant m_variant;
public:
- explicit QVariantConstPointer(QVariant variant);
+ Q_CORE_EXPORT explicit QVariantConstPointer(QVariant variant);
- QVariant operator*() const;
- const QVariant *operator->() const;
+ Q_CORE_EXPORT QVariant operator*() const;
+ Q_CORE_EXPORT const QVariant *operator->() const;
};
-template<typename Pointer>
-class QVariantPointer
+template<typename Pointer> class
+QT_DEPRECATED_VERSION_X_6_15("Use QVariant::Pointer instead.")
+QVariantPointer
{
private:
const Pointer *m_pointer = nullptr;
@@ -1032,6 +1040,9 @@ public:
Pointer operator->() const { return *m_pointer; }
};
+QT_WARNING_POP
+#endif // QT_DEPRECATED_SINCE(6, 15)
+
QT_END_NAMESPACE
#endif // QVARIANT_H
diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp
index de7043e8c1d..9c26de94b6d 100644
--- a/src/corelib/mimetypes/qmimeprovider.cpp
+++ b/src/corelib/mimetypes/qmimeprovider.cpp
@@ -512,8 +512,8 @@ QMimeBinaryProvider::MimeTypeExtraMap::const_iterator
QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName)
{
#if QT_CONFIG(xmlstreamreader)
- auto it = m_mimetypeExtra.find(mimeName);
- if (it == m_mimetypeExtra.cend()) {
+ auto [it, insertionOccurred] = m_mimetypeExtra.try_emplace(mimeName);
+ if (insertionOccurred) {
// load comment and globPatterns
// shared-mime-info since 1.3 lowercases the xml files
@@ -523,9 +523,8 @@ QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName)
QFile qfile(mimeFile);
if (!qfile.open(QFile::ReadOnly))
- return m_mimetypeExtra.cend();
+ return it;
- it = m_mimetypeExtra.try_emplace(mimeName).first;
MimeTypeExtra &extra = it->second;
QString mainPattern;
diff --git a/src/corelib/thread/qfuture.qdoc b/src/corelib/thread/qfuture.qdoc
index c4d4daae99f..f3f32e20adc 100644
--- a/src/corelib/thread/qfuture.qdoc
+++ b/src/corelib/thread/qfuture.qdoc
@@ -215,6 +215,20 @@
It's recommended to use it on the QFuture object that represents the entire
continuation chain, like it's shown in the example above.
+ If any of the continuations in the chain executes an asynchronous
+ computation and returns a QFuture representing it, the \c cancelChain() call
+ will not be propagated into such nested computation once it is started.
+ The reason for that is that the future will be available in the continuation
+ chain only when the outer future is fulfilled, but the cancellation might
+ happen when both an outer and a nested futures are still waiting for their
+ computations to be finished. In such cases, the nested future needs to be
+ captured and canceled explicitly.
+
+ \snippet code/src_corelib_thread_qfuture.cpp 39
+
+ In this example, if \c runNestedComputation() is already in progress,
+ it can only be canceled by calling \c {nested.cancel()}.
+
\sa cancel()
*/
diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h
index 5a827fb4148..bdb0e24dde8 100644
--- a/src/corelib/tools/qflatmap_p.h
+++ b/src/corelib/tools/qflatmap_p.h
@@ -609,14 +609,18 @@ public:
T value(const Key &key) const
{
auto it = find(key);
- return it == end() ? T() : it.value();
+ if (it == end())
+ return T();
+ return it.value();
}
template <class X, class Y = Compare, is_marked_transparent<Y> = nullptr>
T value(const X &key) const
{
auto it = find(key);
- return it == end() ? T() : it.value();
+ if (it == end())
+ return T();
+ return it.value();
}
T &operator[](const Key &key)
@@ -899,12 +903,13 @@ private:
T do_take(iterator it)
{
- if (it != end()) {
+ if (it == end())
+ return {};
+ return [&] {
T result = std::move(it.value());
erase(it);
return result;
- }
- return {};
+ }();
}
template <class InputIt, is_compatible_iterator<InputIt> = nullptr>
diff --git a/src/corelib/tools/qmargins.h b/src/corelib/tools/qmargins.h
index f833a338b16..cbdb093adc8 100644
--- a/src/corelib/tools/qmargins.h
+++ b/src/corelib/tools/qmargins.h
@@ -333,20 +333,13 @@ private:
qreal m_right;
qreal m_bottom;
- QT_WARNING_PUSH
- QT_WARNING_DISABLE_FLOAT_COMPARE
friend constexpr bool qFuzzyCompare(const QMarginsF &lhs, const QMarginsF &rhs) noexcept
{
- return ((!lhs.m_left || !rhs.m_left) ? qFuzzyIsNull(lhs.m_left - rhs.m_left)
- : qFuzzyCompare(lhs.m_left, rhs.m_left))
- && ((!lhs.m_top || !rhs.m_top) ? qFuzzyIsNull(lhs.m_top - rhs.m_top)
- : qFuzzyCompare(lhs.m_top, rhs.m_top))
- && ((!lhs.m_right || !rhs.m_right) ? qFuzzyIsNull(lhs.m_right - rhs.m_right)
- : qFuzzyCompare(lhs.m_right, rhs.m_right))
- && ((!lhs.m_bottom || !rhs.m_bottom) ? qFuzzyIsNull(lhs.m_bottom - rhs.m_bottom)
- : qFuzzyCompare(lhs.m_bottom, rhs.m_bottom));
+ return QtPrivate::fuzzyCompare(lhs.m_left, rhs.m_left)
+ && QtPrivate::fuzzyCompare(lhs.m_top, rhs.m_top)
+ && QtPrivate::fuzzyCompare(lhs.m_right, rhs.m_right)
+ && QtPrivate::fuzzyCompare(lhs.m_bottom, rhs.m_bottom);
}
- QT_WARNING_POP
friend constexpr bool qFuzzyIsNull(const QMarginsF &m) noexcept
{
return qFuzzyIsNull(m.m_left) && qFuzzyIsNull(m.m_top)
diff --git a/src/corelib/tools/qpoint.h b/src/corelib/tools/qpoint.h
index ae896ba7079..1b767324058 100644
--- a/src/corelib/tools/qpoint.h
+++ b/src/corelib/tools/qpoint.h
@@ -259,14 +259,11 @@ public:
}
private:
- QT_WARNING_PUSH
- QT_WARNING_DISABLE_FLOAT_COMPARE
friend constexpr bool qFuzzyCompare(const QPointF &p1, const QPointF &p2) noexcept
{
- return ((!p1.xp || !p2.xp) ? qFuzzyIsNull(p1.xp - p2.xp) : qFuzzyCompare(p1.xp, p2.xp))
- && ((!p1.yp || !p2.yp) ? qFuzzyIsNull(p1.yp - p2.yp) : qFuzzyCompare(p1.yp, p2.yp));
+ return QtPrivate::fuzzyCompare(p1.xp, p2.xp)
+ && QtPrivate::fuzzyCompare(p1.yp, p2.yp);
}
- QT_WARNING_POP
friend constexpr bool qFuzzyIsNull(const QPointF &point) noexcept
{
return qFuzzyIsNull(point.xp) && qFuzzyIsNull(point.yp);
diff --git a/src/corelib/tools/qsize.h b/src/corelib/tools/qsize.h
index 86509cb6483..1c5b02ed1f0 100644
--- a/src/corelib/tools/qsize.h
+++ b/src/corelib/tools/qsize.h
@@ -258,10 +258,9 @@ private:
QT_WARNING_DISABLE_FLOAT_COMPARE
friend constexpr bool qFuzzyCompare(const QSizeF &s1, const QSizeF &s2) noexcept
{
- // Cannot use qFuzzyCompare(), because it will give incorrect results
// if one of the arguments is 0.0.
- return ((!s1.wd || !s2.wd) ? qFuzzyIsNull(s1.wd - s2.wd) : qFuzzyCompare(s1.wd, s2.wd))
- && ((!s1.ht || !s2.ht) ? qFuzzyIsNull(s1.ht - s2.ht) : qFuzzyCompare(s1.ht, s2.ht));
+ return QtPrivate::fuzzyCompare(s1.wd, s2.wd)
+ && QtPrivate::fuzzyCompare(s1.ht, s2.ht);
}
QT_WARNING_POP
friend constexpr bool qFuzzyIsNull(const QSizeF &size) noexcept
diff --git a/src/dbus/qdbusmetaobject.cpp b/src/dbus/qdbusmetaobject.cpp
index 149392f9c3c..a4ffd7a64dd 100644
--- a/src/dbus/qdbusmetaobject.cpp
+++ b/src/dbus/qdbusmetaobject.cpp
@@ -383,9 +383,10 @@ void QDBusMetaObjectGenerator::write(QDBusMetaObject *obj)
- methods.size(); // ditto
QDBusMetaObjectPrivate *header = reinterpret_cast<QDBusMetaObjectPrivate *>(idata.data());
- static_assert(QMetaObjectPrivate::OutputRevision == 13, "QtDBus meta-object generator should generate the same version as moc");
+ static_assert(QMetaObjectPrivate::OutputRevision == 14, "QtDBus meta-object generator should generate the same version as moc");
header->revision = QMetaObjectPrivate::OutputRevision;
header->className = 0;
+ header->metaObjectHashIndex = -1; // TODO support hash in dbus metaobject too
header->classInfoCount = 0;
header->classInfoData = 0;
header->methodCount = int(signals_.size() + methods.size());
diff --git a/src/gui/accessible/linux/atspiadaptor.cpp b/src/gui/accessible/linux/atspiadaptor.cpp
index dad0ac2b74a..dae83437b89 100644
--- a/src/gui/accessible/linux/atspiadaptor.cpp
+++ b/src/gui/accessible/linux/atspiadaptor.cpp
@@ -132,7 +132,7 @@ AtSpiAdaptor::~AtSpiAdaptor()
*/
QString AtSpiAdaptor::introspect(const QString &path) const
{
- static const QLatin1StringView accessibleIntrospection(
+ constexpr auto accessibleIntrospection =
" <interface name=\"org.a11y.atspi.Accessible\">\n"
" <property access=\"read\" type=\"s\" name=\"Name\"/>\n"
" <property access=\"read\" type=\"s\" name=\"Description\"/>\n"
@@ -182,9 +182,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"s\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView actionIntrospection(
+ constexpr auto actionIntrospection =
" <interface name=\"org.a11y.atspi.Action\">\n"
" <property access=\"read\" type=\"i\" name=\"NActions\"/>\n"
" <method name=\"GetDescription\">\n"
@@ -208,9 +208,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView applicationIntrospection(
+ constexpr auto applicationIntrospection =
" <interface name=\"org.a11y.atspi.Application\">\n"
" <property access=\"read\" type=\"s\" name=\"ToolkitName\"/>\n"
" <property access=\"read\" type=\"s\" name=\"Version\"/>\n"
@@ -223,9 +223,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"s\" name=\"address\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView collectionIntrospection(
+ constexpr auto collectionIntrospection =
" <interface name=\"org.a11y.atspi.Collection\">\n"
" <method name=\"GetMatches\">\n"
" <arg direction=\"in\" name=\"rule\" type=\"(aiia{ss}iaiiasib)\"/>\n"
@@ -266,9 +266,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QSpiReferenceSet\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView componentIntrospection(
+ constexpr auto componentIntrospection =
" <interface name=\"org.a11y.atspi.Component\">\n"
" <method name=\"Contains\">\n"
" <arg direction=\"in\" type=\"i\" name=\"x\"/>\n"
@@ -329,9 +329,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView editableTextIntrospection(
+ constexpr auto editableTextIntrospection =
" <interface name=\"org.a11y.atspi.EditableText\">\n"
" <method name=\"SetTextContents\">\n"
" <arg direction=\"in\" type=\"s\" name=\"newContents\"/>\n"
@@ -362,9 +362,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView selectionIntrospection(
+ constexpr auto selectionIntrospection =
" <interface name=\"org.a11y.atspi.Selection\">\n"
" <property name=\"NSelectedChildren\" type=\"i\" access=\"read\"/>\n"
" <method name=\"GetSelectedChild\">\n"
@@ -395,9 +395,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView tableIntrospection(
+ constexpr auto tableIntrospection =
" <interface name=\"org.a11y.atspi.Table\">\n"
" <property access=\"read\" type=\"i\" name=\"NRows\"/>\n"
" <property access=\"read\" type=\"i\" name=\"NColumns\"/>\n"
@@ -503,9 +503,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\" name=\"is_selected\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView tableCellIntrospection(
+ constexpr auto tableCellIntrospection =
" <interface name=\"org.a11y.atspi.TableCell\">\n"
" <property access=\"read\" name=\"ColumnSpan\" type=\"i\" />\n"
" <property access=\"read\" name=\"Position\" type=\"(ii)\">\n"
@@ -531,9 +531,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <annotation value=\"QSpiObjectReferenceArray\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView textIntrospection(
+ constexpr auto textIntrospection =
" <interface name=\"org.a11y.atspi.Text\">\n"
" <property access=\"read\" type=\"i\" name=\"CharacterCount\"/>\n"
" <property access=\"read\" type=\"i\" name=\"CaretOffset\"/>\n"
@@ -670,9 +670,9 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"out\" type=\"b\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
- static const QLatin1StringView valueIntrospection(
+ constexpr auto valueIntrospection =
" <interface name=\"org.a11y.atspi.Value\">\n"
" <property access=\"read\" type=\"d\" name=\"MinimumValue\"/>\n"
" <property access=\"read\" type=\"d\" name=\"MaximumValue\"/>\n"
@@ -682,7 +682,7 @@ QString AtSpiAdaptor::introspect(const QString &path) const
" <arg direction=\"in\" type=\"d\" name=\"value\"/>\n"
" </method>\n"
" </interface>\n"
- );
+ ""_L1;
QAccessibleInterface * interface = interfaceFromPath(path);
if (!interface) {
diff --git a/src/gui/accessible/linux/qspi_struct_marshallers.cpp b/src/gui/accessible/linux/qspi_struct_marshallers.cpp
index 241bad502e3..5e171244cd0 100644
--- a/src/gui/accessible/linux/qspi_struct_marshallers.cpp
+++ b/src/gui/accessible/linux/qspi_struct_marshallers.cpp
@@ -28,7 +28,6 @@ QT_IMPL_METATYPE_EXTERN(QSpiRelationArray)
QT_IMPL_METATYPE_EXTERN(QSpiTextRange)
QT_IMPL_METATYPE_EXTERN(QSpiTextRangeList)
QT_IMPL_METATYPE_EXTERN(QSpiAttributeSet)
-QT_IMPL_METATYPE_EXTERN(QSpiAppUpdate)
QT_IMPL_METATYPE_EXTERN(QSpiDeviceEvent)
QT_IMPL_METATYPE_EXTERN(QSpiMatchRule)
@@ -134,23 +133,6 @@ const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiEventListener
return argument;
}
-/* QSpiAppUpdate */
-/*---------------------------------------------------------------------------*/
-
-QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update) {
- argument.beginStructure();
- argument << update.type << update.address;
- argument.endStructure();
- return argument;
-}
-
-const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update) {
- argument.beginStructure();
- argument >> update.type >> update.address;
- argument.endStructure();
- return argument;
-}
-
/* QSpiRelationArrayEntry */
/*---------------------------------------------------------------------------*/
@@ -245,7 +227,6 @@ void qSpiInitializeStructTypes()
qDBusRegisterMetaType<QSpiEventListenerArray>();
qDBusRegisterMetaType<QSpiDeviceEvent>();
qDBusRegisterMetaType<QSpiMatchRule>();
- qDBusRegisterMetaType<QSpiAppUpdate>();
qDBusRegisterMetaType<QSpiRelationArrayEntry>();
qDBusRegisterMetaType<QSpiRelationArray>();
}
diff --git a/src/gui/accessible/linux/qspi_struct_marshallers_p.h b/src/gui/accessible/linux/qspi_struct_marshallers_p.h
index fe2d52fb4c2..4c446a97040 100644
--- a/src/gui/accessible/linux/qspi_struct_marshallers_p.h
+++ b/src/gui/accessible/linux/qspi_struct_marshallers_p.h
@@ -106,21 +106,6 @@ Q_DECLARE_TYPEINFO(QSpiTextRange, Q_RELOCATABLE_TYPE);
typedef QList<QSpiTextRange> QSpiTextRangeList;
typedef QMap <QString, QString> QSpiAttributeSet;
-enum QSpiAppUpdateType {
- QSPI_APP_UPDATE_ADDED = 0,
- QSPI_APP_UPDATE_REMOVED = 1
-};
-Q_DECLARE_TYPEINFO(QSpiAppUpdateType, Q_PRIMITIVE_TYPE);
-
-struct QSpiAppUpdate {
- int type; /* Is an application added or removed */
- QString address; /* D-Bus address of application added or removed */
-};
-Q_DECLARE_TYPEINFO(QSpiAppUpdate, Q_RELOCATABLE_TYPE);
-
-QDBusArgument &operator<<(QDBusArgument &argument, const QSpiAppUpdate &update);
-const QDBusArgument &operator>>(const QDBusArgument &argument, QSpiAppUpdate &update);
-
struct QSpiDeviceEvent {
unsigned int type;
int id;
@@ -171,7 +156,6 @@ QT_DECL_METATYPE_EXTERN(QSpiRelationArray, /* not exported */)
QT_DECL_METATYPE_EXTERN(QSpiTextRange, /* not exported */)
QT_DECL_METATYPE_EXTERN(QSpiTextRangeList, /* not exported */)
QT_DECL_METATYPE_EXTERN(QSpiAttributeSet, /* not exported */)
-QT_DECL_METATYPE_EXTERN(QSpiAppUpdate, /* not exported */)
QT_DECL_METATYPE_EXTERN(QSpiDeviceEvent, /* not exported */)
QT_DECL_METATYPE_EXTERN(QSpiMatchRule, /* not exported */)
diff --git a/src/gui/accessible/linux/qspimatchrulematcher.cpp b/src/gui/accessible/linux/qspimatchrulematcher.cpp
index 48357f7ae63..a63bdd04443 100644
--- a/src/gui/accessible/linux/qspimatchrulematcher.cpp
+++ b/src/gui/accessible/linux/qspimatchrulematcher.cpp
@@ -37,7 +37,7 @@ QSpiMatchRuleMatcher::QSpiMatchRuleMatcher(const QSpiMatchRule &matchRule)
}
}
- // use qualified interface names to match what accessibleInterfaces() returns
+ // use qualified interface names to match what AtSpiAdaptor::accessibleInterfaces returns
m_interfaces.reserve(matchRule.interfaces.size());
for (const QString &ifaceName : matchRule.interfaces)
m_interfaces.push_back("org.a11y.atspi."_L1 + ifaceName);
diff --git a/src/gui/accessible/qaccessiblecache.cpp b/src/gui/accessible/qaccessiblecache.cpp
index a8255e04c02..311b53aeaa3 100644
--- a/src/gui/accessible/qaccessiblecache.cpp
+++ b/src/gui/accessible/qaccessiblecache.cpp
@@ -4,6 +4,7 @@
#include "qaccessiblecache_p.h"
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>
+#include <private/qguiapplication_p.h>
#if QT_CONFIG(accessibility)
@@ -176,10 +177,28 @@ void QAccessibleCache::sendObjectDestroyedEvent(QObject *obj)
void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
{
- QAccessibleInterface *iface = idToInterface.take(id);
+ const auto it = idToInterface.find(id);
+ if (it == idToInterface.end()) // the interface may be deleted already
+ return;
+
+ QAccessibleInterface *iface = *it;
qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
- if (!iface) // the interface may be deleted already
+ if (!iface) {
+ idToInterface.erase(it);
return;
+ }
+
+ // QObjects sends this from their destructor, but
+ // the object less interfaces calls deleteInterface
+ // directly
+ if (!obj && !iface->object()) {
+ if (QGuiApplicationPrivate::is_app_running && !QGuiApplicationPrivate::is_app_closing && QAccessible::isActive()) {
+ QAccessibleObjectDestroyedEvent event(id);
+ QAccessible::updateAccessibility(&event);
+ }
+ }
+
+ idToInterface.erase(it);
interfaceToId.take(iface);
if (!obj)
obj = iface->object();
diff --git a/src/gui/image/qabstractfileiconprovider.cpp b/src/gui/image/qabstractfileiconprovider.cpp
index 78777ec115a..ad646a6b89a 100644
--- a/src/gui/image/qabstractfileiconprovider.cpp
+++ b/src/gui/image/qabstractfileiconprovider.cpp
@@ -288,3 +288,5 @@ QString QAbstractFileIconProvider::type(const QFileInfo &info) const
}
QT_END_NAMESPACE
+
+#include "moc_qabstractfileiconprovider.cpp"
diff --git a/src/gui/kernel/qguiapplication_p.h b/src/gui/kernel/qguiapplication_p.h
index cb4702b5f7e..633ae7895ba 100644
--- a/src/gui/kernel/qguiapplication_p.h
+++ b/src/gui/kernel/qguiapplication_p.h
@@ -228,8 +228,8 @@ public:
// to use single-point precision.
friend constexpr bool operator==(const QLastCursorPosition &p1, const QPointF &p2) noexcept
{
- return qFuzzyCompare(float(p1.x()), float(p2.x()))
- && qFuzzyCompare(float(p1.y()), float(p2.y()));
+ return QtPrivate::fuzzyCompare(float(p1.x()), float(p2.x()))
+ && QtPrivate::fuzzyCompare(float(p1.y()), float(p2.y()));
}
friend constexpr bool operator!=(const QLastCursorPosition &p1, const QPointF &p2) noexcept
{
diff --git a/src/gui/math3d/qmatrix4x4.cpp b/src/gui/math3d/qmatrix4x4.cpp
index f6a06fd47ca..95b9524172f 100644
--- a/src/gui/math3d/qmatrix4x4.cpp
+++ b/src/gui/math3d/qmatrix4x4.cpp
@@ -738,22 +738,22 @@ QMatrix4x4 operator/(const QMatrix4x4& matrix, float divisor)
*/
bool qFuzzyCompare(const QMatrix4x4& m1, const QMatrix4x4& m2) noexcept
{
- return qFuzzyCompare(m1.m[0][0], m2.m[0][0]) &&
- qFuzzyCompare(m1.m[0][1], m2.m[0][1]) &&
- qFuzzyCompare(m1.m[0][2], m2.m[0][2]) &&
- qFuzzyCompare(m1.m[0][3], m2.m[0][3]) &&
- qFuzzyCompare(m1.m[1][0], m2.m[1][0]) &&
- qFuzzyCompare(m1.m[1][1], m2.m[1][1]) &&
- qFuzzyCompare(m1.m[1][2], m2.m[1][2]) &&
- qFuzzyCompare(m1.m[1][3], m2.m[1][3]) &&
- qFuzzyCompare(m1.m[2][0], m2.m[2][0]) &&
- qFuzzyCompare(m1.m[2][1], m2.m[2][1]) &&
- qFuzzyCompare(m1.m[2][2], m2.m[2][2]) &&
- qFuzzyCompare(m1.m[2][3], m2.m[2][3]) &&
- qFuzzyCompare(m1.m[3][0], m2.m[3][0]) &&
- qFuzzyCompare(m1.m[3][1], m2.m[3][1]) &&
- qFuzzyCompare(m1.m[3][2], m2.m[3][2]) &&
- qFuzzyCompare(m1.m[3][3], m2.m[3][3]);
+ return QtPrivate::fuzzyCompare(m1.m[0][0], m2.m[0][0])
+ && QtPrivate::fuzzyCompare(m1.m[0][1], m2.m[0][1])
+ && QtPrivate::fuzzyCompare(m1.m[0][2], m2.m[0][2])
+ && QtPrivate::fuzzyCompare(m1.m[0][3], m2.m[0][3])
+ && QtPrivate::fuzzyCompare(m1.m[1][0], m2.m[1][0])
+ && QtPrivate::fuzzyCompare(m1.m[1][1], m2.m[1][1])
+ && QtPrivate::fuzzyCompare(m1.m[1][2], m2.m[1][2])
+ && QtPrivate::fuzzyCompare(m1.m[1][3], m2.m[1][3])
+ && QtPrivate::fuzzyCompare(m1.m[2][0], m2.m[2][0])
+ && QtPrivate::fuzzyCompare(m1.m[2][1], m2.m[2][1])
+ && QtPrivate::fuzzyCompare(m1.m[2][2], m2.m[2][2])
+ && QtPrivate::fuzzyCompare(m1.m[2][3], m2.m[2][3])
+ && QtPrivate::fuzzyCompare(m1.m[3][0], m2.m[3][0])
+ && QtPrivate::fuzzyCompare(m1.m[3][1], m2.m[3][1])
+ && QtPrivate::fuzzyCompare(m1.m[3][2], m2.m[3][2])
+ && QtPrivate::fuzzyCompare(m1.m[3][3], m2.m[3][3]);
}
diff --git a/src/gui/math3d/qquaternion.cpp b/src/gui/math3d/qquaternion.cpp
index a675f59eb1f..57587322ea5 100644
--- a/src/gui/math3d/qquaternion.cpp
+++ b/src/gui/math3d/qquaternion.cpp
@@ -409,7 +409,7 @@ QQuaternion QQuaternion::fromAxisAndAngle
(float x, float y, float z, float angle)
{
float length = qHypot(x, y, z);
- if (!qFuzzyCompare(length, 1.0f) && !qFuzzyIsNull(length)) {
+ if (!qFuzzyIsNull(length) && !qFuzzyCompare(length, 1.0f)) {
x /= length;
y /= length;
z /= length;
diff --git a/src/gui/math3d/qquaternion.h b/src/gui/math3d/qquaternion.h
index a7b1d432df7..c92e7177199 100644
--- a/src/gui/math3d/qquaternion.h
+++ b/src/gui/math3d/qquaternion.h
@@ -305,10 +305,10 @@ constexpr QQuaternion operator/(const QQuaternion &quaternion, float divisor)
constexpr bool qFuzzyCompare(const QQuaternion &q1, const QQuaternion &q2) noexcept
{
- return qFuzzyCompare(q1.wp, q2.wp) &&
- qFuzzyCompare(q1.xp, q2.xp) &&
- qFuzzyCompare(q1.yp, q2.yp) &&
- qFuzzyCompare(q1.zp, q2.zp);
+ return QtPrivate::fuzzyCompare(q1.wp, q2.wp)
+ && QtPrivate::fuzzyCompare(q1.xp, q2.xp)
+ && QtPrivate::fuzzyCompare(q1.yp, q2.yp)
+ && QtPrivate::fuzzyCompare(q1.zp, q2.zp);
}
#if QT_GUI_INLINE_IMPL_SINCE(6, 11)
diff --git a/src/gui/math3d/qvectornd.cpp b/src/gui/math3d/qvectornd.cpp
index dcd7bdbcf80..ee070b2b5be 100644
--- a/src/gui/math3d/qvectornd.cpp
+++ b/src/gui/math3d/qvectornd.cpp
@@ -375,7 +375,8 @@ QT_BEGIN_NAMESPACE
*/
bool qFuzzyCompare(QVector2D v1, QVector2D v2) noexcept
{
- return qFuzzyCompare(v1.v[0], v2.v[0]) && qFuzzyCompare(v1.v[1], v2.v[1]);
+ return QtPrivate::fuzzyCompare(v1.v[0], v2.v[0])
+ && QtPrivate::fuzzyCompare(v1.v[1], v2.v[1]);
}
#ifndef QT_NO_VECTOR3D
@@ -467,7 +468,6 @@ QDataStream &operator>>(QDataStream &stream, QVector2D &vector)
float x, y;
stream >> x;
stream >> y;
- Q_ASSERT(qIsFinite(x) && qIsFinite(y));
vector.setX(x);
vector.setY(y);
return stream;
@@ -980,9 +980,9 @@ QVector3D QVector3D::unproject(const QMatrix4x4 &modelView, const QMatrix4x4 &pr
*/
bool qFuzzyCompare(QVector3D v1, QVector3D v2) noexcept
{
- return qFuzzyCompare(v1.v[0], v2.v[0]) &&
- qFuzzyCompare(v1.v[1], v2.v[1]) &&
- qFuzzyCompare(v1.v[2], v2.v[2]);
+ return QtPrivate::fuzzyCompare(v1.v[0], v2.v[0])
+ && QtPrivate::fuzzyCompare(v1.v[1], v2.v[1])
+ && QtPrivate::fuzzyCompare(v1.v[2], v2.v[2]);
}
#ifndef QT_NO_VECTOR2D
@@ -1098,7 +1098,6 @@ QDataStream &operator>>(QDataStream &stream, QVector3D &vector)
stream >> x;
stream >> y;
stream >> z;
- Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z));
vector.setX(x);
vector.setY(y);
vector.setZ(z);
@@ -1503,10 +1502,10 @@ QDataStream &operator>>(QDataStream &stream, QVector3D &vector)
*/
bool qFuzzyCompare(QVector4D v1, QVector4D v2) noexcept
{
- return qFuzzyCompare(v1.v[0], v2.v[0]) &&
- qFuzzyCompare(v1.v[1], v2.v[1]) &&
- qFuzzyCompare(v1.v[2], v2.v[2]) &&
- qFuzzyCompare(v1.v[3], v2.v[3]);
+ return QtPrivate::fuzzyCompare(v1.v[0], v2.v[0])
+ && QtPrivate::fuzzyCompare(v1.v[1], v2.v[1])
+ && QtPrivate::fuzzyCompare(v1.v[2], v2.v[2])
+ && QtPrivate::fuzzyCompare(v1.v[3], v2.v[3]);
}
#ifndef QT_NO_VECTOR2D
@@ -1627,7 +1626,6 @@ QDataStream &operator>>(QDataStream &stream, QVector4D &vector)
stream >> y;
stream >> z;
stream >> w;
- Q_ASSERT(qIsFinite(x) && qIsFinite(y) && qIsFinite(z) && qIsFinite(w));
vector.setX(x);
vector.setY(y);
vector.setZ(z);
diff --git a/src/gui/platform/unix/qxkbcommon.cpp b/src/gui/platform/unix/qxkbcommon.cpp
index ebac9f108e8..e755892cf36 100644
--- a/src/gui/platform/unix/qxkbcommon.cpp
+++ b/src/gui/platform/unix/qxkbcommon.cpp
@@ -328,6 +328,12 @@ static constexpr const auto KeyTbl = qMakeArray(
Xkb2Qt<XKB_KEY_XF86Option, Qt::Key_Option>,
Xkb2Qt<XKB_KEY_XF86Paste, Qt::Key_Paste>,
Xkb2Qt<XKB_KEY_XF86Phone, Qt::Key_Phone>,
+#ifdef XKB_KEY_XF86PickupPhone
+ Xkb2Qt<XKB_KEY_XF86PickupPhone, Qt::Key_Call>,
+#endif
+#ifdef XKB_KEY_XF86HangupPhone
+ Xkb2Qt<XKB_KEY_XF86HangupPhone, Qt::Key_Hangup>,
+#endif
Xkb2Qt<XKB_KEY_XF86Reply, Qt::Key_Reply>,
Xkb2Qt<XKB_KEY_XF86Reload, Qt::Key_Reload>,
Xkb2Qt<XKB_KEY_XF86RotateWindows, Qt::Key_RotateWindows>,
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index a511eb854cb..33e35ba6694 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -2159,6 +2159,18 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
}
#endif
+ // Add self-dependency to be able to add memory barriers for writes in graphics stages
+ VkSubpassDependency selfDependency;
+ VkPipelineStageFlags stageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
+ selfDependency.srcSubpass = 0;
+ selfDependency.dstSubpass = 0;
+ selfDependency.srcStageMask = stageMask;
+ selfDependency.dstStageMask = stageMask;
+ selfDependency.srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ selfDependency.dstAccessMask = selfDependency.srcAccessMask;
+ selfDependency.dependencyFlags = 0;
+ rpD->subpassDeps.append(selfDependency);
+
// rpD->subpassDeps stays empty: don't yet know the correct initial/final
// access and stage stuff for the implicit deps at this point, so leave it
// to the resource tracking and activateTextureRenderTarget() to generate
@@ -4864,6 +4876,17 @@ void QRhiVulkan::recordPrimaryCommandBuffer(QVkCommandBuffer *cbD)
cmd.args.beginRenderPass.useSecondaryCb ? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
: VK_SUBPASS_CONTENTS_INLINE);
break;
+ case QVkCommandBuffer::Command::MemoryBarrier: {
+ VkMemoryBarrier barrier;
+ barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+ barrier.pNext = nullptr;
+ barrier.dstAccessMask = cmd.args.memoryBarrier.dstAccessMask;
+ barrier.srcAccessMask = cmd.args.memoryBarrier.srcAccessMask;
+ df->vkCmdPipelineBarrier(cbD->cb, cmd.args.memoryBarrier.srcStageMask, cmd.args.memoryBarrier.dstStageMask, cmd.args.memoryBarrier.dependencyFlags,
+ 1, &barrier,
+ 0, VK_NULL_HANDLE,
+ 0, VK_NULL_HANDLE);
+ } break;
case QVkCommandBuffer::Command::EndRenderPass:
df->vkCmdEndRenderPass(cbD->cb);
break;
@@ -5702,6 +5725,9 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
QVkShaderResourceBindings *srbD = QRHI_RES(QVkShaderResourceBindings, srb);
auto &descSetBd(srbD->boundResourceData[currentFrameSlot]);
bool rewriteDescSet = false;
+ bool addWriteBarrier = false;
+ VkPipelineStageFlags writeBarrierSrcStageMask = 0;
+ VkPipelineStageFlags writeBarrierDstStageMask = 0;
// Do host writes and mark referenced shader resources as in-use.
// Also prepare to ensure the descriptor set we are going to bind refers to up-to-date Vk objects.
@@ -5789,9 +5815,22 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
access = QRhiPassResourceTracker::TexStorageStore;
else
access = QRhiPassResourceTracker::TexStorageLoadStore;
+
+ const auto stage = QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage);
+ const auto prevAccess = passResTracker.textures().find(texD);
+ if (prevAccess != passResTracker.textures().end()) {
+ const QRhiPassResourceTracker::Texture &tex = prevAccess->second;
+ if (tex.access == QRhiPassResourceTracker::TexStorageStore
+ || tex.access == QRhiPassResourceTracker::TexStorageLoadStore) {
+ addWriteBarrier = true;
+ writeBarrierDstStageMask |= toVkPipelineStage(stage);
+ writeBarrierSrcStageMask |= toVkPipelineStage(tex.stage);
+ }
+ }
+
trackedRegisterTexture(&passResTracker, texD,
access,
- QRhiPassResourceTracker::toPassTrackerTextureStage(b->stage));
+ stage);
if (texD->generation != bd.simage.generation || texD->m_id != bd.simage.id) {
rewriteDescSet = true;
@@ -5818,9 +5857,21 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
access = QRhiPassResourceTracker::BufStorageStore;
else
access = QRhiPassResourceTracker::BufStorageLoadStore;
+
+ const auto stage = QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage);
+ const auto prevAccess = passResTracker.buffers().find(bufD);
+ if (prevAccess != passResTracker.buffers().end()) {
+ const QRhiPassResourceTracker::Buffer &buf = prevAccess->second;
+ if (buf.access == QRhiPassResourceTracker::BufStorageStore
+ || buf.access == QRhiPassResourceTracker::BufStorageLoadStore) {
+ addWriteBarrier = true;
+ writeBarrierDstStageMask |= toVkPipelineStage(stage);
+ writeBarrierSrcStageMask |= toVkPipelineStage(buf.stage);
+ }
+ }
trackedRegisterBuffer(&passResTracker, bufD, bufD->m_type == QRhiBuffer::Dynamic ? currentFrameSlot : 0,
access,
- QRhiPassResourceTracker::toPassTrackerBufferStage(b->stage));
+ stage);
if (bufD->generation != bd.sbuf.generation || bufD->m_id != bd.sbuf.id) {
rewriteDescSet = true;
@@ -5835,6 +5886,28 @@ void QRhiVulkan::setShaderResources(QRhiCommandBuffer *cb, QRhiShaderResourceBin
}
}
+ if (addWriteBarrier) {
+ if (cbD->passUsesSecondaryCb) {
+ VkMemoryBarrier barrier;
+ barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
+ barrier.pNext = nullptr;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ barrier.srcAccessMask = barrier.dstAccessMask;
+ df->vkCmdPipelineBarrier(cbD->activeSecondaryCbStack.last(), writeBarrierSrcStageMask, writeBarrierDstStageMask, 0,
+ 1, &barrier,
+ 0, VK_NULL_HANDLE,
+ 0, VK_NULL_HANDLE);
+ } else {
+ QVkCommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QVkCommandBuffer::Command::MemoryBarrier;
+ cmd.args.memoryBarrier.dependencyFlags = 0;
+ cmd.args.memoryBarrier.dstStageMask = writeBarrierDstStageMask;
+ cmd.args.memoryBarrier.srcStageMask = writeBarrierSrcStageMask;
+ cmd.args.memoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT;
+ cmd.args.memoryBarrier.srcAccessMask = cmd.args.memoryBarrier.dstAccessMask;
+ }
+ }
+
// write descriptor sets, if needed
if (rewriteDescSet)
updateShaderResourceBindings(srb);
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index 1e9318513fd..21044545ad2 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -425,7 +425,8 @@ struct QVkCommandBuffer : public QRhiCommandBuffer
TransitionPassResources,
Dispatch,
ExecuteSecondary,
- SetShadingRate
+ SetShadingRate,
+ MemoryBarrier
};
Cmd cmd;
@@ -464,6 +465,13 @@ struct QVkCommandBuffer : public QRhiCommandBuffer
struct {
VkPipelineStageFlags srcStageMask;
VkPipelineStageFlags dstStageMask;
+ VkAccessFlags srcAccessMask;
+ VkAccessFlags dstAccessMask;
+ VkDependencyFlags dependencyFlags;
+ } memoryBarrier;
+ struct {
+ VkPipelineStageFlags srcStageMask;
+ VkPipelineStageFlags dstStageMask;
int count;
int index;
} bufferBarrier;
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index 2b2f2a27fcd..c144820fa24 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -2170,6 +2170,7 @@ QString QFont::key() const
\li Style strategy
\li Font style
\li Font features
+ \li Variable axes
\endlist
\sa fromString()
@@ -2195,12 +2196,12 @@ QString QFont::toString() const
QString::number((int)styleStrategy()) + comma +
styleName();
- QMap<Tag, quint32> sortedFeatures;
+ fontDescription += comma + QString::number(d->features.size());
for (const auto &[tag, value] : std::as_const(d->features).asKeyValueRange())
- sortedFeatures.insert(tag, value);
+ fontDescription += comma + QLatin1StringView{tag.toString()} + u'=' + QString::number(value);
- fontDescription += comma + QString::number(sortedFeatures.size());
- for (const auto &[tag, value] : std::as_const(sortedFeatures).asKeyValueRange())
+ fontDescription += comma + QString::number(d->request.variableAxisValues.size());
+ for (const auto &[tag, value] : std::as_const(d->request.variableAxisValues).asKeyValueRange())
fontDescription += comma + QLatin1StringView{tag.toString()} + u'=' + QString::number(value);
return fontDescription;
@@ -2216,7 +2217,7 @@ size_t qHash(const QFont &font, size_t seed) noexcept
return qHash(QFontPrivate::get(font)->request, seed);
}
-static std::optional<std::pair<QFont::Tag, quint32>> tagAndValueFromString(QStringView view)
+static std::optional<std::pair<QFont::Tag, quint32>> fontFeatureFromString(QStringView view)
{
const int separator = view.indexOf(u'=');
if (separator == -1)
@@ -2234,6 +2235,24 @@ static std::optional<std::pair<QFont::Tag, quint32>> tagAndValueFromString(QStri
return std::make_pair(*tag, value);
}
+static std::optional<std::pair<QFont::Tag, float>> variableAxisFromString(QStringView view)
+{
+ const int separator = view.indexOf(u'=');
+ if (separator == -1)
+ return std::nullopt;
+
+ const std::optional<QFont::Tag> tag = QFont::Tag::fromString(view.sliced(0, separator));
+ if (!tag)
+ return std::nullopt;
+
+ bool valueOk = false;
+ const float value = view.sliced(separator + 1).toFloat(&valueOk);
+ if (!valueOk)
+ return std::nullopt;
+
+ return std::make_pair(*tag, value);
+}
+
/*!
Sets this font to match the description \a descrip. The description
is a comma-separated list of the font attributes, as returned by
@@ -2246,8 +2265,7 @@ bool QFont::fromString(const QString &descrip)
const auto sr = QStringView(descrip).trimmed();
const auto l = sr.split(u',');
const int count = l.size();
- if (!count || (count > 2 && count < 9) || count == 9 ||
- l.first().isEmpty()) {
+ if (!count || (count > 2 && count < 10) || l.first().isEmpty()) {
qWarning("QFont::fromString: Invalid description '%s'",
descrip.isEmpty() ? "(empty)" : descrip.toLatin1().data());
return false;
@@ -2256,14 +2274,8 @@ bool QFont::fromString(const QString &descrip)
setFamily(l[0].toString());
if (count > 1 && l[1].toDouble() > 0.0)
setPointSizeF(l[1].toDouble());
- if (count == 9) {
- setStyleHint((StyleHint) l[2].toInt());
- setWeight(QFont::Weight(l[3].toInt()));
- setItalic(l[4].toInt());
- setUnderline(l[5].toInt());
- setStrikeOut(l[6].toInt());
- setFixedPitch(l[7].toInt());
- } else if (count >= 10) {
+
+ if (count >= 10) {
if (l[2].toInt() > 0)
setPixelSize(l[2].toInt());
setStyleHint((StyleHint) l[3].toInt());
@@ -2275,6 +2287,8 @@ bool QFont::fromString(const QString &descrip)
setUnderline(l[6].toInt());
setStrikeOut(l[7].toInt());
setFixedPitch(l[8].toInt());
+ if (!d->request.fixedPitch) // assume 'false' fixedPitch equals default
+ d->request.ignorePitch = true;
if (count >= 16) {
setCapitalization((Capitalization)l[10].toInt());
setLetterSpacing((SpacingType)l[11].toInt(), l[12].toDouble());
@@ -2291,19 +2305,33 @@ bool QFont::fromString(const QString &descrip)
d->request.styleName.clear();
clearFeatures();
- if (count >= 18) {
- const int featureCount = l[17].toInt();
- if (count >= featureCount + 18) {
- for (int i = 0; i < featureCount; ++i) {
- if (const auto feature = tagAndValueFromString(l[18 + i]))
- setFeature(feature->first, feature->second);
- }
- }
+ clearVariableAxes();
+
+ int position = 17;
+ if (position >= count)
+ return true;
+
+ const int featureCount = l[position++].toInt();
+ if (position + featureCount > count)
+ return true;
+
+ for (int i = 0; i < featureCount; ++i) {
+ if (const auto feature = fontFeatureFromString(l[position++]))
+ setFeature(feature->first, feature->second);
}
- }
- if (count >= 9 && !d->request.fixedPitch) // assume 'false' fixedPitch equals default
- d->request.ignorePitch = true;
+ if (position >= count)
+ return true;
+
+ const int variableAxisCount = l[position++].toInt();
+ if (position + variableAxisCount > count)
+ return true;
+
+ for (int i = 0; i < variableAxisCount; ++i) {
+ if (const auto axis = variableAxisFromString(l[position++]))
+ setVariableAxis(axis->first, axis->second);
+ }
+ }
return true;
}
diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h
index 27bc2a6a7cc..76ff29f6e91 100644
--- a/src/gui/text/qfont_p.h
+++ b/src/gui/text/qfont_p.h
@@ -192,7 +192,7 @@ public:
QFixed letterSpacing;
QFixed wordSpacing;
- QHash<QFont::Tag, quint32> features;
+ QMap<QFont::Tag, quint32> features;
mutable QFontPrivate *scFont;
QFont smallCapsFont() const { return QFont(smallCapsFontPrivate()); }
diff --git a/src/gui/text/qrawfont.cpp b/src/gui/text/qrawfont.cpp
index 7acc3c5218c..5bd9799ca7d 100644
--- a/src/gui/text/qrawfont.cpp
+++ b/src/gui/text/qrawfont.cpp
@@ -224,7 +224,7 @@ void QRawFont::loadFromData(const QByteArray &fontData,
\since 6.11
*/
-int QRawFont::glyphCount() const
+quint32 QRawFont::glyphCount() const
{
return d->isValid() ? d->fontEngine->glyphCount() : 0;
}
diff --git a/src/gui/text/qrawfont.h b/src/gui/text/qrawfont.h
index f13f04ebe37..a1522aa8048 100644
--- a/src/gui/text/qrawfont.h
+++ b/src/gui/text/qrawfont.h
@@ -55,7 +55,7 @@ public:
inline bool operator!=(const QRawFont &other) const
{ return !operator==(other); }
- int glyphCount() const;
+ quint32 glyphCount() const;
QString familyName() const;
QString styleName() const;
diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp
index 29fda652ef6..41d2d417133 100644
--- a/src/gui/text/qtextengine.cpp
+++ b/src/gui/text/qtextengine.cpp
@@ -1414,7 +1414,7 @@ void QTextEngine::shapeText(int item) const
#endif
bool letterSpacingIsAbsolute;
bool shapingEnabled = false;
- QHash<QFont::Tag, quint32> features;
+ QMap<QFont::Tag, quint32> features;
QFixed letterSpacing, wordSpacing;
#ifndef QT_NO_RAWFONT
if (useRawFont) {
@@ -1610,7 +1610,7 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
int stringBaseIndex, int stringLength, int itemLength,
QFontEngine *fontEngine, QSpan<uint> itemBoundaries,
bool kerningEnabled, bool hasLetterSpacing,
- const QHash<QFont::Tag, quint32> &fontFeatures) const
+ const QMap<QFont::Tag, quint32> &fontFeatures) const
{
uint glyphs_shaped = 0;
@@ -1746,7 +1746,7 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st
// fix up clusters so that the cluster indices will be monotonic
// and thus we never return out-of-order indices
- while (last_cluster++ < cluster && str_pos < item_length)
+ for (uint j = last_cluster; j < cluster && str_pos < item_length; ++j)
log_clusters[str_pos++] = last_glyph_pos;
last_glyph_pos = i + glyphs_shaped;
last_cluster = cluster;
diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h
index e513fd598ba..f27463f7728 100644
--- a/src/gui/text/qtextengine_p.h
+++ b/src/gui/text/qtextengine_p.h
@@ -628,7 +628,7 @@ private:
int stringLength, int itemLength, QFontEngine *fontEngine,
QSpan<uint> itemBoundaries, bool kerningEnabled,
bool hasLetterSpacing,
- const QHash<QFont::Tag, quint32> &features) const;
+ const QMap<QFont::Tag, quint32> &features) const;
#endif
int endOfLine(int lineNum);
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index cb304fc865c..2a15dca0635 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -148,6 +148,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http
access/qrestaccessmanager.cpp access/qrestaccessmanager.h access/qrestaccessmanager_p.h
access/qrestreply.cpp access/qrestreply.h access/qrestreply_p.h
access/qsocketabstraction_p.h
+ access/qtcpkeepaliveconfiguration_p.h
socket/qhttpsocketengine.cpp socket/qhttpsocketengine_p.h
)
diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h
index 2fde9e4c9d5..37e960b19dc 100644
--- a/src/network/access/qhttp2protocolhandler_p.h
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -93,7 +93,6 @@ private:
// Stream's lifecycle management:
QHttp2Stream *createNewStream(const HttpMessagePair &message, bool uploadDone = false);
void connectStream(const HttpMessagePair &message, QHttp2Stream *stream);
- quint32 popStreamToResume();
QHttp2Connection *h2Connection;
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index e0f5bfc2d64..1b8bfd5d72b 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -1485,6 +1485,18 @@ void QHttpNetworkConnection::setHttp2Parameters(const QHttp2Configuration &param
d->http2Parameters = params;
}
+QTcpKeepAliveConfiguration QHttpNetworkConnection::tcpKeepAliveParameters() const
+{
+ Q_D(const QHttpNetworkConnection);
+ return d->tcpKeepAliveConfiguration;
+}
+
+void QHttpNetworkConnection::setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config)
+{
+ Q_D(QHttpNetworkConnection);
+ d->tcpKeepAliveConfiguration = config;
+}
+
// SSL support below
#ifndef QT_NO_SSL
void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config)
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index 67b568caea8..f35b89d1aec 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -36,6 +36,7 @@
#include <private/http2protocol_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
+#include <private/qtcpkeepaliveconfiguration_p.h>
#include <utility>
@@ -98,6 +99,9 @@ public:
QHttp2Configuration http2Parameters() const;
void setHttp2Parameters(const QHttp2Configuration &params);
+ QTcpKeepAliveConfiguration tcpKeepAliveParameters() const;
+ void setTcpKeepAliveParameters(QTcpKeepAliveConfiguration config);
+
#ifndef QT_NO_SSL
void setSslConfiguration(const QSslConfiguration &config);
void ignoreSslErrors(int channel = -1);
@@ -255,6 +259,8 @@ public:
QString peerVerifyName;
+ QTcpKeepAliveConfiguration tcpKeepAliveConfiguration;
+
friend class QHttpNetworkConnectionChannel;
};
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index ffd5d8ff333..e427175ce61 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -923,9 +923,19 @@ void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket
// not sure yet if it helps, but it makes sense
absSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
- int kaIdleOption = qEnvironmentVariableIntegerValue(keepAliveIdleOption).value_or(TCP_KEEPIDLE_DEF);
- int kaIntervalOption = qEnvironmentVariableIntegerValue(keepAliveIntervalOption).value_or(TCP_KEEPINTVL_DEF);
- int kaCountOption = qEnvironmentVariableIntegerValue(keepAliveCountOption).value_or(TCP_KEEPCNT_DEF);
+ QTcpKeepAliveConfiguration keepAliveConfig = connection->tcpKeepAliveParameters();
+
+ auto getKeepAliveValue = [](int configValue,
+ const char* envName,
+ int defaultValue) {
+ if (configValue > 0)
+ return configValue;
+ return static_cast<int>(qEnvironmentVariableIntegerValue(envName).value_or(defaultValue));
+ };
+
+ int kaIdleOption = getKeepAliveValue(keepAliveConfig.idleTimeBeforeProbes.count(), keepAliveIdleOption, TCP_KEEPIDLE_DEF);
+ int kaIntervalOption = getKeepAliveValue(keepAliveConfig.intervalBetweenProbes.count(), keepAliveIntervalOption, TCP_KEEPINTVL_DEF);
+ int kaCountOption = getKeepAliveValue(keepAliveConfig.probeCount, keepAliveCountOption, TCP_KEEPCNT_DEF);
absSocket->setSocketOption(QAbstractSocket::KeepAliveIdleOption, kaIdleOption);
absSocket->setSocketOption(QAbstractSocket::KeepAliveIntervalOption, kaIntervalOption);
absSocket->setSocketOption(QAbstractSocket::KeepAliveCountOption, kaCountOption);
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index fbbc55dc4a4..82455e96a81 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -327,6 +327,8 @@ void QHttpThreadDelegate::startRequest()
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
httpConnection->setHttp2Parameters(http2Parameters);
}
+
+ httpConnection->setTcpKeepAliveParameters(tcpKeepAliveParameters);
#ifndef QT_NO_SSL
// Set the QSslConfiguration from this QNetworkRequest.
if (ssl)
diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
index 2ce64dc9a17..f179d95ac17 100644
--- a/src/network/access/qhttpthreaddelegate_p.h
+++ b/src/network/access/qhttpthreaddelegate_p.h
@@ -34,6 +34,7 @@
#include "qnetworkaccessauthenticationmanager_p.h"
#include <QtNetwork/private/http2protocol_p.h>
#include <QtNetwork/qhttpheaders.h>
+#include "qtcpkeepaliveconfiguration_p.h"
#ifndef QT_NO_SSL
#include <memory>
@@ -91,6 +92,7 @@ public:
QString incomingErrorDetail;
QHttp1Configuration http1Parameters;
QHttp2Configuration http2Parameters;
+ QTcpKeepAliveConfiguration tcpKeepAliveParameters;
protected:
// The zerocopy download buffer, if used:
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index f76d79571c3..9a27da00960 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -896,6 +896,9 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
// Propagate Http/2 settings:
delegate->http2Parameters = request.http2Configuration();
delegate->http1Parameters = request.http1Configuration();
+ delegate->tcpKeepAliveParameters.idleTimeBeforeProbes = request.tcpKeepAliveIdleTimeBeforeProbes();
+ delegate->tcpKeepAliveParameters.intervalBetweenProbes = request.tcpKeepAliveIntervalBetweenProbes();
+ delegate->tcpKeepAliveParameters.probeCount = request.tcpKeepAliveProbeCount();
if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid())
delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt();
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index 5047fc77bd5..d41124f7b14 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -475,6 +475,9 @@ public:
decompressedSafetyCheckThreshold = other.decompressedSafetyCheckThreshold;
#endif
transferTimeout = other.transferTimeout;
+ idleTimeBeforeProbes = other.idleTimeBeforeProbes;
+ intervalBetweenProbes = other.intervalBetweenProbes;
+ probeCount = other.probeCount;
}
inline bool operator==(const QNetworkRequestPrivate &other) const
@@ -491,6 +494,9 @@ public:
#endif
&& transferTimeout == other.transferTimeout
&& QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders)
+ && idleTimeBeforeProbes == other.idleTimeBeforeProbes
+ && intervalBetweenProbes == other.intervalBetweenProbes
+ && probeCount == other.probeCount;
;
// don't compare cookedHeaders
}
@@ -508,6 +514,9 @@ public:
qint64 decompressedSafetyCheckThreshold = 10ll * 1024ll * 1024ll;
#endif
std::chrono::milliseconds transferTimeout = 0ms;
+ std::chrono::duration<int> idleTimeBeforeProbes{0};
+ std::chrono::duration<int> intervalBetweenProbes{0};
+ int probeCount = 0;
};
/*!
@@ -1035,6 +1044,96 @@ void QNetworkRequest::setDecompressedSafetyCheckThreshold(qint64 threshold)
}
#endif // QT_CONFIG(http)
+/*!
+ \since 6.11
+
+ Returns the time the connection needs to remain idle before TCP
+ starts sending keepalive probes, if the TCP Keepalive functionality has
+ been turned on.
+
+ \sa setIdleTimeBeforeProbes
+*/
+
+std::chrono::seconds QNetworkRequest::tcpKeepAliveIdleTimeBeforeProbes() const
+{
+ return d->idleTimeBeforeProbes;
+}
+
+/*!
+ \fn void QNetworkRequest::setTcpKeepAliveIdleTimeBeforeProbes(std::chrono::seconds idle)
+ \since 6.11
+
+ Sets the time the connection needs to remain idle before TCP starts
+ sending keepalive probes to be \a idle, if the TCP Keepalive
+ functionality has been turned on.
+
+ \sa idleTimeBeforeProbes
+*/
+
+void QNetworkRequest::doSetIdleTimeBeforeProbes(std::chrono::duration<int> seconds) noexcept
+{
+ d->idleTimeBeforeProbes = seconds;
+}
+
+/*!
+ \since 6.11
+
+ Returns the time between individual keepalive probes, if the TCP
+ Keepalive functionality has been turned on.
+
+ \sa setIntervalBetweenProbes
+*/
+
+std::chrono::seconds QNetworkRequest::tcpKeepAliveIntervalBetweenProbes() const
+{
+ return d->intervalBetweenProbes;
+}
+
+/*!
+ \fn void QNetworkRequest::setTcpKeepAliveIntervalBetweenProbes(std::chrono::seconds interval)
+ \since 6.11
+
+ Sets the time between individual keepalive probes to be \a interval,
+ if the TCP Keepalive functionality has been turned on.
+
+ \sa intervalBetweenProbes
+*/
+
+void QNetworkRequest::doSetIntervalBetweenProbes(std::chrono::duration<int> seconds) noexcept
+{
+ d->intervalBetweenProbes = seconds;
+}
+
+/*!
+ \since 6.11
+
+ Returns the maximum number of keepalive probes TCP should send before
+ dropping the connection, if the TCP Keepalive functionality has been
+ turned on.
+
+ \sa setIntervalBetweenProbes
+*/
+
+int QNetworkRequest::tcpKeepAliveProbeCount() const
+{
+ return d->probeCount;
+}
+
+/*!
+ \since 6.11
+
+ Sets the maximum number of keepalive \a probes TCP should send
+ before dropping the connection, if the TCP Keepalive functionality has
+ been turned on.
+
+ \sa probeCount
+*/
+
+void QNetworkRequest::setTcpKeepAliveProbeCount(int probes) noexcept
+{
+ d->probeCount = probes;
+}
+
#if QT_CONFIG(http) || defined (Q_OS_WASM)
/*!
\fn int QNetworkRequest::transferTimeout() const
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index ea70255a718..49aa45233af 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -7,12 +7,15 @@
#include <QtNetwork/qtnetworkglobal.h>
#include <QtNetwork/qhttpheaders.h>
+
+#include <QtCore/qassert.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QVariant>
#include <QtCore/q26numeric.h>
+#include <QtCore/q20utility.h>
#include <chrono>
@@ -178,6 +181,22 @@ public:
qint64 decompressedSafetyCheckThreshold() const;
void setDecompressedSafetyCheckThreshold(qint64 threshold);
#endif // QT_CONFIG(http)
+ std::chrono::seconds tcpKeepAliveIdleTimeBeforeProbes() const;
+ void setTcpKeepAliveIdleTimeBeforeProbes(std::chrono::seconds idle)
+ {
+ const auto r = q26::saturate_cast<int>(idle.count());
+ Q_PRE(q20::cmp_equal(r, idle.count()));
+ doSetIdleTimeBeforeProbes(std::chrono::duration<int>(r));
+ }
+ std::chrono::seconds tcpKeepAliveIntervalBetweenProbes() const;
+ void setTcpKeepAliveIntervalBetweenProbes(std::chrono::seconds interval)
+ {
+ const auto r = q26::saturate_cast<int>(interval.count());
+ Q_PRE(q20::cmp_equal(r, interval.count()));
+ doSetIntervalBetweenProbes(std::chrono::duration<int>(r));
+ }
+ int tcpKeepAliveProbeCount() const;
+ void setTcpKeepAliveProbeCount(int probes) noexcept;
#if QT_CONFIG(http) || defined (Q_OS_WASM)
QT_NETWORK_INLINE_SINCE(6, 8)
@@ -189,6 +208,8 @@ public:
void setTransferTimeout(std::chrono::milliseconds duration = DefaultTransferTimeout);
#endif // QT_CONFIG(http) || defined (Q_OS_WASM)
private:
+ void doSetIdleTimeBeforeProbes(std::chrono::duration<int> idle) noexcept;
+ void doSetIntervalBetweenProbes(std::chrono::duration<int> interval) noexcept;
QSharedDataPointer<QNetworkRequestPrivate> d;
friend class QNetworkRequestPrivate;
};
diff --git a/src/network/access/qtcpkeepaliveconfiguration_p.h b/src/network/access/qtcpkeepaliveconfiguration_p.h
new file mode 100644
index 00000000000..b8bd96666ef
--- /dev/null
+++ b/src/network/access/qtcpkeepaliveconfiguration_p.h
@@ -0,0 +1,50 @@
+// 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
+// Qt-Security score:significant reason:default
+
+#ifndef QTCPKEEPALIVECONFIGURATION_P_H
+#define QTCPKEEPALIVECONFIGURATION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qglobal_p.h>
+#include <QtNetwork/qtnetworkglobal.h>
+
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+struct QTcpKeepAliveConfiguration
+{
+ std::chrono::duration<int> idleTimeBeforeProbes;
+ std::chrono::duration<int> intervalBetweenProbes;
+ int probeCount;
+
+ bool isEqual(const QTcpKeepAliveConfiguration &other) const noexcept
+ {
+ return idleTimeBeforeProbes == other.idleTimeBeforeProbes
+ && intervalBetweenProbes == other.intervalBetweenProbes
+ && probeCount == other.probeCount;
+ }
+
+ friend bool operator==(const QTcpKeepAliveConfiguration &lhs, const QTcpKeepAliveConfiguration &rhs) noexcept
+ { return lhs.isEqual(rhs); }
+ friend bool operator!=(const QTcpKeepAliveConfiguration &lhs, const QTcpKeepAliveConfiguration &rhs) noexcept
+ { return !lhs.isEqual(rhs); }
+
+};
+
+Q_DECLARE_TYPEINFO(QTcpKeepAliveConfiguration, Q_PRIMITIVE_TYPE);
+
+QT_END_NAMESPACE
+
+#endif // QTCPKEEPALIVECONFIGURATION_P_H
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp
index 975332a14ab..eb95d891e42 100644
--- a/src/network/socket/qabstractsocket.cpp
+++ b/src/network/socket/qabstractsocket.cpp
@@ -1045,7 +1045,7 @@ void QAbstractSocketPrivate::_q_connectToNextAddress()
host = addresses.takeFirst();
#if defined(QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocketPrivate::_q_connectToNextAddress(), connecting to %s:%i, %d left to try",
- host.toString().toLatin1().constData(), port, addresses.count());
+ host.toString().toLatin1().constData(), port, int(addresses.count()));
#endif
if (cachedSocketDescriptor == -1 && !initSocketLayer(host.protocol())) {
@@ -1247,6 +1247,9 @@ void QAbstractSocketPrivate::emitReadyRead(int channel)
void QAbstractSocketPrivate::emitBytesWritten(qint64 bytes, int channel)
{
Q_Q(QAbstractSocket);
+
+ bytesWrittenEmissionCount++;
+
// Only emit bytesWritten() when not recursing.
if (!emittedBytesWritten && channel == currentWriteChannel) {
QScopedValueRollback<bool> r(emittedBytesWritten);
@@ -2265,6 +2268,8 @@ bool QAbstractSocket::waitForBytesWritten(int msecs)
if (d->writeBuffer.isEmpty())
return false;
+ const quint32 bwEmissionCountAtEntry = d->bytesWrittenEmissionCount;
+
QDeadlineTimer deadline{msecs};
// handle a socket in connecting state
@@ -2304,6 +2309,13 @@ bool QAbstractSocket::waitForBytesWritten(int msecs)
qDebug("QAbstractSocket::waitForBytesWritten returns true");
#endif
return true;
+ } else if (d->bytesWrittenEmissionCount != bwEmissionCountAtEntry) {
+ // A slot connected to any signal emitted by this method has written data, which
+ // fulfills the condition to return true that at least one byte has been written.
+#if defined (QABSTRACTSOCKET_DEBUG)
+ qDebug("QAbstractSocket::waitForBytesWritten returns true (write in signal handler)");
+#endif
+ return true;
}
}
diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h
index 5f33eddbc7b..5a0a6489e2e 100644
--- a/src/network/socket/qabstractsocket_p.h
+++ b/src/network/socket/qabstractsocket_p.h
@@ -117,6 +117,8 @@ public:
bool hasPendingData = false;
bool hasPendingDatagram = false;
+ quint32 bytesWrittenEmissionCount = 0;
+
QTimer *connectTimer = nullptr;
int hostLookupId = -1;
diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm
index 64c806a087b..b3c22ff051e 100644
--- a/src/plugins/platforms/cocoa/qnsview_drawing.mm
+++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm
@@ -183,6 +183,9 @@
{
qCDebug(lcQpaDrawing) << "Backing properties changed for" << self;
+ if (!m_platformWindow)
+ return;
+
[self propagateBackingProperties];
// Ideally we would plumb this situation through QPA in a way that lets
diff --git a/src/plugins/platforms/directfb/qdirectfbconvenience.cpp b/src/plugins/platforms/directfb/qdirectfbconvenience.cpp
index 881a233e694..5b86c1e1725 100644
--- a/src/plugins/platforms/directfb/qdirectfbconvenience.cpp
+++ b/src/plugins/platforms/directfb/qdirectfbconvenience.cpp
@@ -254,6 +254,7 @@ QDirectFbKeyMap::QDirectFbKeyMap()
insert(DIKS_FAVORITES , Qt::Key_Favorites);
insert(DIKS_KEYBOARD , Qt::Key_Keyboard);
insert(DIKS_PHONE , Qt::Key_Phone);
+ insert(DIKS_CALL , Qt::Key_Call)
insert(DIKS_PROGRAM , Qt::Key_Guide);
insert(DIKS_TIME , Qt::Key_Time);
diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp
index 730816b9a99..757959e5694 100644
--- a/src/plugins/platforms/wasm/qwasmdrag.cpp
+++ b/src/plugins/platforms/wasm/qwasmdrag.cpp
@@ -16,6 +16,9 @@
#include <QtCore/qtimer.h>
#include <QFile>
+#include <private/qshapedpixmapdndwindow_p.h>
+#include <private/qdnd_p.h>
+
#include <functional>
#include <string>
#include <utility>
@@ -92,9 +95,8 @@ Qt::DropAction QWasmDrag::drag(QDrag *drag)
Qt::DropAction dragResult = Qt::IgnoreAction;
if (qstdweb::haveJspi()) {
- QEventLoop loop;
- m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); });
- loop.exec();
+ m_dragState = std::make_unique<DragState>(drag, window, [this]() { QSimpleDrag::cancelDrag(); });
+ QSimpleDrag::drag(drag);
dragResult = m_dragState->dropAction;
m_dragState.reset();
}
@@ -110,14 +112,16 @@ void QWasmDrag::onNativeDragStarted(DragEvent *event)
Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
"The event is not a DragStart event");
- event->webEvent.call<void>("preventDefault");
-
// It is possible for a drag start event to arrive from another window.
if (!m_dragState || m_dragState->window != event->targetWindow) {
event->cancelDragStart();
return;
}
+ // We have our own window
+ if (shapedPixmapWindow())
+ shapedPixmapWindow()->setVisible(false);
+
m_dragState->dragImage = std::make_unique<DragState::DragImage>(
m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow);
event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
@@ -170,19 +174,21 @@ void QWasmDrag::onNativeDrop(DragEvent *event)
// files, but the browser expects that accepted state is set before any
// async calls.
event->acceptDrop();
+ std::shared_ptr<DragState> dragState = m_dragState;
- const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos,
+ const auto dropCallback = [dragState, wasmWindow, targetWindowPos,
actions, mouseButton, modifiers](QMimeData *mimeData) {
-
- auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
- *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
+ if (mimeData) {
+ auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
+ *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
targetWindowPos, actions,
mouseButton, modifiers);
- if (dropResponse->isAccepted())
- m_dragState->dropAction = dropResponse->acceptedAction();
+ if (dragState && dropResponse->isAccepted())
+ dragState->dropAction = dropResponse->acceptedAction();
- delete mimeData;
+ delete mimeData;
+ }
};
event->dataTransfer.toMimeDataWithFile(dropCallback);
@@ -195,10 +201,28 @@ void QWasmDrag::onNativeDragFinished(DragEvent *event)
m_dragState->quitEventLoopClosure();
}
+void QWasmDrag::onNativeDragEnter(DragEvent *event)
+{
+ event->webEvent.call<void>("preventDefault");
+
+ // Already dragging
+ if (QDragManager::self() && QDragManager::self()->object())
+ return;
+
+ // Event coming from external browser, start a drag
+ if (m_dragState)
+ m_dragState->dropAction = event->dropAction;
+
+ QDrag *drag = new QDrag(this);
+ drag->setMimeData(new QMimeData());
+ drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
+}
+
void QWasmDrag::onNativeDragLeave(DragEvent *event)
{
event->webEvent.call<void>("preventDefault");
- m_dragState->dropAction = event->dropAction;
+ if (m_dragState)
+ m_dragState->dropAction = event->dropAction;
event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
}
diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h
index e821470c913..5bb8ec66a3c 100644
--- a/src/plugins/platforms/wasm/qwasmdrag.h
+++ b/src/plugins/platforms/wasm/qwasmdrag.h
@@ -32,6 +32,7 @@ public:
void onNativeDrop(DragEvent *event);
void onNativeDragStarted(DragEvent *event);
void onNativeDragFinished(DragEvent *event);
+ void onNativeDragEnter(DragEvent *event);
void onNativeDragLeave(DragEvent *event);
// QPlatformDrag:
@@ -40,7 +41,7 @@ public:
private:
struct DragState;
- std::unique_ptr<DragState> m_dragState;
+ std::shared_ptr<DragState> m_dragState;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp
index e6d5a20a24d..676d9c8e155 100644
--- a/src/plugins/platforms/wasm/qwasmevent.cpp
+++ b/src/plugins/platforms/wasm/qwasmevent.cpp
@@ -112,7 +112,7 @@ bool Event::isTargetedForQtElement() const
return topElementClassName.startsWith("qt-"); // .e.g. qt-window-canvas
}
-KeyEvent::KeyEvent(EventType type, emscripten::val event, QWasmDeadKeySupport *deadKeySupport) : Event(type, event)
+KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event)
{
const auto code = event["code"].as<std::string>();
const auto webKey = event["key"].as<std::string>();
@@ -137,8 +137,6 @@ KeyEvent::KeyEvent(EventType type, emscripten::val event, QWasmDeadKeySupport *d
if (key == Qt::Key_Tab)
text = "\t";
-
- deadKeySupport->applyDeadKeyTranslations(this);
}
MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event)
diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h
index 9b6f11fd5da..07faee3fe4b 100644
--- a/src/plugins/platforms/wasm/qwasmevent.h
+++ b/src/plugins/platforms/wasm/qwasmevent.h
@@ -17,14 +17,13 @@
#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
-
-class QWasmDeadKeySupport;
class QWindow;
enum class EventType {
DragEnd,
DragOver,
DragStart,
+ DragEnter,
DragLeave,
Drop,
KeyDown,
@@ -65,7 +64,7 @@ struct Event
struct KeyEvent : public Event
{
- KeyEvent(EventType type, emscripten::val webEvent, QWasmDeadKeySupport *deadKeySupport);
+ KeyEvent(EventType type, emscripten::val webEvent);
Qt::Key key;
QFlags<Qt::KeyboardModifier> modifiers;
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index b56c57974d1..7417f316169 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -200,7 +200,7 @@ QWasmWindow *QWasmIntegration::createWindow(QWindow *window, WId nativeHandle) c
{
auto *wasmScreen = QWasmScreen::get(window->screen());
QWasmCompositor *compositor = wasmScreen->compositor();
- return new QWasmWindow(window, wasmScreen->deadKeySupport(), compositor,
+ return new QWasmWindow(window, compositor,
m_backingStores.value(window), nativeHandle);
}
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
index 8f5240d2d0c..90a4ee807fb 100644
--- a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
@@ -250,46 +250,4 @@ std::optional<Qt::Key> QWasmKeyTranslator::mapWebKeyTextToQtKey(const char *toFi
: std::optional<Qt::Key>();
}
-QWasmDeadKeySupport::QWasmDeadKeySupport() = default;
-
-QWasmDeadKeySupport::~QWasmDeadKeySupport() = default;
-
-void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event)
-{
- if (event->deadKey) {
- m_activeDeadKey = event->key;
- } else if (m_activeDeadKey != Qt::Key_unknown
- && (((m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
- && event->type == EventType::KeyDown))
- || (m_keyModifiedByDeadKeyOnPress == event->key
- && event->type == EventType::KeyUp))) {
- const Qt::Key baseKey = event->key;
- const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_activeDeadKey);
- if (translatedKey != Qt::Key_unknown) {
- event->key = translatedKey;
-
- auto foundText = event->modifiers.testFlag(Qt::ShiftModifier)
- ? findKeyTextByKeyId(DiacriticalCharsKeyToTextUppercase, event->key)
- : findKeyTextByKeyId(DiacriticalCharsKeyToTextLowercase, event->key);
- Q_ASSERT(foundText.has_value());
- event->text = foundText->size() == 1 ? *foundText : QString();
- }
-
- if (!event->text.isEmpty()) {
- if (event->type == EventType::KeyDown) {
- // Assume the first keypress with an active dead key is treated as modified,
- // regardless of whether it has actually been modified or not. Take into account
- // only events that produce actual key text.
- if (!event->text.isEmpty())
- m_keyModifiedByDeadKeyOnPress = baseKey;
- } else {
- Q_ASSERT(event->type == EventType::KeyUp);
- Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
- m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
- m_activeDeadKey = Qt::Key_unknown;
- }
- }
- }
-}
-
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.h b/src/plugins/platforms/wasm/qwasmkeytranslator.h
index 11a89e61930..3e18bcb8802 100644
--- a/src/plugins/platforms/wasm/qwasmkeytranslator.h
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.h
@@ -17,18 +17,5 @@ namespace QWasmKeyTranslator {
std::optional<Qt::Key> mapWebKeyTextToQtKey(const char *toFind);
}
-class QWasmDeadKeySupport
-{
-public:
- explicit QWasmDeadKeySupport();
- ~QWasmDeadKeySupport();
-
- void applyDeadKeyTranslations(KeyEvent *event);
-
-private:
- Qt::Key m_activeDeadKey = Qt::Key_unknown;
- Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
-};
-
QT_END_NAMESPACE
#endif // QWASMKEYTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index a2c8306b13b..bbfc71edc54 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -30,8 +30,7 @@ QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
: m_container(containerOrCanvas),
m_intermediateContainer(emscripten::val::undefined()),
m_shadowContainer(emscripten::val::undefined()),
- m_compositor(new QWasmCompositor(this)),
- m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
+ m_compositor(new QWasmCompositor(this))
{
auto document = m_container["ownerDocument"];
diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h
index a19818af2ff..6ddd4c736d0 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.h
+++ b/src/plugins/platforms/wasm/qwasmscreen.h
@@ -22,7 +22,6 @@ class QPlatformOpenGLContext;
class QWasmWindow;
class QWasmBackingStore;
class QWasmCompositor;
-class QWasmDeadKeySupport;
class QOpenGLContext;
class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode<>
@@ -41,7 +40,6 @@ public:
QPointingDevice *tabletDevice() { return m_tabletDevice.get(); }
QWasmCompositor *compositor();
- QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); }
QList<QWasmWindow *> allWindows() const;
@@ -83,7 +81,6 @@ private:
std::unique_ptr<QWasmCompositor> m_compositor;
std::unique_ptr<QPointingDevice> m_touchDevice;
std::unique_ptr<QPointingDevice> m_tabletDevice;
- std::unique_ptr<QWasmDeadKeySupport> m_deadKeySupport;
QRect m_geometry = QRect(0, 0, 100, 100);
int m_depth = 32;
QImage::Format m_format = QImage::Format_RGB32;
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index d318c977a90..6e8bd46ca58 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -40,13 +40,12 @@ QT_BEGIN_NAMESPACE
Q_GUI_EXPORT int qt_defaultDpiX();
-QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
+QWasmWindow::QWasmWindow(QWindow *w,
QWasmCompositor *compositor, QWasmBackingStore *backingStore,
WId nativeHandle)
: QPlatformWindow(w),
m_compositor(compositor),
m_backingStore(backingStore),
- m_deadKeySupport(deadKeySupport),
m_document(dom::document()),
m_decoratedWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
m_window(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
@@ -204,6 +203,12 @@ void QWasmWindow::registerEventHandlers()
QWasmDrag::instance()->onNativeDragFinished(&dragEvent);
}
);
+ m_dragEnterCallback = QWasmEventHandler(m_window, "dragenter",
+ [this](emscripten::val event) {
+ DragEvent dragEvent(EventType::DragEnter, event, window());
+ QWasmDrag::instance()->onNativeDragEnter(&dragEvent);
+ }
+ );
m_dragLeaveCallback = QWasmEventHandler(m_window, "dragleave",
[this](emscripten::val event) {
DragEvent dragEvent(EventType::DragLeave, event, window());
@@ -216,9 +221,9 @@ void QWasmWindow::registerEventHandlers()
[this](emscripten::val event) { this->handleWheelEvent(event); });
m_keyDownCallback = QWasmEventHandler(m_window, "keydown",
- [this](emscripten::val event) { this->handleKeyEvent(KeyEvent(EventType::KeyDown, event, m_deadKeySupport)); });
+ [this](emscripten::val event) { this->handleKeyEvent(KeyEvent(EventType::KeyDown, event)); });
m_keyUpCallback =QWasmEventHandler(m_window, "keyup",
- [this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyUp, event, m_deadKeySupport)); });
+ [this](emscripten::val event) {this->handleKeyEvent(KeyEvent(EventType::KeyUp, event)); });
m_inputCallback = QWasmEventHandler(m_window, "input",
[this](emscripten::val event){ handleInputEvent(event); });
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index 8e6e5021dcf..ca5c9132ca0 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -31,7 +31,6 @@ class EventCallback;
struct KeyEvent;
struct PointerEvent;
-class QWasmDeadKeySupport;
struct WheelEvent;
Q_DECLARE_LOGGING_CATEGORY(qLcQpaWasmInputContext)
@@ -41,7 +40,7 @@ class QWasmWindow final : public QPlatformWindow,
public QNativeInterface::Private::QWasmWindow
{
public:
- QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor,
+ QWasmWindow(QWindow *w, QWasmCompositor *compositor,
QWasmBackingStore *backingStore, WId nativeHandle);
~QWasmWindow() final;
@@ -159,7 +158,6 @@ private:
QWasmCompositor *m_compositor = nullptr;
QWasmBackingStore *m_backingStore = nullptr;
- QWasmDeadKeySupport *m_deadKeySupport;
QRect m_normalGeometry {0, 0, 0 ,0};
emscripten::val m_document;
@@ -197,6 +195,7 @@ private:
QWasmEventHandler m_dragStartCallback;
QWasmEventHandler m_dragEndCallback;
QWasmEventHandler m_dropCallback;
+ QWasmEventHandler m_dragEnterCallback;
QWasmEventHandler m_dragLeaveCallback;
QWasmEventHandler m_wheelEventCallback;
diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp
index f27943070d0..2be05625971 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow.cpp
+++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp
@@ -513,7 +513,6 @@ void QWaylandWindow::setGeometry(const QRect &r)
mWindowDecoration->update();
QWindowSystemInterface::handleGeometryChange<QWindowSystemInterface::SynchronousDelivery>(window(), geometry());
- mSentInitialResize = true;
}
// Wayland has no concept of areas being exposed or not, only the entire window, when our geometry changes, we need to flag the new area as exposed
diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h
index 9e1bd92af30..7dda16cc776 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow_p.h
+++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h
@@ -334,7 +334,6 @@ protected:
int mFrameCallbackTimeout = 100;
QVariantMap m_properties;
- bool mSentInitialResize = false;
QPoint mOffset;
std::optional<qreal> mScale = std::nullopt;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
index db3fb160593..e2f181aa628 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
@@ -82,9 +82,12 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
if (event->changedStates().checked || event->changedStates().checkStateMixed) {
- // Notifies states changes in checkboxes and switches.
+ // Notifies states changes in checkboxes, switches, and checkable item view items.
if (accessible->role() == QAccessible::CheckBox
- || accessible->role() == QAccessible::Switch) {
+ || accessible->role() == QAccessible::Switch
+ || accessible->role() == QAccessible::Cell
+ || accessible->role() == QAccessible::ListItem
+ || accessible->role() == QAccessible::TreeItem) {
if (auto provider = providerForAccessible(accessible)) {
long toggleState = ToggleState_Off;
if (accessible->state().checked)
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index b4c12ed1a0c..d8e41a753ef 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -856,7 +856,7 @@ void QXcbDrag::handle_xdnd_status(const xcb_client_message_event_t *event)
if (event->data.data32[0] && event->data.data32[0] != current_target)
return;
- const bool dropPossible = event->data.data32[1];
+ const bool dropPossible = event->data.data32[1] & 1;
setCanDrop(dropPossible);
if (dropPossible) {
diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp
index 13682256370..7caa352afe4 100644
--- a/src/plugins/styles/modernwindows/qwindows11style.cpp
+++ b/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -87,34 +87,35 @@ inline ControlState calcControlState(const QStyleOption *option)
} // namespace StyleOptionHelper
-#define AcceptMedium u"\uF78C"_s
-// QStringLiteral(u"\uE73C")
-#define Dash12 u"\uE629"_s
-#define CheckMark u"\uE73E"_s
-
-#define CaretLeftSolid8 u"\uEDD9"_s
-#define CaretRightSolid8 u"\uEDDA"_s
-#define CaretUpSolid8 u"\uEDDB"_s
-#define CaretDownSolid8 u"\uEDDC"_s
-
-#define ChevronDown u"\uE70D"_s
-#define ChevronUp u"\uE70E"_s
-
-#define ChevronDownMed u"\uE972"_s
-#define ChevronLeftMed u"\uE973"_s
-#define ChevronRightMed u"\uE974"_s
-
-#define ChevronUpSmall u"\uE96D"_s
-#define ChevronDownSmall u"\uE96E"_s
-
-#define ChromeMinimize u"\uE921"_s
-#define ChromeMaximize u"\uE922"_s
-#define ChromeRestore u"\uE923"_s
-#define ChromeClose u"\uE8BB"_s
+enum class Icon : ushort
+{
+ AcceptMedium = 0xF78C,
+ Dash12 = 0xE629,
+ CheckMark = 0xE73E,
+ CaretLeftSolid8 = 0xEDD9,
+ CaretRightSolid8 = 0xEDDA,
+ CaretUpSolid8 = 0xEDDB,
+ CaretDownSolid8 = 0xEDDC,
+ ChevronDown = 0xE70D,
+ ChevronUp = 0xE70E,
+ ChevronDownMed = 0xE972,
+ ChevronLeftMed = 0xE973,
+ ChevronRightMed = 0xE974,
+ ChevronUpSmall = 0xE96D,
+ ChevronDownSmall = 0xE96E,
+ ChromeMinimize = 0xE921,
+ ChromeMaximize = 0xE922,
+ ChromeRestore = 0xE923,
+ ChromeClose = 0xE8BB,
+ More = 0xE712,
+ Help = 0xE897,
+ Clear = 0xE894,
+};
-#define More u"\uE712"_s
-#define Help u"\uE897"_s
-#define Clear u"\uE894"_s
+static inline QString fluentIcon(Icon i)
+{
+ return QChar(ushort(i));
+}
template <typename R, typename P, typename B>
static inline void drawRoundedRect(QPainter *p, R &&rect, P &&pen, B &&brush)
@@ -316,7 +317,7 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
{
QWindows11StylePrivate *d = const_cast<QWindows11StylePrivate*>(d_func());
- const auto drawTitleBarButton = [&](ComplexControl control, SubControl sc, const QString &str) {
+ const auto drawTitleBarButton = [&](ComplexControl control, SubControl sc, Icon ico) {
using namespace StyleOptionHelper;
const QRect buttonRect = proxy()->subControlRect(control, option, sc, widget);
if (buttonRect.isValid()) {
@@ -324,10 +325,10 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
if (hover)
painter->fillRect(buttonRect, winUI3Color(subtleHighlightColor));
painter->setPen(option->palette.color(QPalette::WindowText));
- painter->drawText(buttonRect, Qt::AlignCenter, str);
+ painter->drawText(buttonRect, Qt::AlignCenter, fluentIcon(ico));
}
};
- const auto drawTitleBarCloseButton = [&](ComplexControl control, SubControl sc, const QString &str) {
+ const auto drawTitleBarCloseButton = [&](ComplexControl control, SubControl sc, Icon ico) {
using namespace StyleOptionHelper;
const QRect buttonRect = proxy()->subControlRect(control, option, sc, widget);
if (buttonRect.isValid()) {
@@ -349,7 +350,7 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
break;
}
painter->setPen(pen);
- painter->drawText(buttonRect, Qt::AlignCenter, str);
+ painter->drawText(buttonRect, Qt::AlignCenter, fluentIcon(ico));
}
};
@@ -416,18 +417,13 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
sb, sb->rect.size());
if (cp.needsPainting()) {
const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
- drawRoundedRect(cp.painter(), frameRect, Qt::NoPen, option->palette.brush(QPalette::Base));
+ drawRoundedRect(cp.painter(), frameRect, Qt::NoPen, inputFillBrush(option, widget));
if (sb->frame && (sub & SC_SpinBoxFrame))
drawLineEditFrame(cp.painter(), frameRect, option);
- const bool isMouseOver = state & State_MouseOver;
- const bool hasFocus = state & State_HasFocus;
- const bool isEnabled = state & QStyle::State_Enabled;
- if (isEnabled && isMouseOver && !hasFocus && !highContrastTheme)
- drawRoundedRect(cp.painter(), frameRect, Qt::NoPen, winUI3Color(subtleHighlightColor));
-
const auto drawUpDown = [&](QStyle::SubControl sc) {
+ const bool isEnabled = state & QStyle::State_Enabled;
const bool isUp = sc == SC_SpinBoxUp;
const QRect rect = proxy()->subControlRect(CC_SpinBox, option, sc, widget);
if (isEnabled && sb->activeSubControls & sc)
@@ -437,7 +433,7 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
cp->setFont(d->assetFont);
cp->setPen(sb->palette.buttonText().color());
cp->setBrush(Qt::NoBrush);
- cp->drawText(rect, Qt::AlignCenter, isUp ? ChevronUp : ChevronDown);
+ cp->drawText(rect, Qt::AlignCenter, fluentIcon(isUp ? Icon::ChevronUp : Icon::ChevronDown));
};
if (sub & SC_SpinBoxUp) drawUpDown(SC_SpinBoxUp);
if (sub & SC_SpinBoxDown) drawUpDown(SC_SpinBoxDown);
@@ -586,21 +582,22 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
case CC_ComboBox:
if (const QStyleOptionComboBox *combobox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
- drawRoundedRect(painter, frameRect, Qt::NoPen, option->palette.brush(QPalette::Base));
+ QStyleOption opt(*option);
+ opt.state.setFlag(QStyle::State_On, false);
+ drawRoundedRect(painter, frameRect, Qt::NoPen,
+ combobox->editable ? inputFillBrush(option, widget)
+ : controlFillBrush(&opt, ControlType::Control));
if (combobox->frame)
drawLineEditFrame(painter, frameRect, combobox, combobox->editable);
const bool hasFocus = state & State_HasFocus;
- 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);
+ QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
painter->setFont(d->assetFont);
painter->setPen(controlTextColor(option));
- painter->drawText(rect, Qt::AlignCenter, ChevronDownMed);
+ painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
}
if (state & State_KeyboardFocusChange && hasFocus) {
QStyleOptionFocusRect fropt;
@@ -662,9 +659,9 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
f.setPointSize(6);
cp->setFont(f);
cp->setPen(Qt::gray);
- const auto str = vertical ? CaretDownSolid8
- : (isRtl ? CaretLeftSolid8 : CaretRightSolid8);
- cp->drawText(rect, Qt::AlignCenter, str);
+ const auto ico = vertical ? Icon::CaretDownSolid8
+ : (isRtl ? Icon::CaretLeftSolid8 : Icon::CaretRightSolid8);
+ cp->drawText(rect, Qt::AlignCenter, fluentIcon(ico));
}
}
if (sub & SC_ScrollBarSubLine) {
@@ -674,9 +671,9 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
f.setPointSize(6);
cp->setFont(f);
cp->setPen(Qt::gray);
- const auto str = vertical ? CaretUpSolid8
- : (isRtl ? CaretRightSolid8 : CaretLeftSolid8);
- cp->drawText(rect, Qt::AlignCenter, str);
+ const auto ico = vertical ? Icon::CaretUpSolid8
+ : (isRtl ? Icon::CaretRightSolid8 : Icon::CaretLeftSolid8);
+ cp->drawText(rect, Qt::AlignCenter, fluentIcon(ico));
}
}
}
@@ -686,9 +683,9 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
QFont buttonFont = QFont(d->assetFont);
buttonFont.setPointSize(8);
painter->setFont(buttonFont);
- drawTitleBarCloseButton(CC_MdiControls, SC_MdiCloseButton, ChromeClose);
- drawTitleBarButton(CC_MdiControls, SC_MdiNormalButton, ChromeRestore);
- drawTitleBarButton(CC_MdiControls, SC_MdiMinButton, ChromeMinimize);
+ drawTitleBarCloseButton(CC_MdiControls, SC_MdiCloseButton, Icon::ChromeClose);
+ drawTitleBarButton(CC_MdiControls, SC_MdiNormalButton, Icon::ChromeRestore);
+ drawTitleBarButton(CC_MdiControls, SC_MdiMinButton, Icon::ChromeMinimize);
}
break;
case CC_TitleBar:
@@ -716,18 +713,18 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
// min button
if (shouldDrawButton(SC_TitleBarMinButton, Qt::WindowMinimizeButtonHint) &&
!(titlebar->titleBarState & Qt::WindowMinimized)) {
- drawTitleBarButton(CC_TitleBar, SC_TitleBarMinButton, ChromeMinimize);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarMinButton, Icon::ChromeMinimize);
}
// max button
if (shouldDrawButton(SC_TitleBarMaxButton, Qt::WindowMaximizeButtonHint) &&
!(titlebar->titleBarState & Qt::WindowMaximized)) {
- drawTitleBarButton(CC_TitleBar, SC_TitleBarMaxButton, ChromeMaximize);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarMaxButton, Icon::ChromeMaximize);
}
// close button
if (shouldDrawButton(SC_TitleBarCloseButton, Qt::WindowSystemMenuHint))
- drawTitleBarCloseButton(CC_TitleBar, SC_TitleBarCloseButton, ChromeClose);
+ drawTitleBarCloseButton(CC_TitleBar, SC_TitleBarCloseButton, Icon::ChromeClose);
// normalize button
if ((titlebar->subControls & SC_TitleBarNormalButton) &&
@@ -735,20 +732,20 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt
(titlebar->titleBarState & Qt::WindowMinimized)) ||
((titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint) &&
(titlebar->titleBarState & Qt::WindowMaximized)))) {
- drawTitleBarButton(CC_TitleBar, SC_TitleBarNormalButton, ChromeRestore);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarNormalButton, Icon::ChromeRestore);
}
// context help button
if (shouldDrawButton(SC_TitleBarContextHelpButton, Qt::WindowContextHelpButtonHint))
- drawTitleBarButton(CC_TitleBar, SC_TitleBarContextHelpButton, Help);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarContextHelpButton, Icon::Help);
// shade button
if (shouldDrawButton(SC_TitleBarShadeButton, Qt::WindowShadeButtonHint))
- drawTitleBarButton(CC_TitleBar, SC_TitleBarShadeButton, ChevronUpSmall);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarShadeButton, Icon::ChevronUpSmall);
// unshade button
if (shouldDrawButton(SC_TitleBarUnshadeButton, Qt::WindowShadeButtonHint))
- drawTitleBarButton(CC_TitleBar, SC_TitleBarUnshadeButton, ChevronDownSmall);
+ drawTitleBarButton(CC_TitleBar, SC_TitleBarUnshadeButton, Icon::ChevronDownSmall);
// window icon for system menu
if (shouldDrawButton(SC_TitleBarSysMenu, Qt::WindowSystemMenuHint)) {
@@ -872,9 +869,9 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
f.setPointSize(6);
painter->setFont(f);
painter->setPen(header->palette.text().color());
- painter->drawText(option->rect, Qt::AlignCenter,
- indicator == QStyleOptionHeader::SortUp ? ChevronDown
- : ChevronUp);
+ const auto ico = indicator == QStyleOptionHeader::SortUp ? Icon::ChevronDown
+ : Icon::ChevronUp;
+ painter->drawText(option->rect, Qt::AlignCenter, fluentIcon(ico));
}
}
break;
@@ -892,8 +889,9 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
painter->setFont(d->assetFont);
painter->setPen(controlTextColor(option, QPalette::Window));
qreal clipWidth = 1.0;
+ const QString str = fluentIcon(Icon::AcceptMedium);
QFontMetrics fm(d->assetFont);
- QRectF clipRect = fm.boundingRect(AcceptMedium);
+ QRectF clipRect = fm.boundingRect(str);
if (d->transitionsEnabled() && option->styleObject) {
QNumberStyleAnimation *animation = qobject_cast<QNumberStyleAnimation *>(
d->animation(option->styleObject));
@@ -904,13 +902,13 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
clipRect.moveCenter(center);
clipRect.setLeft(rect.x() + (rect.width() - clipRect.width()) / 2.0 + 0.5);
clipRect.setWidth(clipWidth * clipRect.width());
- painter->drawText(clipRect, Qt::AlignVCenter | Qt::AlignLeft, AcceptMedium);
+ painter->drawText(clipRect, Qt::AlignVCenter | Qt::AlignLeft, str);
} else if (isPartial) {
QFont f(d->assetFont);
f.setPointSize(6);
painter->setFont(f);
painter->setPen(controlTextColor(option, QPalette::Window));
- painter->drawText(rect, Qt::AlignCenter, Dash12);
+ painter->drawText(rect, Qt::AlignCenter, fluentIcon(Icon::Dash12));
}
}
break;
@@ -919,12 +917,14 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
const bool isReverse = option->direction == Qt::RightToLeft;
const bool isOpen = option->state & QStyle::State_Open;
QFont f(d->assetFont);
- f.setPointSize(6);
+ f.setPointSize(8);
painter->setFont(f);
painter->setPen(option->palette.color(isOpen ? QPalette::Active : QPalette::Disabled,
QPalette::WindowText));
- const auto str = isOpen ? ChevronDownMed : (isReverse ? ChevronLeftMed : ChevronRightMed);
- painter->drawText(option->rect, Qt::AlignCenter, str);
+ const auto ico = isOpen ? Icon::ChevronDownMed
+ : (isReverse ? Icon::ChevronLeftMed
+ : Icon::ChevronRightMed);
+ painter->drawText(option->rect, Qt::AlignCenter, fluentIcon(ico));
}
}
break;
@@ -995,10 +995,17 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption
}
case PE_PanelLineEdit:
if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
- const auto frameRect = QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
- drawRoundedRect(painter, frameRect, Qt::NoPen, inputFillBrush(option, widget));
- if (panel->lineWidth > 0)
- proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
+ const bool isInSpinBox =
+ widget && qobject_cast<const QAbstractSpinBox *>(widget->parent()) != nullptr;
+ const bool isInComboBox =
+ widget && qobject_cast<const QComboBox *>(widget->parent()) != nullptr;
+ if (!isInSpinBox && !isInComboBox) {
+ const auto frameRect =
+ QRectF(option->rect).marginsRemoved(QMarginsF(1.5, 1.5, 1.5, 1.5));
+ drawRoundedRect(painter, frameRect, Qt::NoPen, inputFillBrush(option, widget));
+ if (panel->lineWidth > 0)
+ proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
+ }
}
break;
case PE_FrameLineEdit: {
@@ -1511,7 +1518,7 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
if (isEnabled)
penColor.setAlpha(percentToAlpha(60.63)); // fillColorTextSecondary
painter->setPen(penColor);
- painter->drawText(vindRect, Qt::AlignCenter, ChevronDownMed);
+ painter->drawText(vindRect, Qt::AlignCenter, fluentIcon(Icon::ChevronDownMed));
}
}
break;
@@ -1587,8 +1594,7 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
QPainterStateGuard psg(painter);
painter->setFont(d->assetFont);
painter->setPen(option->palette.text().color());
- const auto textToDraw = QStringLiteral(u"\uE73E");
- painter->drawText(vRect, Qt::AlignCenter, textToDraw);
+ painter->drawText(vRect, Qt::AlignCenter, fluentIcon(Icon::CheckMark));
}
if (menuitem->menuHasCheckableItems)
xOffset += checkMarkWidth + contentItemHMargin;
@@ -1669,8 +1675,8 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op
QRect vSubMenuRect = visualMenuRect(submenuRect);
painter->setPen(option->palette.text().color());
const bool isReverse = option->direction == Qt::RightToLeft;
- const auto str = isReverse ? ChevronLeftMed : ChevronRightMed;
- painter->drawText(vSubMenuRect, Qt::AlignCenter, str);
+ const auto ico = isReverse ? Icon::ChevronLeftMed : Icon::ChevronRightMed;
+ painter->drawText(vSubMenuRect, Qt::AlignCenter, fluentIcon(ico));
}
}
break;
@@ -1936,32 +1942,31 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption
#if QT_CONFIG(spinbox)
case CC_SpinBox:
if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
- QSize bs;
- int fw = spinbox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0;
- bs.setHeight(qMax(8, spinbox->rect.height() - fw));
- bs.setWidth(16);
- int y = fw + spinbox->rect.y();
- int x, lx, rx;
- x = spinbox->rect.x() + spinbox->rect.width() - fw - 2 * bs.width();
- lx = fw;
- rx = x - fw;
+ const bool hasButtons = spinbox->buttonSymbols != QAbstractSpinBox::NoButtons;
+ const int fw = spinbox->frame
+ ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget)
+ : 0;
+ const int buttonHeight = hasButtons
+ ? qMin(spinbox->rect.height() - 3 * fw, spinbox->fontMetrics.height() * 5 / 4)
+ : 0;
+ const QSize buttonSize(buttonHeight * 6 / 5, buttonHeight);
+ const int textFieldLength = spinbox->rect.width() - 2 * fw - 2 * buttonSize.width();
+ const QPoint topLeft(spinbox->rect.topLeft() + QPoint(fw, fw));
switch (subControl) {
case SC_SpinBoxUp:
- if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
+ case SC_SpinBoxDown: {
+ if (!hasButtons)
return QRect();
- ret = QRect(x, y, bs.width(), bs.height());
- break;
- case SC_SpinBoxDown:
- if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
- return QRect();
- ret = QRect(x + bs.width(), y, bs.width(), bs.height());
+ const int yOfs = ((spinbox->rect.height() - 2 * fw) - buttonSize.height()) / 2;
+ ret = QRect(topLeft.x() + textFieldLength, topLeft.y() + yOfs, buttonSize.width(),
+ buttonSize.height());
+ if (subControl == SC_SpinBoxDown)
+ ret.moveRight(ret.right() + buttonSize.width());
break;
+ }
case SC_SpinBoxEditField:
- if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) {
- ret = QRect(lx, fw, spinbox->rect.width() - 2*fw, spinbox->rect.height() - 2*fw);
- } else {
- ret = QRect(lx, fw, rx, spinbox->rect.height() - 2*fw);
- }
+ ret = QRect(topLeft,
+ spinbox->rect.bottomRight() - QPoint(fw + 2 * buttonSize.width(), fw));
break;
case SC_SpinBoxFrame:
ret = spinbox->rect;
@@ -2076,16 +2081,37 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption
break;
}
case CC_ComboBox: {
- if (subControl == SC_ComboBoxArrow) {
+ if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
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);
+ switch (subControl) {
+ case SC_ComboBoxArrow: {
+ const int fw =
+ cb->frame ? proxy()->pixelMetric(PM_ComboBoxFrameWidth, cb, widget) : 0;
+ const int buttonHeight =
+ qMin(cb->rect.height() - 3 * fw, cb->fontMetrics.height() * 5 / 4);
+ const QSize buttonSize(buttonHeight * 6 / 5, buttonHeight);
+ const int textFieldLength = cb->rect.width() - 2 * fw - buttonSize.width();
+ const QPoint topLeft(cb->rect.topLeft() + QPoint(fw, fw));
+ const int yOfs = ((cb->rect.height() - 2 * fw) - buttonSize.height()) / 2;
+ ret = QRect(topLeft.x() + textFieldLength, topLeft.y() + yOfs, buttonSize.width(),
+ buttonSize.height());
+ ret = visualRect(option->direction, option->rect, ret);
+ break;
+ }
+ case SC_ComboBoxEditField: {
+ ret = option->rect;
+ if (cb->frame) {
+ const int fw = proxy()->pixelMetric(PM_ComboBoxFrameWidth, cb, widget);
+ ret = ret.marginsRemoved(QMargins(fw, fw, fw, fw));
+ }
+ ret.setWidth(ret.width() - indicatorWidth - contentHMargin * 2);
+ break;
+ }
+ default:
+ ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
+ break;
+ }
}
break;
}
@@ -2177,15 +2203,15 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
break;
#endif // QT_CONFIG(menu)
#if QT_CONFIG(spinbox)
- case QStyle::CT_SpinBox: {
+ case CT_SpinBox: {
if (const auto *spinBoxOpt = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
// Add button + frame widths
- const qreal dpi = QStyleHelper::dpi(option);
const bool hasButtons = (spinBoxOpt->buttonSymbols != QAbstractSpinBox::NoButtons);
const int margins = 8;
- const int buttonWidth = hasButtons ? qRound(QStyleHelper::dpiScaled(16, dpi)) : 0;
- const int frameWidth = spinBoxOpt->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth,
- spinBoxOpt, widget) : 0;
+ const int buttonWidth = hasButtons ? 16 + contentItemHMargin : 0;
+ const int frameWidth = spinBoxOpt->frame
+ ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget)
+ : 0;
contentSize += QSize(2 * buttonWidth + 2 * frameWidth + 2 * margins, 2 * frameWidth);
}
@@ -2196,7 +2222,7 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
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
+ contentSize += QSize(0, 4); // for the lineedit frame
if (comboBoxOpt->subControls & SC_ComboBoxArrow) {
const auto w = proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget);
contentSize.rwidth() += w + contentItemHMargin;
@@ -2204,6 +2230,13 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o
}
break;
#endif
+ case CT_LineEdit: {
+ if (qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); // don't rely on QWindowsThemeData
+ contentSize += QSize(0, 4); // for the lineedit frame
+ }
+ break;
+ }
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
@@ -2335,7 +2368,7 @@ int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option,
QFont f(d->assetFont);
f.setPointSize(qRound(fontSize * 0.9f)); // a little bit smaller
QFontMetrics fm(f);
- const auto width = fm.horizontalAdvance(ChevronDownMed);
+ const auto width = fm.horizontalAdvance(fluentIcon(Icon::ChevronDownMed));
m_fontPoint2ChevronDownMedWidth.insert(fontSize, width);
res += width;
} else {
@@ -2346,6 +2379,8 @@ int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option,
}
break;
}
+ case PM_ComboBoxFrameWidth:
+ case PM_SpinBoxFrameWidth:
case PM_DefaultFrameWidth:
res = 2;
break;
@@ -2601,7 +2636,7 @@ QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon,
switch (standardIcon) {
case SP_LineEditClearButton: {
if (d->m_lineEditClearButton.isNull()) {
- auto e = new WinFontIconEngine(Clear, d->assetFont);
+ auto e = new WinFontIconEngine(fluentIcon(Icon::Clear), d->assetFont);
d->m_lineEditClearButton = QIcon(e);
}
return d->m_lineEditClearButton;
@@ -2609,7 +2644,7 @@ QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon,
case SP_ToolBarHorizontalExtensionButton:
case SP_ToolBarVerticalExtensionButton: {
if (d->m_toolbarExtensionButton.isNull()) {
- auto e = new WinFontIconEngine(More, d->assetFont);
+ auto e = new WinFontIconEngine(fluentIcon(Icon::More), d->assetFont);
e->setScale(1.0);
d->m_toolbarExtensionButton = QIcon(e);
}
diff --git a/src/plugins/tls/schannel/qtls_schannel.cpp b/src/plugins/tls/schannel/qtls_schannel.cpp
index 667f2d8a6c3..1034e99b7e0 100644
--- a/src/plugins/tls/schannel/qtls_schannel.cpp
+++ b/src/plugins/tls/schannel/qtls_schannel.cpp
@@ -2267,14 +2267,19 @@ static void attachPrivateKeyToCertificate(const QSslCertificate &certificate,
}
const auto freeProvider = qScopeGuard([provider]() { NCryptFreeObject(provider); });
- const QString certName = certificate.subjectInfo(QSslCertificate::CommonName).front();
+ const QString certName = [certificate]() {
+ if (auto cn = certificate.subjectInfo(QSslCertificate::CommonName); !cn.isEmpty())
+ return cn.front();
+ return QString();
+ }();
QSpan<const QChar> nameSpan(certName);
NCryptBuffer nbuffer{ ULONG(nameSpan.size_bytes() + sizeof(char16_t)),
NCRYPTBUFFER_PKCS_KEY_NAME,
const_reinterpret_cast<void *>(nameSpan.data()) };
NCryptBufferDesc bufferDesc{ NCRYPTBUFFER_VERSION, 1, &nbuffer };
+ auto *bufferDescPtr = nameSpan.isEmpty() ? nullptr : &bufferDesc;
NCRYPT_KEY_HANDLE ncryptKey = 0;
- status = NCryptImportKey(provider, 0, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, &bufferDesc, &ncryptKey,
+ status = NCryptImportKey(provider, 0, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, bufferDescPtr, &ncryptKey,
PBYTE(buffer.data()), buffer.size(), 0);
if (status != SEC_E_OK) {
qCWarning(lcTlsBackendSchannel())
diff --git a/src/sql/doc/qtsql.qdocconf b/src/sql/doc/qtsql.qdocconf
index 2545bcf4050..03efa743957 100644
--- a/src/sql/doc/qtsql.qdocconf
+++ b/src/sql/doc/qtsql.qdocconf
@@ -58,3 +58,6 @@ manifestmeta.highlighted.names = \
# Enforce zero documentation warnings
warninglimit = 0
+
+# Report warnings for images without alt text
+reportmissingalttextforimages = true
diff --git a/src/sql/doc/src/sql-programming.qdoc b/src/sql/doc/src/sql-programming.qdoc
index 07daf942ac4..948baec6f38 100644
--- a/src/sql/doc/src/sql-programming.qdoc
+++ b/src/sql/doc/src/sql-programming.qdoc
@@ -419,7 +419,9 @@
\table
\row \li \inlineimage noforeignkeys.png
+ {Table showing city and country as numeric foreign key values}
\li \inlineimage foreignkeys.png
+ {Table showing city and country resolved to text strings}
\endtable
The screenshot on the left shows a plain QSqlTableModel in a
diff --git a/src/tools/moc/generator.cpp b/src/tools/moc/generator.cpp
index fbd6d3154e2..94c75ae6eb3 100644
--- a/src/tools/moc/generator.cpp
+++ b/src/tools/moc/generator.cpp
@@ -78,16 +78,18 @@ QT_FOR_EACH_STATIC_TYPE(RETURN_METATYPENAME_STRING)
return nullptr;
}
- Generator::Generator(Moc *moc, ClassDef *classDef, const QList<QByteArray> &metaTypes,
+ Generator::Generator(Moc *moc, const ClassDef *classDef, const QList<QByteArray> &metaTypes,
const QHash<QByteArray, QByteArray> &knownQObjectClasses,
- const QHash<QByteArray, QByteArray> &knownGadgets, FILE *outfile,
- bool requireCompleteTypes)
+ const QHash<QByteArray, QByteArray> &knownGadgets,
+ const QHash<QByteArray, QByteArray> &hashes,
+ FILE *outfile, bool requireCompleteTypes)
: parser(moc),
out(outfile),
cdef(classDef),
metaTypes(metaTypes),
knownQObjectClasses(knownQObjectClasses),
knownGadgets(knownGadgets),
+ hashes(hashes),
requireCompleteTypes(requireCompleteTypes)
{
if (cdef->superclassList.size())
@@ -228,28 +230,11 @@ void Generator::generateCode()
bool isQObject = (cdef->classname == "QObject");
bool isConstructible = !cdef->constructorList.isEmpty();
- // filter out undeclared enumerators and sets
- {
- QList<EnumDef> enumList;
- for (EnumDef def : std::as_const(cdef->enumList)) {
- if (cdef->enumDeclarations.contains(def.name)) {
- enumList += def;
- }
- def.enumName = def.name;
- QByteArray alias = cdef->flagAliases.value(def.name);
- if (cdef->enumDeclarations.contains(alias)) {
- def.name = alias;
- def.flags |= cdef->enumDeclarations[alias];
- enumList += def;
- }
- }
- cdef->enumList = enumList;
- }
-
//
// Register all strings used in data section
//
strreg(cdef->qualified);
+ strreg(hashes[cdef->qualified]);
registerClassInfoStrings();
registerFunctionStrings(cdef->signalList);
registerFunctionStrings(cdef->slotList);
@@ -308,6 +293,8 @@ void Generator::generateCode()
addEnums();
fprintf(out, " };\n");
+ fprintf(out, " uint qt_metaObjectHashIndex = %d;\n", stridx(hashes[cdef->qualified]));
+
const char *uintDataParams = "";
if (isConstructible || !cdef->classInfoList.isEmpty()) {
if (isConstructible) {
@@ -340,7 +327,7 @@ void Generator::generateCode()
if (!requireCompleteness)
tagType = "qt_meta_tag_" + qualifiedClassNameIdentifier + "_t";
fprintf(out, " return QtMocHelpers::metaObjectData<%s, %s>(%s, qt_stringData,\n"
- " qt_methods, qt_properties, qt_enums%s);\n"
+ " qt_methods, qt_properties, qt_enums, qt_metaObjectHashIndex%s);\n"
"}\n",
ownType, tagType.constData(), metaObjectFlags, uintDataParams);
}
@@ -770,6 +757,10 @@ void Generator::addProperties()
addFlag("Constant");
if (p.final)
addFlag("Final");
+ if (p.virtual_)
+ addFlag("Virtual");
+ if (p.override)
+ addFlag("Override");
if (p.user != "false")
addFlag("User");
if (p.required)
diff --git a/src/tools/moc/generator.h b/src/tools/moc/generator.h
index 45df0783c2b..77be2fc6714 100644
--- a/src/tools/moc/generator.h
+++ b/src/tools/moc/generator.h
@@ -12,14 +12,15 @@ class Generator
{
Moc *parser = nullptr;
FILE *out;
- ClassDef *cdef;
+ const ClassDef *cdef;
QList<uint> meta_data;
public:
- Generator(Moc *moc, ClassDef *classDef, const QList<QByteArray> &metaTypes,
+ Generator(Moc *moc, const ClassDef *classDef, const QList<QByteArray> &metaTypes,
const QHash<QByteArray, QByteArray> &knownQObjectClasses,
- const QHash<QByteArray, QByteArray> &knownGadgets, FILE *outfile = nullptr,
- bool requireCompleteTypes = false);
+ const QHash<QByteArray, QByteArray> &knownGadgets,
+ const QHash<QByteArray, QByteArray> &hashes,
+ FILE *outfile = nullptr, bool requireCompleteTypes = false);
void generateCode();
qsizetype registeredStringsCount() { return strings.size(); }
@@ -54,6 +55,7 @@ private:
QList<QByteArray> metaTypes;
QHash<QByteArray, QByteArray> knownQObjectClasses;
QHash<QByteArray, QByteArray> knownGadgets;
+ QHash<QByteArray, QByteArray> hashes;
bool requireCompleteTypes;
};
diff --git a/src/tools/moc/moc.cpp b/src/tools/moc/moc.cpp
index 64af8c10fc1..7f05f34edb6 100644
--- a/src/tools/moc/moc.cpp
+++ b/src/tools/moc/moc.cpp
@@ -17,6 +17,10 @@
#include <private/qmetaobject_moc_p.h>
#include <private/qduplicatetracker_p.h>
+// This is a bootstrapped tool, so we can't rely on QCryptographicHash for the
+// faster SHA1 implementations from OpenSSL.
+#include "../../3rdparty/sha1/sha1.cpp"
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -1191,6 +1195,24 @@ static QByteArrayList requiredQtContainers(const QList<ClassDef> &classes)
return required;
}
+QByteArray classDefJsonObjectHash(const QJsonObject &object)
+{
+ const QByteArray json = QJsonDocument(object).toJson(QJsonValue::JsonFormat::Compact);
+ QByteArray hash(20, 0); // SHA1 produces 160 bits of data
+
+ {
+ Sha1State state;
+ sha1InitState(&state);
+ sha1Update(&state, reinterpret_cast<const uchar *>(json.constData()), json.size());
+ sha1FinalizeState(&state);
+ sha1ToHash(&state, reinterpret_cast<uchar *>(hash.data()));
+ }
+
+ static const char revisionPrefix[] = "0$";
+ const QByteArray hashB64 = hash.toBase64(QByteArray::OmitTrailingEquals);
+ return revisionPrefix + hashB64;
+}
+
void Moc::generate(FILE *out, FILE *jsonOutput)
{
QByteArrayView fn = strippedFileName();
@@ -1247,14 +1269,40 @@ void Moc::generate(FILE *out, FILE *jsonOutput)
"#endif\n\n");
#endif
+ // filter out undeclared enumerators and sets
+ for (ClassDef &cdef : classList) {
+ QList<EnumDef> enumList;
+ for (EnumDef def : std::as_const(cdef.enumList)) {
+ if (cdef.enumDeclarations.contains(def.name)) {
+ enumList += def;
+ }
+ def.enumName = def.name;
+ QByteArray alias = cdef.flagAliases.value(def.name);
+ if (cdef.enumDeclarations.contains(alias)) {
+ def.name = alias;
+ def.flags |= cdef.enumDeclarations[alias];
+ enumList += def;
+ }
+ }
+ cdef.enumList = enumList;
+ }
+
fprintf(out, "QT_WARNING_PUSH\n");
fprintf(out, "QT_WARNING_DISABLE_DEPRECATED\n");
fprintf(out, "QT_WARNING_DISABLE_GCC(\"-Wuseless-cast\")\n");
+ QHash<QByteArray, QJsonObject> classDefJsonObjects;
+ QHash<QByteArray, QByteArray> metaObjectHashes;
+ for (const ClassDef &def : std::as_const(classList)) {
+ const QJsonObject jsonObject = def.toJson();
+ classDefJsonObjects.insert(def.qualified, jsonObject);
+ metaObjectHashes.insert(def.qualified, classDefJsonObjectHash(jsonObject));
+ }
+
fputs("", out);
- for (ClassDef &def : classList) {
- Generator generator(this, &def, metaTypes, knownQObjectClasses, knownGadgets, out,
- requireCompleteTypes);
+ for (const ClassDef &def : std::as_const(classList)) {
+ Generator generator(this, &def, metaTypes, knownQObjectClasses, knownGadgets,
+ metaObjectHashes, out, requireCompleteTypes);
generator.generateCode();
// generator.generateCode() should have already registered all strings
@@ -1273,13 +1321,20 @@ void Moc::generate(FILE *out, FILE *jsonOutput)
mocData["inputFile"_L1] = QLatin1StringView(fn.constData());
QJsonArray classesJsonFormatted;
+ QJsonObject hashesJsonObject;
- for (const ClassDef &cdef: std::as_const(classList))
- classesJsonFormatted.append(cdef.toJson());
+ for (const ClassDef &cdef : std::as_const(classList)) {
+ classesJsonFormatted.append(classDefJsonObjects[cdef.qualified]);
+ hashesJsonObject.insert(QString::fromLatin1(cdef.qualified),
+ QString::fromLatin1(metaObjectHashes[cdef.qualified]));
+ }
if (!classesJsonFormatted.isEmpty())
mocData["classes"_L1] = classesJsonFormatted;
+ if (!hashesJsonObject.isEmpty())
+ mocData["hashes"_L1] = hashesJsonObject;
+
QJsonDocument jsonDoc(mocData);
fputs(jsonDoc.toJson().constData(), jsonOutput);
}
@@ -1434,6 +1489,9 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef)
next(IDENTIFIER);
propDef.name = lexem();
continue;
+ } else if (l[0] == 'O' && l == "OVERRIDE") {
+ propDef.override = true;
+ continue;
} else if (l[0] == 'R' && l == "REQUIRED") {
propDef.required = true;
continue;
@@ -1441,6 +1499,9 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef)
prev();
propDef.revision = parseRevision().toEncodedVersion<int>();
continue;
+ } else if (l[0] == 'V' && l == "VIRTUAL") {
+ propDef.virtual_ = true;
+ continue;
}
QByteArray v, v2;
@@ -1545,6 +1606,24 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef)
propDef.write = "";
warning(msg.constData());
}
+ if (propDef.override && propDef.virtual_) {
+ const QByteArray msg = "Issue with property declaration " + propDef.name
+ + ": VIRTUAL is redundant when overriding a property. The OVERRIDE "
+ "must only be used when actually overriding an existing property; using it on a "
+ "new property is an error.";
+ error(msg.constData());
+ }
+ if (propDef.override && propDef.final) {
+ const QByteArray msg = "Issue with property declaration " + propDef.name
+ + ": OVERRIDE is redundant when property is marked FINAL";
+ error(msg.constData());
+ }
+ if (propDef.virtual_ && propDef.final) {
+ const QByteArray msg = "Issue with property declaration " + propDef.name
+ + ": The VIRTUAL cannot be combined with FINAL, as these attributes are mutually "
+ "exclusive";
+ error(msg.constData());
+ }
}
void Moc::parseProperty(ClassDef *def, Moc::PropertyMode mode)
diff --git a/src/tools/moc/moc.h b/src/tools/moc/moc.h
index aafa80d2164..a211433622a 100644
--- a/src/tools/moc/moc.h
+++ b/src/tools/moc/moc.h
@@ -130,6 +130,8 @@ struct PropertyDef
TypeTags typeTag;
bool constant = false;
bool final = false;
+ bool virtual_ = false;
+ bool override = false;
bool required = false;
int relativeIndex = -1; // property index in current metaobject
int lineNumber = 0;
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index f4fc96b867d..d43b6ec4fb2 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -339,12 +339,12 @@ set(qstyle_resource_fusion_files
"styles/images/fusion_closedock-32.png"
"styles/images/fusion_closedock-48.png"
"styles/images/fusion_closedock-64.png"
- "styles/images/fusion_normalizedockup_10.png"
+ "styles/images/fusion_normalizedockup-10.png"
"styles/images/fusion_normalizedockup-16.png"
- "styles/images/fusion_normalizedockup_20.png"
+ "styles/images/fusion_normalizedockup-20.png"
"styles/images/fusion_normalizedockup-32.png"
- "styles/images/fusion_normalizedockup_48.png"
- "styles/images/fusion_normalizedockup_64.png"
+ "styles/images/fusion_normalizedockup-48.png"
+ "styles/images/fusion_normalizedockup-64.png"
"styles/images/fusion_titlebar-min-10.png"
"styles/images/fusion_titlebar-min-16.png"
"styles/images/fusion_titlebar-min-20.png"
diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp
index cc3a230f9b4..ba941012dd7 100644
--- a/src/widgets/accessible/itemviews.cpp
+++ b/src/widgets/accessible/itemviews.cpp
@@ -99,6 +99,21 @@ QHeaderView *QAccessibleTable::verticalHeader() const
return header;
}
+// Normally cellAt takes row/column in the range
+// [0 .. rowCount())
+// [0 .. columnCount())
+//
+// As an extension we allow clients to ask for headers
+//
+// * Has both vertical and horizontal headers:
+// (-1,-1) -> corner button
+// * Has column headers:
+// (-1, column) -> column header for column \a column
+// * has row headers
+// (row, -1) -> row header for row \a row
+//
+// If asking for a header that does not exist, The invalid
+// index warning is logged, and nullptr is returned.
QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const
{
const QAbstractItemView *theView = view();
@@ -107,6 +122,22 @@ QAccessibleInterface *QAccessibleTable::cellAt(int row, int column) const
return nullptr;
Q_ASSERT(role() != QAccessible::List);
Q_ASSERT(role() != QAccessible::Tree);
+
+ const int vHeader = verticalHeader() ? 1 : 0;
+ const int hHeader = horizontalHeader() ? 1 : 0;
+
+ const int doHHeader = ((row == -1) && hHeader);
+ const int doVHeader = ((column == -1) && vHeader);
+
+ if (doVHeader && doHHeader)
+ return child(0);
+
+ if (doVHeader)
+ return child((row + hHeader) * (columnCount() + vHeader) + (column + vHeader));
+
+ if (doHHeader)
+ return child((row + hHeader) * (columnCount() + vHeader) + (column + vHeader));
+
QModelIndex index = theModel->index(row, column, theView->rootIndex());
if (Q_UNLIKELY(!index.isValid())) {
qWarning() << "QAccessibleTable::cellAt: invalid index: " << index << " for " << theView;
diff --git a/src/widgets/itemviews/qabstractitemview.cpp b/src/widgets/itemviews/qabstractitemview.cpp
index 6288aae096a..05233ba5801 100644
--- a/src/widgets/itemviews/qabstractitemview.cpp
+++ b/src/widgets/itemviews/qabstractitemview.cpp
@@ -172,6 +172,43 @@ void QAbstractItemViewPrivate::checkMouseMove(const QPersistentModelIndex &index
}
}
+#if QT_CONFIG(accessibility)
+void QAbstractItemViewPrivate::updateItemAccessibility(const QModelIndex &index,
+ const QList<int> &roles)
+{
+ Q_Q(QAbstractItemView);
+
+ if (!QAccessible::isActive())
+ return;
+
+ const int childIndex = accessibleChildIndex(index);
+ if (childIndex < 0)
+ return;
+
+ // see QAccessibleTableCell for how role data are mapped to the a11y layer
+
+ for (int role : roles) {
+ if (role == Qt::AccessibleTextRole
+ || (role == Qt::DisplayRole
+ && index.data(Qt::AccessibleTextRole).toString().isEmpty())) {
+ QAccessibleEvent event(q, QAccessible::NameChanged);
+ event.setChild(childIndex);
+ QAccessible::updateAccessibility(&event);
+ } else if (role == Qt::AccessibleDescriptionRole) {
+ QAccessibleEvent event(q, QAccessible::DescriptionChanged);
+ event.setChild(childIndex);
+ QAccessible::updateAccessibility(&event);
+ } else if (role == Qt::CheckStateRole) {
+ QAccessible::State state;
+ state.checked = true;
+ QAccessibleStateChangeEvent event(q, state);
+ event.setChild(childIndex);
+ QAccessible::updateAccessibility(&event);
+ }
+ }
+}
+#endif
+
#if QT_CONFIG(gestures) && QT_CONFIG(scroller)
// stores and restores the selection and current item when flicking
@@ -3495,6 +3532,10 @@ void QAbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelInde
accessibleEvent.setLastRow(bottomRight.row());
accessibleEvent.setLastColumn(bottomRight.column());
QAccessible::updateAccessibility(&accessibleEvent);
+
+ // send accessibility events as needed when current item is modified
+ if (topLeft == bottomRight && topLeft == currentIndex())
+ d->updateItemAccessibility(topLeft, roles);
}
#endif
d->updateGeometry();
diff --git a/src/widgets/itemviews/qabstractitemview_p.h b/src/widgets/itemviews/qabstractitemview_p.h
index 60799fb8a50..f9e899d7fc8 100644
--- a/src/widgets/itemviews/qabstractitemview_p.h
+++ b/src/widgets/itemviews/qabstractitemview_p.h
@@ -272,6 +272,18 @@ public:
return isIndexValid(index) && isIndexSelectable(index);
}
+#if QT_CONFIG(accessibility)
+ virtual int accessibleChildIndex(const QModelIndex &index) const
+ {
+ Q_UNUSED(index);
+ return -1;
+ }
+#endif
+
+#if QT_CONFIG(accessibility)
+ void updateItemAccessibility(const QModelIndex &index, const QList<int> &roles);
+#endif
+
// reimplemented from QAbstractScrollAreaPrivate
QPoint contentsOffset() const override {
Q_Q(const QAbstractItemView);
diff --git a/src/widgets/itemviews/qlistview.cpp b/src/widgets/itemviews/qlistview.cpp
index e245f98151b..50b6034500d 100644
--- a/src/widgets/itemviews/qlistview.cpp
+++ b/src/widgets/itemviews/qlistview.cpp
@@ -1959,6 +1959,14 @@ bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QMo
}
#endif
+#if QT_CONFIG(accessibility)
+int QListViewPrivate::accessibleChildIndex(const QModelIndex &index) const
+{
+ Q_Q(const QListView);
+ return q->visualIndex(index);
+}
+#endif
+
void QListViewPrivate::removeCurrentAndDisabled(QList<QModelIndex> *indexes,
const QModelIndex &current) const
{
@@ -3397,11 +3405,12 @@ void QIconModeViewBase::updateContentsSize()
*/
void QListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
+ Q_D(const QListView);
QAbstractItemView::currentChanged(current, previous);
#if QT_CONFIG(accessibility)
if (QAccessible::isActive()) {
if (current.isValid() && hasFocus()) {
- int entry = visualIndex(current);
+ int entry = d->accessibleChildIndex(current);
QAccessibleEvent event(this, QAccessible::Focus);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
@@ -3417,18 +3426,19 @@ void QListView::selectionChanged(const QItemSelection &selected,
const QItemSelection &deselected)
{
#if QT_CONFIG(accessibility)
+ Q_D(const QListView);
if (QAccessible::isActive()) {
// ### does not work properly for selection ranges.
QModelIndex sel = selected.indexes().value(0);
if (sel.isValid()) {
- int entry = visualIndex(sel);
+ int entry = d->accessibleChildIndex(sel);
QAccessibleEvent event(this, QAccessible::SelectionAdd);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
}
QModelIndex desel = deselected.indexes().value(0);
if (desel.isValid()) {
- int entry = visualIndex(desel);
+ int entry = d->accessibleChildIndex(desel);
QAccessibleEvent event(this, QAccessible::SelectionRemove);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
diff --git a/src/widgets/itemviews/qlistview_p.h b/src/widgets/itemviews/qlistview_p.h
index 4475fa5461f..7e36887a65c 100644
--- a/src/widgets/itemviews/qlistview_p.h
+++ b/src/widgets/itemviews/qlistview_p.h
@@ -346,6 +346,10 @@ public:
bool dropOn(QDropEvent *event, int *row, int *col, QModelIndex *index) override;
#endif
+#if QT_CONFIG(accessibility)
+ int accessibleChildIndex(const QModelIndex &index) const override;
+#endif
+
inline void setGridSize(const QSize &size) { grid = size; }
inline QSize gridSize() const { return grid; }
inline void setWrapping(bool b) { wrap = b; }
diff --git a/src/widgets/itemviews/qtableview.cpp b/src/widgets/itemviews/qtableview.cpp
index 40e3fcaf91b..2d28b3d4a81 100644
--- a/src/widgets/itemviews/qtableview.cpp
+++ b/src/widgets/itemviews/qtableview.cpp
@@ -3593,7 +3593,7 @@ void QTableView::currentChanged(const QModelIndex &current, const QModelIndex &p
if (QAccessible::isActive()) {
if (current.isValid() && hasFocus()) {
Q_D(QTableView);
- int entry = d->accessibleTable2Index(current);
+ int entry = d->accessibleChildIndex(current);
QAccessibleEvent event(this, QAccessible::Focus);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
@@ -3616,14 +3616,14 @@ void QTableView::selectionChanged(const QItemSelection &selected,
// ### does not work properly for selection ranges.
QModelIndex sel = selected.indexes().value(0);
if (sel.isValid()) {
- int entry = d->accessibleTable2Index(sel);
+ int entry = d->accessibleChildIndex(sel);
QAccessibleEvent event(this, QAccessible::SelectionAdd);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
}
QModelIndex desel = deselected.indexes().value(0);
if (desel.isValid()) {
- int entry = d->accessibleTable2Index(desel);
+ int entry = d->accessibleChildIndex(desel);
QAccessibleEvent event(this, QAccessible::SelectionRemove);
event.setChild(entry);
QAccessible::updateAccessibility(&event);
diff --git a/src/widgets/itemviews/qtableview_p.h b/src/widgets/itemviews/qtableview_p.h
index 8ddb8e797a9..9a7ce229880 100644
--- a/src/widgets/itemviews/qtableview_p.h
+++ b/src/widgets/itemviews/qtableview_p.h
@@ -141,11 +141,14 @@ public:
QStyleOptionViewItem::ViewItemPosition viewItemPosition(const QModelIndex &index) const;
- inline int accessibleTable2Index(const QModelIndex &index) const {
+#if QT_CONFIG(accessibility)
+ inline int accessibleChildIndex(const QModelIndex &index) const override
+ {
const int vHeader = verticalHeader ? 1 : 0;
return (index.row() + (horizontalHeader ? 1 : 0)) * (index.model()->columnCount() + vHeader)
+ index.column() + vHeader;
}
+#endif
int sectionSpanEndLogical(const QHeaderView *header, int logical, int span) const;
int sectionSpanSize(const QHeaderView *header, int logical, int span) const;
diff --git a/src/widgets/itemviews/qtreeview.cpp b/src/widgets/itemviews/qtreeview.cpp
index e38d78b72f8..570566793dc 100644
--- a/src/widgets/itemviews/qtreeview.cpp
+++ b/src/widgets/itemviews/qtreeview.cpp
@@ -4083,13 +4083,15 @@ void QTreeViewPrivate::sortIndicatorChanged(int column, Qt::SortOrder order)
model->sort(column, order);
}
-int QTreeViewPrivate::accessibleTree2Index(const QModelIndex &index) const
+#if QT_CONFIG(accessibility)
+int QTreeViewPrivate::accessibleChildIndex(const QModelIndex &index) const
{
Q_Q(const QTreeView);
// Note that this will include the header, even if its hidden.
return (q->visualIndex(index) + (q->header() ? 1 : 0)) * index.model()->columnCount() + index.column();
}
+#endif
void QTreeViewPrivate::updateIndentationFromStyle()
{
@@ -4116,7 +4118,7 @@ void QTreeView::currentChanged(const QModelIndex &current, const QModelIndex &pr
Q_D(QTreeView);
QAccessibleEvent event(this, QAccessible::Focus);
- event.setChild(d->accessibleTree2Index(current));
+ event.setChild(d->accessibleChildIndex(current));
QAccessible::updateAccessibility(&event);
}
#endif
@@ -4136,7 +4138,7 @@ void QTreeView::selectionChanged(const QItemSelection &selected,
// ### does not work properly for selection ranges.
QModelIndex sel = selected.indexes().value(0);
if (sel.isValid()) {
- int entry = d->accessibleTree2Index(sel);
+ int entry = d->accessibleChildIndex(sel);
Q_ASSERT(entry >= 0);
QAccessibleEvent event(this, QAccessible::SelectionAdd);
event.setChild(entry);
@@ -4144,7 +4146,7 @@ void QTreeView::selectionChanged(const QItemSelection &selected,
}
QModelIndex desel = deselected.indexes().value(0);
if (desel.isValid()) {
- int entry = d->accessibleTree2Index(desel);
+ int entry = d->accessibleChildIndex(desel);
Q_ASSERT(entry >= 0);
QAccessibleEvent event(this, QAccessible::SelectionRemove);
event.setChild(entry);
diff --git a/src/widgets/itemviews/qtreeview_p.h b/src/widgets/itemviews/qtreeview_p.h
index 5a4e057901c..34db2fcdacb 100644
--- a/src/widgets/itemviews/qtreeview_p.h
+++ b/src/widgets/itemviews/qtreeview_p.h
@@ -234,7 +234,9 @@ public:
return (viewIndex(index) + (header ? 1 : 0)) * model->columnCount()+index.column();
}
- int accessibleTree2Index(const QModelIndex &index) const;
+#if QT_CONFIG(accessibility)
+ int accessibleChildIndex(const QModelIndex &index) const override;
+#endif
void updateIndentationFromStyle();
diff --git a/src/widgets/kernel/qtooltip.cpp b/src/widgets/kernel/qtooltip.cpp
index d989feb7f91..fa17c94a23f 100644
--- a/src/widgets/kernel/qtooltip.cpp
+++ b/src/widgets/kernel/qtooltip.cpp
@@ -389,13 +389,16 @@ void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
p += offset;
#if QT_CONFIG(wayland)
- create();
- if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(windowHandle()->handle())) {
- // based on the existing code below, by default position at 'p' stored at the bottom right of our rect
- // then flip to the other arbitrary 4x24 space if constrained
- const QRect controlGeometry(QRect(p.x() - 4, p.y() - 24, 4, 24));
- waylandWindow->setParentControlGeometry(controlGeometry);
- waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ToolTip);
+ if (w) {
+ create();
+ if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(windowHandle()->handle())) {
+ // based on the existing code below, by default position at 'p' stored at the bottom right of our rect
+ // then flip to the other arbitrary 4x24 space if constrained
+ const QRect controlGeometry = QRect(p.x() - 4, p.y() - 24, 4, 24)
+ .translated(-w->window()->mapToGlobal(QPoint(0, 0)));
+ waylandWindow->setParentControlGeometry(controlGeometry);
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ToolTip);
+ }
}
#endif
diff --git a/src/widgets/styles/images/fusion_normalizedockup_10.png b/src/widgets/styles/images/fusion_normalizedockup-10.png
index 7516e4ee4f8..7516e4ee4f8 100644
--- a/src/widgets/styles/images/fusion_normalizedockup_10.png
+++ b/src/widgets/styles/images/fusion_normalizedockup-10.png
Binary files differ
diff --git a/src/widgets/styles/images/fusion_normalizedockup_20.png b/src/widgets/styles/images/fusion_normalizedockup-20.png
index 2bc9421d5ac..2bc9421d5ac 100644
--- a/src/widgets/styles/images/fusion_normalizedockup_20.png
+++ b/src/widgets/styles/images/fusion_normalizedockup-20.png
Binary files differ
diff --git a/src/widgets/styles/images/fusion_normalizedockup_48.png b/src/widgets/styles/images/fusion_normalizedockup-48.png
index 6c497abdded..6c497abdded 100644
--- a/src/widgets/styles/images/fusion_normalizedockup_48.png
+++ b/src/widgets/styles/images/fusion_normalizedockup-48.png
Binary files differ
diff --git a/src/widgets/styles/images/fusion_normalizedockup_64.png b/src/widgets/styles/images/fusion_normalizedockup-64.png
index 5ec620e5a04..5ec620e5a04 100644
--- a/src/widgets/styles/images/fusion_normalizedockup_64.png
+++ b/src/widgets/styles/images/fusion_normalizedockup-64.png
Binary files differ
diff --git a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json
index 9bd20506429..28be7330cc1 100644
--- a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json
+++ b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json
@@ -14,6 +14,9 @@
]
}
],
+ "hashes": {
+ "MetaType": "0$swya0mP+olQ6EImtfZ4HW3dVkKs"
+ },
"inputFile": "MetaType.h",
"outputRevision": 69
}
diff --git a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json
index fe80985f796..576668df12f 100644
--- a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json
+++ b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json
@@ -30,6 +30,9 @@
]
}
],
+ "hashes": {
+ "MetaType": "0$NMxUTKrEcV2vk8Gr4Jl/SR4Q7/c"
+ },
"inputFile": "MetaType.h",
"outputRevision": 69
}
diff --git a/tests/auto/corelib/global/qxp/is_virtual_base_of/tst_is_virtual_base_of.cpp b/tests/auto/corelib/global/qxp/is_virtual_base_of/tst_is_virtual_base_of.cpp
index e50575f5eca..59e1b040e6e 100644
--- a/tests/auto/corelib/global/qxp/is_virtual_base_of/tst_is_virtual_base_of.cpp
+++ b/tests/auto/corelib/global/qxp/is_virtual_base_of/tst_is_virtual_base_of.cpp
@@ -58,9 +58,15 @@ class AmbiguousBase1 : public IntermediateDerived, public Base {};
class AmbiguousBase2 : public IntermediateDerived, public virtual Base {};
static_assert(!qxp::is_virtual_base_of_v<Base, AmbiguousBase1>);
-#ifndef Q_CC_MSVC_ONLY // https://developercommunity.visualstudio.com/t/c-templates-multiple-inheritance-ambiguous-access/185674
+#ifdef __cpp_lib_is_virtual_base_of
+// Our own implementation cannot handle ambiguous bases correctly;
+// the stdlib one does.
+static_assert(qxp::is_virtual_base_of_v<Base, AmbiguousBase2>);
+#else
+#if !defined(Q_CC_MSVC_ONLY) || Q_CC_MSVC_ONLY >= 1940 // https://developercommunity.visualstudio.com/t/c-templates-multiple-inheritance-ambiguous-access/185674
static_assert(!qxp::is_virtual_base_of_v<Base, AmbiguousBase2>);
#endif
+#endif
QT_WARNING_POP
// Const
diff --git a/tests/auto/corelib/io/CMakeLists.txt b/tests/auto/corelib/io/CMakeLists.txt
index c0d5ea3136e..10327ceaefb 100644
--- a/tests/auto/corelib/io/CMakeLists.txt
+++ b/tests/auto/corelib/io/CMakeLists.txt
@@ -9,7 +9,7 @@ endif()
if(QT_FEATURE_private_tests)
add_subdirectory(qabstractfileengine)
add_subdirectory(qfileinfo)
- if(LINUX AND QT_FEATURE_liburing)
+ if((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring))
add_subdirectory(qioring)
endif()
add_subdirectory(qipaddress)
diff --git a/tests/auto/corelib/io/qioring/tst_qioring.cpp b/tests/auto/corelib/io/qioring/tst_qioring.cpp
index 1128bcd7979..75d4fe68c55 100644
--- a/tests/auto/corelib/io/qioring/tst_qioring.cpp
+++ b/tests/auto/corelib/io/qioring/tst_qioring.cpp
@@ -5,7 +5,12 @@
#include <QtCore/private/qioring_p.h>
+#ifdef Q_OS_WIN
+#include <QtCore/qt_windows.h>
+#include <io.h>
+#else
#include <QtCore/private/qcore_unix_p.h>
+#endif
using namespace Qt::StringLiterals;
using namespace std::chrono_literals;
@@ -30,7 +35,13 @@ private:
void tst_QIORing::closeFile(qintptr fd)
{
+#ifdef Q_OS_WIN
+ // NOLINTNEXTLINE(performance-no-int-to-ptr)
+ HANDLE h = HANDLE(fd);
+ CloseHandle(h);
+#else
QT_CLOSE(fd);
+#endif
}
qintptr tst_QIORing::openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags)
@@ -109,7 +120,11 @@ void tst_QIORing::read()
QFile file(QFINDTESTDATA("data/input.txt"));
QVERIFY(file.open(QIODevice::ReadOnly));
int fd = file.handle();
+#ifdef Q_OS_WIN
+ qintptr nativeFd = _get_osfhandle(fd);
+#else
qintptr nativeFd = fd;
+#endif
QIORing ring;
QVERIFY(ring.ensureInitialized());
diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp
index 71389abc976..a4ef698d380 100644
--- a/tests/auto/corelib/io/qurl/tst_qurl.cpp
+++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp
@@ -925,7 +925,7 @@ void tst_QUrl::resolving_data()
QTest::newRow("/-on-empty-no-authority") << "scheme:" << "/" << "scheme:/";
QTest::newRow(".-on-empty-no-authority") << "scheme:" << "." << "scheme:";
QTest::newRow("./-on-empty-no-authority") << "scheme:" << "./" << "scheme:";
- QTest::newRow(".//-on-empty-no-authority") << "scheme:" << "./" << "scheme:";
+ QTest::newRow(".//-on-empty-no-authority") << "scheme:" << ".//" << "scheme:";
QTest::newRow("..-on-empty-no-authority") << "scheme:" << ".." << "scheme:";
QTest::newRow("../-on-empty-no-authority") << "scheme:" << "../" << "scheme:";
diff --git a/tests/auto/corelib/itemmodels/CMakeLists.txt b/tests/auto/corelib/itemmodels/CMakeLists.txt
index 8304fa15bc6..ffd326b7eed 100644
--- a/tests/auto/corelib/itemmodels/CMakeLists.txt
+++ b/tests/auto/corelib/itemmodels/CMakeLists.txt
@@ -4,6 +4,7 @@
add_subdirectory(qstringlistmodel)
if(TARGET Qt::Gui)
add_subdirectory(qrangemodel)
+ add_subdirectory(qrangemodeladapter)
add_subdirectory(qabstractitemmodel)
if(QT_FEATURE_proxymodel)
add_subdirectory(qabstractproxymodel)
diff --git a/tests/auto/corelib/itemmodels/qrangemodel/data.h b/tests/auto/corelib/itemmodels/qrangemodel/data.h
index bb4074e80f8..879017ba1f4 100644
--- a/tests/auto/corelib/itemmodels/qrangemodel/data.h
+++ b/tests/auto/corelib/itemmodels/qrangemodel/data.h
@@ -50,6 +50,14 @@ public:
void setToolTip(const QString &toolTip) { m_toolTip = toolTip; }
private:
+ friend inline bool comparesEqual(const Item &lhs, const Item &rhs) noexcept
+ {
+ return lhs.m_display == rhs.m_display
+ && lhs.m_decoration == rhs.m_decoration
+ && lhs.m_toolTip == rhs.m_toolTip;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(Item);
+
QString m_display;
QColor m_decoration;
QString m_toolTip;
@@ -70,6 +78,17 @@ public:
QColor m_decoration;
QVariant m_user;
int m_number = 0;
+
+private:
+ friend inline bool comparesEqual(const MultiRoleGadget &lhs,
+ const MultiRoleGadget &rhs) noexcept
+ {
+ return lhs.m_display == rhs.m_display
+ && lhs.m_decoration == rhs.m_decoration
+ && lhs.m_user == rhs.m_user
+ && lhs.m_number == rhs.m_number;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(MultiRoleGadget);
};
template <>
@@ -116,7 +135,10 @@ class Object : public QObject
Q_PROPERTY(QString string READ string WRITE setString NOTIFY stringChanged)
Q_PROPERTY(int number READ number WRITE setNumber NOTIFY numberChanged)
public:
- using QObject::QObject;
+ Object() = default;
+ explicit Object(const QString &string, int number)
+ : m_string(string), m_number(number)
+ {}
QString string() const { return m_string; }
void setString(const QString &string)
@@ -145,6 +167,19 @@ private:
int m_number = -1;
};
+struct ObjectRow
+{
+ std::array<Object *, 5> m_objects = {};
+
+ template <std::size_t I> // read-only is enough for this
+ friend decltype(auto) get(const ObjectRow &row) { return row.m_objects[I]; }
+};
+
+namespace std {
+ template <> struct tuple_size<ObjectRow> : std::integral_constant<std::size_t, 5> {};
+ template <std::size_t I> struct tuple_element<I, ObjectRow> { using type = Object *; };
+}
+
// a class that can be both and requires disambiguation
class MetaObjectTuple : public QObject
{
diff --git a/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp b/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp
index 97122353ab3..3583583a16e 100644
--- a/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp
+++ b/tests/auto/corelib/itemmodels/qrangemodel/tst_qrangemodel.cpp
@@ -959,6 +959,29 @@ void tst_QRangeModel::autoConnectPolicy()
QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), child01Index);
QCOMPARE(dataChangedSpy.at(0).at(2), QVariant::fromValue(QList{Qt::UserRole + 1}));
}();
+
+ // build tests
+ { // make sure we don't kill the compiler with recursive templates
+ QList<std::array<Object *, 1000000>> wideList;
+ QRangeModel model(wideList);
+ }
+
+ { // work with custom tuple types
+ QList<ObjectRow> objectRows;
+ QRangeModel model(objectRows);
+ }
+
+ { // correctly resolve optional children
+ struct Protocol {
+ ObjectRow *parentRow(const ObjectRow &) const { return nullptr; }
+ const auto &childRows(const ObjectRow &) const { return emptyRow; }
+
+ std::optional<std::vector<ObjectRow>> emptyRow = std::nullopt;
+ };
+ std::vector<ObjectRow> objectTree;
+ QRangeModel model(objectTree, Protocol{});
+ model.setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full);
+ }
}
void tst_QRangeModel::dimensions()
@@ -1078,6 +1101,13 @@ void tst_QRangeModel::setData()
model->setData(first, oldValue, Qt::UserRole + 255);
}
+static constexpr bool fakedRole(int role)
+{
+ return role == Qt::EditRole
+ || role == Qt::RangeModelDataRole
+ || role == Qt::RangeModelDataRole + 1;
+}
+
void tst_QRangeModel::itemData()
{
QFETCH(Factory, factory);
@@ -1088,7 +1118,8 @@ void tst_QRangeModel::itemData()
const QModelIndex index = model->index(0, 0);
const QMap<int, QVariant> itemData = model->itemData(index);
for (int role = 0; role < Qt::UserRole; ++role) {
- if (role == Qt::EditRole || role == Qt::RangeModelDataRole) // we fake that in data()
+ // we fake those in data()
+ if (fakedRole(role))
continue;
QCOMPARE(itemData.value(role), index.data(role));
}
@@ -1113,7 +1144,7 @@ void tst_QRangeModel::setItemData()
itemData = {};
for (int role : roles) {
- if (role == Qt::EditRole || role == Qt::RangeModelDataRole) // faked
+ if (fakedRole(role)) // faked
continue;
QVariant data = role != Qt::DecorationRole ? QVariant(QStringLiteral("%1").arg(role))
: QVariant(QColor(Qt::magenta));
@@ -1139,7 +1170,7 @@ void tst_QRangeModel::setItemData()
}
for (int role = 0; role < Qt::UserRole; ++role) {
- if (role == Qt::EditRole || role == Qt::RangeModelDataRole) // faked role
+ if (fakedRole(role))
continue;
QVariant data = index.data(role);
diff --git a/tests/auto/corelib/itemmodels/qrangemodeladapter/CMakeLists.txt b/tests/auto/corelib/itemmodels/qrangemodeladapter/CMakeLists.txt
new file mode 100644
index 00000000000..b689265026f
--- /dev/null
+++ b/tests/auto/corelib/itemmodels/qrangemodeladapter/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2025 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+#####################################################################
+## tst_qrangemodel Test:
+#####################################################################
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_qrangemodel LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_qrangemodeladapter
+ SOURCES
+ tst_qrangemodeladapter.cpp
+ ../qrangemodel/data.h
+ LIBRARIES
+ Qt::Gui
+)
+
+if (
+ # INTEGRITY and VxWorks don't come with all the concepts headers
+ NOT INTEGRITY AND NOT VXWORKS AND
+ # gcc 10.2.0 chokes in a standard library header
+ (NOT CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 10.2.0))
+ if ("${CMAKE_CXX_COMPILE_FEATURES}" MATCHES "cxx_std_23")
+ set_property(TARGET tst_qrangemodeladapter PROPERTY CXX_STANDARD 23)
+ elseif ("${CMAKE_CXX_COMPILE_FEATURES}" MATCHES "cxx_std_20")
+ set_property(TARGET tst_qrangemodeladapter PROPERTY CXX_STANDARD 20)
+ endif()
+endif()
diff --git a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp
new file mode 100644
index 00000000000..4124b723b4c
--- /dev/null
+++ b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp
@@ -0,0 +1,2718 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#define Q_ASSERT(cond) ((cond) ? static_cast<void>(0) : qCritical(#cond))
+#define Q_ASSERT_X(cond, x, msg) ((cond) ? static_cast<void>(0) \
+ : qCritical("%s: %s returned false - %s", x, #cond, msg))
+
+#include "../qrangemodel/data.h"
+
+#include <QtTest/qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/qrangemodeladapter.h>
+#include <QtCore/qregularexpression.h>
+#include <QtCore/qxptype_traits.h>
+
+using namespace Qt::StringLiterals;
+
+class tst_QRangeModelAdapter : public QRangeModelTest
+{
+ Q_OBJECT
+public:
+ using QRangeModelTest::QRangeModelTest;
+
+ // compile tests
+ void setRange_API();
+
+ void indexOfRow_API();
+ void indexOfCell_API();
+ void indexOfPath_API();
+
+ void dimension_API();
+
+ void iterator_API();
+ void access_API();
+
+ void insertRow_API();
+ void insertRows_API();
+ void removeRow_API();
+ void removeRows_API();
+ void moveRow_API();
+ void moveRows_API();
+
+ void insertColumn_API();
+ void insertColumns_API();
+ void removeColumn_API();
+ void removeColumns_API();
+ void moveColumn_API();
+ void moveColumns_API();
+
+private slots:
+ void init()
+ {
+ m_data.reset(new Data);
+ }
+ void cleanup()
+ {
+ m_data.reset();
+ }
+
+ void modelLifetime();
+ void valueBehavior();
+ void modelReset();
+
+ void listIterate();
+ void listAccess();
+ void listWriteAccess();
+
+ void tableIterate();
+ void tableAccess();
+ void tableWriteAccess();
+
+ void treeIterate();
+ void treeAccess();
+ void treeWriteAccess();
+
+ void insertRow();
+ void insertRows();
+ void removeRow();
+ void removeRows();
+ void moveRow();
+ void moveRows();
+
+ void insertColumn();
+ void insertColumns();
+ void removeColumn();
+ void removeColumns();
+ void moveColumn();
+ void moveColumns();
+
+ void buildValueTree();
+ void buildPointerTree();
+
+ void insertAutoConnectObjects();
+
+private:
+ void expectInvalidIndex(int count)
+ {
+#ifndef QT_NO_DEBUG
+ static QRegularExpression invalidIndex{".* - Index at position is invalid"};
+
+ for (int i = 0; i < count; ++i) // at and DataRef accesses when testing out-of-bounds
+ QTest::ignoreMessage(QtCriticalMsg, invalidIndex);
+#else
+ Q_UNUSED(count);
+#endif
+ };
+
+ static value_tree createValueTree()
+ {
+ tree_row root[] = {
+ {"1", "one"},
+ {"2", "two"},
+ {"3", "three"},
+ {"4", "four"},
+ {"5", "five"},
+ };
+ value_tree tree{std::make_move_iterator(std::begin(root)),
+ std::make_move_iterator(std::end(root))};
+
+ tree[1].addChild("2.1", "two.one");
+ tree[1].addChild("2.2", "two.two");
+ tree_row &row23 = tree[1].addChild("2.3", "two.three");
+
+ row23.addChild("2.3.1", "two.three.one");
+ row23.addChild("2.3.2", "two.three.two");
+
+ return tree;
+ }
+
+ static pointer_tree createPointerTree()
+ {
+ pointer_tree pointerTree = {
+ new tree_row("1", "one"),
+ new tree_row("2", "two"),
+ new tree_row("3", "three"),
+ new tree_row("4", "four"),
+ new tree_row("5", "five"),
+ };
+
+ pointerTree.at(1)->addChildPointer("2.1", "two.one");
+ pointerTree.at(1)->addChildPointer("2.2", "two.two");
+ tree_row *row23 = pointerTree.at(1)->addChildPointer("2.3", "two.three");
+
+ row23->addChildPointer("2.3.1", "two.three.one");
+ row23->addChildPointer("2.3.2", "two.three.two");
+
+ return pointerTree;
+ }
+
+ std::unique_ptr<Data> m_data;
+};
+
+namespace {
+
+template <typename Range, typename Protocol>
+struct Adapter
+{
+ using type = decltype(QRangeModelAdapter(std::move(std::declval<Range>()), std::declval<Protocol>()));
+};
+
+template <typename Range>
+struct Adapter<Range, void>
+{
+ using type = decltype(QRangeModelAdapter(std::move(std::declval<Range>())));
+};
+
+template <typename Range, typename Protocol = void>
+using AdapterType = typename Adapter<Range, Protocol>::type;
+
+
+#define HAS_API(API) \
+template <typename Range> \
+static constexpr bool has_##API(Range &&) { return API##Test<Range, void>::value; } \
+template <typename Range, typename Protocol> \
+static constexpr bool has_##API(Range &&, Protocol &&) { return API##Test<Range, Protocol>::value; } \
+template <typename Range> auto API##_rt(Range &&) -> typename API##Test<Range, void>::return_type;
+
+#define API_TEST(API, METHOD) \
+template <typename Range, typename Protocol, typename = void> \
+struct API##Test : std::false_type { using return_type = std::nullptr_t; }; \
+template <typename Range, typename Protocol> \
+struct API##Test<Range, Protocol, \
+ std::void_t<decltype(std::declval<AdapterType<Range, Protocol>>(). METHOD)>> \
+ : std::true_type { \
+ using return_type = decltype(std::declval<AdapterType<Range, Protocol>>(). METHOD); \
+ }; \
+HAS_API(API)
+
+API_TEST(setRange, setRange({}))
+
+API_TEST(indexOfRow, index(0))
+API_TEST(indexOfCell, index(0, 0))
+API_TEST(indexOfPath, index(QList<int>{0, 0}, 0))
+
+API_TEST(columnCount, columnCount())
+API_TEST(rowCount, rowCount())
+API_TEST(treeRowCount, rowCount(0))
+API_TEST(branchRowCount, rowCount(QList<int>{0, 0}))
+API_TEST(hasChildren, hasChildren(0))
+API_TEST(treeHasChildren, hasChildren(QList<int>{0, 0}))
+
+API_TEST(at, at(0))
+API_TEST(subscript, operator[](0))
+API_TEST(tableAt, at(0, 0))
+API_TEST(tableSubscript, operator[](0, 0))
+API_TEST(treeRowAt, at(QList<int>{0, 0}))
+API_TEST(treeRowSubscript, operator[](QList<int>{0, 0}))
+API_TEST(treeValueAt, at(QList<int>{0, 0}, 0))
+API_TEST(treeValueSubscript, operator[](QList<int>{0, 0}, 0))
+
+API_TEST(insertTableRow, insertRow(0))
+API_TEST(insertTableRowWithData, insertRow(0, {}))
+API_TEST(insertTableRows, insertRows(0, std::declval<Range&>()))
+API_TEST(removeRow, removeRow(0))
+API_TEST(removeRows, removeRows(0, 0))
+API_TEST(moveRow, moveRow(0, 0))
+API_TEST(moveTreeRow, moveRow({0}, {0}))
+API_TEST(moveRows, moveRows(0, 0, 0))
+API_TEST(moveTreeRows, moveRows(QList<int>{0, 0}, 0, QList<int>{0, 0}))
+
+API_TEST(insertColumn, insertColumn(0))
+API_TEST(insertColumnWithData, insertColumn(0, {}))
+API_TEST(insertColumns, insertColumns(0, std::declval<Range&>()))
+API_TEST(removeColumn, removeColumn(0))
+API_TEST(removeColumns, removeColumns(0, 0))
+API_TEST(moveColumn, moveColumn(0, 0))
+API_TEST(moveTreeColumn, moveColumn(QList<int>{}, 0))
+API_TEST(moveColumns, moveColumns(0, 0, 0))
+API_TEST(moveTreeColumns, moveColumns(QList<int>{}, 0, 0))
+
+API_TEST(getCellProperty, at(0).get()->at(0)->number())
+API_TEST(setCellProperty, at(0).get()->at(0)->setNumber(5))
+
+API_TEST(getCellRefProperty, at(0).at(0)->number())
+API_TEST(setCellRefProperty, at(0).at(0)->setNumber(5))
+
+API_TEST(getListItemProperty, at(0)->number())
+API_TEST(setListItemProperty, at(0).get()->setNumber(5))
+}
+
+void tst_QRangeModelAdapter::setRange_API()
+{
+ Data d;
+ auto tree = value_tree{};
+ static_assert(has_setRange(d.vectorOfGadgets));
+ static_assert(has_setRange(std::move(d.tableOfRowPointers)));
+ static_assert(has_setRange(d.m_tree));
+ static_assert(has_setRange(tree));
+#if (!defined(Q_CC_GNU_ONLY) || Q_CC_GNU > 1303) && !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY)
+ static_assert(has_setRange(std::ref(tree)));
+#endif
+ static_assert(has_setRange(std::move(tree)));
+ static_assert(!has_setRange(std::as_const(d.vectorOfGadgets)));
+ static_assert(!has_setRange(std::as_const(tree)));
+}
+
+void tst_QRangeModelAdapter::indexOfRow_API()
+{
+ Data d;
+ static_assert(has_indexOfRow(d.fixedArrayOfNumbers));
+ static_assert(!has_indexOfRow(d.vectorOfGadgets)); // table
+ static_assert(has_indexOfRow(d.listOfMultiRoleGadgets));
+ static_assert(!has_indexOfRow(d.tableOfMetaObjectTuple));
+
+ // naughty cases: tuple<gadget> and tuple<object -> table with a single column
+ static_assert(!has_indexOfRow(d.listOfGadgets));
+ static_assert(!has_indexOfRow(d.listOfMetaObjectTuple));
+}
+
+void tst_QRangeModelAdapter::insertRow_API()
+{
+ Data d;
+ static_assert(!has_insertTableRow(d.fixedArrayOfNumbers));
+ static_assert(!has_insertTableRow(d.cArrayOfNumbers));
+ static_assert(has_insertTableRow(d.vectorOfFixedColumns));
+
+ static_assert(has_insertTableRow(d.vectorOfArrays));
+ static_assert(has_insertTableRow(d.vectorOfGadgets));
+ static_assert(has_insertTableRow(d.listOfGadgets));
+ static_assert(has_insertTableRow(d.listOfMultiRoleGadgets));
+ static_assert(has_insertTableRow(d.vectorOfStructs));
+ static_assert(has_insertTableRow(d.listOfObjects));
+ static_assert(has_insertTableRow(d.listOfMetaObjectTuple));
+ static_assert(has_insertTableRow(d.tableOfMetaObjectTuple));
+ static_assert(has_insertTableRow(d.vectorOfConstStructs));
+ static_assert(has_insertTableRow(d.tableOfNumbers));
+ static_assert(has_insertTableRow(d.tableOfPointers));
+ static_assert(has_insertTableRow(d.tableOfRowPointers));
+ static_assert(!has_insertTableRow(d.tableOfRowRefs));
+ static_assert(!has_insertTableRow(d.arrayOfConstNumbers));
+ static_assert(!has_insertTableRow(d.constListOfNumbers));
+ static_assert(!has_insertTableRow(d.constTableOfNumbers));
+ static_assert(has_insertTableRow(d.listOfNamedRoles));
+ static_assert(has_insertTableRow(d.tableOfEnumRoles));
+ static_assert(has_insertTableRow(d.tableOfIntRoles));
+ static_assert(has_insertTableRow(d.stdTableOfIntRoles));
+ static_assert(has_insertTableRow(d.stdTableOfIntRolesWithSharedRows));
+ static_assert(has_insertTableRow(d.m_tree));
+
+ // needs explicit protocol:
+ // static_assert(has_insertRow(d.m_pointer_tree));
+}
+
+void tst_QRangeModelAdapter::indexOfCell_API()
+{
+ Data d;
+ static_assert(!has_indexOfCell(d.fixedArrayOfNumbers));
+ static_assert(has_indexOfCell(d.vectorOfGadgets));
+ static_assert(!has_indexOfCell(d.listOfMultiRoleGadgets));
+
+ static_assert(has_indexOfCell(d.tableOfNumbers));
+ static_assert(has_indexOfCell(d.tableOfMetaObjectTuple));
+ static_assert(has_indexOfCell(d.m_tree));
+
+ // tuple<gadget> and tuple<object> -> table wiht a single column
+ static_assert(has_indexOfCell(d.listOfGadgets));
+ static_assert(has_indexOfCell(d.listOfMetaObjectTuple));
+}
+
+void tst_QRangeModelAdapter::indexOfPath_API()
+{
+ Data d;
+ static_assert(!has_indexOfPath(d.fixedArrayOfNumbers));
+ static_assert(!has_indexOfPath(d.listOfGadgets));
+ static_assert(!has_indexOfPath(d.listOfMultiRoleGadgets));
+ static_assert(!has_indexOfPath(d.listOfMetaObjectTuple));
+ static_assert(!has_indexOfPath(d.tableOfMetaObjectTuple));
+
+ static_assert(!has_indexOfPath(d.tableOfNumbers));
+ static_assert(!has_indexOfPath(d.tableOfMetaObjectTuple));
+ static_assert(has_indexOfPath(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::dimension_API()
+{
+ Data d;
+ {
+ // list
+ static_assert(has_columnCount(d.fixedArrayOfNumbers));
+ static_assert(has_rowCount(d.fixedArrayOfNumbers));
+ static_assert(!has_treeRowCount(d.fixedArrayOfNumbers));
+ static_assert(!has_branchRowCount(d.fixedArrayOfNumbers));
+ static_assert(!has_hasChildren(d.fixedArrayOfNumbers));
+ static_assert(!has_treeHasChildren(d.fixedArrayOfNumbers));
+
+ // tuple table
+ static_assert(has_columnCount(d.vectorOfFixedColumns));
+ static_assert(has_rowCount(d.vectorOfFixedColumns));
+ static_assert(!has_treeRowCount(d.vectorOfFixedColumns));
+ static_assert(!has_branchRowCount(d.vectorOfFixedColumns));
+ static_assert(!has_hasChildren(d.vectorOfFixedColumns));
+ static_assert(!has_treeHasChildren(d.vectorOfFixedColumns));
+
+ // gadget table
+ static_assert(has_columnCount(d.vectorOfGadgets));
+ static_assert(has_rowCount(d.vectorOfGadgets));
+ static_assert(!has_treeRowCount(d.vectorOfGadgets));
+ static_assert(!has_branchRowCount(d.vectorOfGadgets));
+ static_assert(!has_hasChildren(d.vectorOfGadgets));
+ static_assert(!has_treeHasChildren(d.vectorOfGadgets));
+
+ // tree
+ static_assert(has_columnCount(d.m_tree));
+ static_assert(has_rowCount(d.m_tree));
+ static_assert(has_treeRowCount(d.m_tree));
+ static_assert(has_branchRowCount(d.m_tree));
+ static_assert(has_hasChildren(d.m_tree));
+ static_assert(has_treeHasChildren(d.m_tree));
+ }
+}
+
+#define HAS_OPERATOR_TEST(Name, Op) \
+ template <typename It> using Name##_test = decltype(std::declval<It&>() Op std::declval<It&>())
+
+HAS_OPERATOR_TEST(LessThan, <);
+HAS_OPERATOR_TEST(GreaterThan, >);
+HAS_OPERATOR_TEST(LessThanOrEquals, <=);
+HAS_OPERATOR_TEST(GreaterThanOrEquals, <=);
+
+#define HAS_OPERATOR(It, Name) qxp::is_detected_v<Name##_test, It>
+
+#if defined (__cpp_concepts)
+template <typename RowType, typename MinCategory>
+static constexpr void iterator_API_helper()
+{
+ QRangeModelAdapter adapter = QRangeModelAdapter(std::vector<RowType>());
+ using Adapter = decltype(adapter);
+
+ // the row and column iterators always model random access
+ using row_iterator = typename Adapter::iterator;
+ static_assert(std::random_access_iterator<row_iterator>);
+ using const_row_iterator = typename Adapter::const_iterator;
+ static_assert(std::random_access_iterator<const_row_iterator>);
+
+ using column_iterator = typename Adapter::ColumnIterator;
+ static_assert(std::random_access_iterator<column_iterator>);
+ using const_column_iterator = typename Adapter::ConstColumnIterator;
+ static_assert(std::random_access_iterator<const_column_iterator>);
+
+ // the iterator for the view of a row models the same category as the
+ // row itself; at least forward iterator
+ using rowtype_iterator = typename RowType::iterator;
+ using rowview_iterator = decltype(adapter.at(0).get().begin());
+
+ static_assert(std::is_base_of_v<MinCategory,
+ typename std::iterator_traits<rowtype_iterator>::iterator_category>);
+ static_assert(std::forward_iterator<rowview_iterator>);
+ static_assert(std::bidirectional_iterator<rowview_iterator>
+ == std::bidirectional_iterator<rowtype_iterator>);
+ static_assert(std::random_access_iterator<rowview_iterator>
+ == std::random_access_iterator<rowtype_iterator>);
+
+ static_assert(HAS_OPERATOR(rowview_iterator, LessThan)
+ == HAS_OPERATOR(rowtype_iterator, LessThan));
+ static_assert(HAS_OPERATOR(rowview_iterator, GreaterThan)
+ == HAS_OPERATOR(rowtype_iterator, GreaterThan));
+ static_assert(HAS_OPERATOR(rowview_iterator, LessThanOrEquals)
+ == HAS_OPERATOR(rowtype_iterator, LessThanOrEquals));
+ static_assert(HAS_OPERATOR(rowview_iterator, GreaterThanOrEquals)
+ == HAS_OPERATOR(rowtype_iterator, GreaterThanOrEquals));
+}
+#endif
+
+void tst_QRangeModelAdapter::iterator_API()
+{
+#if defined (__cpp_concepts)
+ {
+ using Row = std::array<int, 5>;
+ iterator_API_helper<Row, std::random_access_iterator_tag>();
+ }
+ {
+ using Row = std::vector<MultiRoleGadget *>;
+ iterator_API_helper<Row, std::random_access_iterator_tag>();
+ }
+
+ {
+ using Row = std::list<std::shared_ptr<MultiRoleGadget>>;
+ iterator_API_helper<Row, std::bidirectional_iterator_tag>();
+ }
+#endif
+}
+
+
+#define has_with_type(fn, Range, Ret) \
+ has_##fn(Range) && std::is_same_v<decltype(fn##_rt( Range)), Ret>
+
+template <typename Range>
+static constexpr auto iterator_type(Range r) -> decltype(QRangeModelAdapter(std::move(r)).begin());
+
+template <typename Range>
+static constexpr auto rowref_type(Range r) -> decltype(iterator_type(std::move(r)).operator*());
+
+template <typename Range>
+static constexpr auto dataref_type(Range r) -> typename decltype(QRangeModelAdapter(std::move(r)))::DataReference;
+
+void tst_QRangeModelAdapter::access_API()
+{
+ Data d;
+ { // list: std::array<int, 5>
+ using data_ref = decltype(dataref_type(d.fixedArrayOfNumbers));
+ static_assert(has_with_type(at, d.fixedArrayOfNumbers, data_ref));
+ static_assert(has_with_type(at, std::as_const(d.fixedArrayOfNumbers), int));
+ static_assert(has_with_type(subscript, d.fixedArrayOfNumbers, data_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.fixedArrayOfNumbers), int));
+
+ static_assert(!has_tableAt(d.fixedArrayOfNumbers));
+ static_assert(!has_treeRowAt(d.fixedArrayOfNumbers));
+ static_assert(!has_treeValueAt(d.fixedArrayOfNumbers));
+ }
+
+ { // list: int[5]
+ using data_ref = decltype(QRangeModelAdapter(std::move(d.cArrayOfNumbers)))::DataReference;
+ static_assert(has_with_type(at, d.cArrayOfNumbers, data_ref));
+ static_assert(has_with_type(at, std::as_const(d.cArrayOfNumbers), int));
+ static_assert(has_with_type(subscript, d.cArrayOfNumbers, data_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.cArrayOfNumbers), int));
+
+ static_assert(!has_tableAt(d.cArrayOfNumbers));
+ static_assert(!has_treeRowAt(d.cArrayOfNumbers));
+ static_assert(!has_treeValueAt(d.cArrayOfNumbers));
+ }
+
+ { // table: vector of tuple
+ using row_ref = decltype(rowref_type(d.vectorOfFixedColumns));
+ using row_type = std::tuple<int, QString>;
+ using data_ref = decltype(dataref_type(d.vectorOfFixedColumns));
+ static_assert(has_with_type(at, d.vectorOfFixedColumns, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.vectorOfFixedColumns), const row_type &));
+ static_assert(has_with_type(subscript, d.vectorOfFixedColumns, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.vectorOfFixedColumns), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.vectorOfFixedColumns, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.vectorOfFixedColumns), QVariant));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.vectorOfFixedColumns, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.vectorOfFixedColumns), QVariant));
+#endif
+
+ static_assert(!has_treeRowAt(d.vectorOfFixedColumns));
+ static_assert(!has_treeValueAt(d.vectorOfFixedColumns));
+ }
+
+ { // table: vector of shared_ptr<tuple>
+ using row_type = std::shared_ptr<std::tuple<int, QString>>;
+ using row_ref = decltype(rowref_type(d.vectorOfFixedSPtrColumns));
+ using data_ref = decltype(dataref_type(d.vectorOfFixedSPtrColumns));
+ static_assert(has_with_type(at, d.vectorOfFixedSPtrColumns, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.vectorOfFixedSPtrColumns), const row_type &));
+ static_assert(has_with_type(subscript, d.vectorOfFixedSPtrColumns, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.vectorOfFixedSPtrColumns), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.vectorOfFixedSPtrColumns, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.vectorOfFixedSPtrColumns), QVariant));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.vectorOfFixedSPtrColumns, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.vectorOfFixedSPtrColumns), QVariant));
+#endif
+
+ static_assert(!has_treeRowAt(d.vectorOfFixedSPtrColumns));
+ static_assert(!has_treeValueAt(d.vectorOfFixedSPtrColumns));
+ }
+
+#ifndef Q_OS_INTEGRITY
+ { // table: std::vector<std::array<int, 10>>
+ using row_type = std::array<int, 10>;
+ using row_ref = decltype(rowref_type(d.vectorOfArrays));
+ using data_ref = decltype(dataref_type(d.vectorOfArrays));
+ static_assert(has_with_type(at, d.vectorOfArrays, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.vectorOfArrays), const row_type &));
+ static_assert(has_with_type(subscript, d.vectorOfArrays, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.vectorOfArrays), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.vectorOfArrays, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.vectorOfArrays), int));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.vectorOfArrays, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.vectorOfArrays), int));
+#endif
+
+ static_assert(!has_treeRowAt(d.vectorOfArrays));
+ static_assert(!has_treeValueAt(d.vectorOfArrays));
+ }
+#endif
+
+ { // table: std::vector<Item>
+ using row_ref = decltype(rowref_type(d.vectorOfGadgets));
+ using data_ref = decltype(dataref_type(d.vectorOfGadgets));
+ static_assert(has_with_type(at, d.vectorOfGadgets, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.vectorOfGadgets), const Item &));
+ static_assert(has_with_type(subscript, d.vectorOfGadgets, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.vectorOfGadgets), const Item &));
+
+ static_assert(has_with_type(tableAt, d.vectorOfGadgets, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.vectorOfGadgets), QVariant));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.vectorOfGadgets, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.vectorOfGadgets), QVariant));
+#endif
+
+ static_assert(!has_treeRowAt(d.vectorOfGadgets));
+ static_assert(!has_treeValueAt(d.vectorOfGadgets));
+ }
+
+ { // 1-column table: std::vector<std::tuple<Item>>
+ using row_type = std::tuple<Item>;
+ using row_ref = decltype(rowref_type(d.listOfGadgets));
+ using data_ref = decltype(dataref_type(d.listOfGadgets));
+ static_assert(has_with_type(at, d.listOfGadgets, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.listOfGadgets), const row_type &));
+ static_assert(has_with_type(subscript, d.listOfGadgets, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.listOfGadgets), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.listOfGadgets, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.listOfGadgets), Item));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.listOfGadgets, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.listOfGadgets), Item));
+#endif
+
+ static_assert(!has_treeRowAt(d.listOfGadgets));
+ static_assert(!has_treeValueAt(d.listOfGadgets));
+ }
+
+ { // list: std::vector<MultiRoleGadget>
+ using row_type = MultiRoleGadget;
+ using data_ref = decltype(dataref_type(d.listOfMultiRoleGadgets));
+ static_assert(has_with_type(at, d.listOfMultiRoleGadgets, data_ref));
+ static_assert(has_with_type(at, std::as_const(d.listOfMultiRoleGadgets), const row_type &));
+ static_assert(has_with_type(subscript, d.listOfMultiRoleGadgets, data_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.listOfMultiRoleGadgets), const row_type &));
+
+ static_assert(!has_tableAt(d.listOfMultiRoleGadgets));
+ static_assert(!has_treeRowAt(d.listOfMultiRoleGadgets));
+ static_assert(!has_treeValueAt(d.listOfMultiRoleGadgets));
+
+ static_assert(has_getListItemProperty(d.listOfMultiRoleGadgets));
+ static_assert(!has_setListItemProperty(d.listOfMultiRoleGadgets));
+ }
+
+#ifndef Q_OS_INTEGRITY
+ { // list: std::vector<ItemAccessType>
+ using row_type = ItemAccessType;
+ using data_ref = decltype(dataref_type(d.vectorOfItemAccess));
+ static_assert(has_with_type(at, d.vectorOfItemAccess, data_ref));
+ static_assert(has_with_type(at, std::as_const(d.vectorOfItemAccess), row_type));
+ static_assert(has_with_type(subscript, d.vectorOfItemAccess, data_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.vectorOfItemAccess), row_type));
+
+ static_assert(!has_tableAt(d.vectorOfItemAccess));
+ static_assert(!has_treeRowAt(d.listOfGadgets));
+ static_assert(!has_treeValueAt(d.listOfGadgets));
+ }
+#endif
+
+#if (!defined(Q_CC_GNU_ONLY) || Q_CC_GNU > 1303) && !defined(Q_OS_VXWORKS) && !defined(Q_OS_INTEGRITY)
+ { // table: std::list<Object *>
+ using row_ref = decltype(rowref_type(std::ref(d.listOfObjects)));
+ using data_ref = decltype(dataref_type(std::ref(d.listOfObjects)));
+ static_assert(has_with_type(at, std::ref(d.listOfObjects), row_ref));
+ static_assert(has_with_type(at, std::ref(std::as_const(d.listOfObjects)), Object *const &));
+ static_assert(has_with_type(subscript, std::ref(d.listOfObjects), row_ref));
+ static_assert(has_with_type(subscript, std::ref(std::as_const(d.listOfObjects)), Object *const &));
+
+ static_assert(has_with_type(tableAt, std::ref(d.listOfObjects), data_ref));
+ static_assert(has_with_type(tableAt, std::ref(std::as_const(d.listOfObjects)), QVariant));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, std::ref(d.listOfObjects), data_ref));
+ static_assert(has_with_type(tableSubscript, std::ref(std::as_const(d.listOfObjects)), QVariant));
+#endif
+
+ static_assert(!has_treeRowAt(std::ref(d.listOfObjects)));
+ static_assert(!has_treeValueAt(std::ref(d.listOfObjects)));
+ }
+#endif
+
+ { // table: std::vector<std::vector<double>>
+ using row_type = std::vector<double>;
+ using row_ref = decltype(rowref_type(d.tableOfNumbers));
+ using data_ref = decltype(dataref_type(d.tableOfNumbers));
+ static_assert(has_with_type(at, d.tableOfNumbers, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.tableOfNumbers), const row_type &));
+ static_assert(has_with_type(subscript, d.tableOfNumbers, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.tableOfNumbers), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.tableOfNumbers, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.tableOfNumbers), double));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.tableOfNumbers, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.tableOfNumbers), double));
+#endif
+
+ static_assert(!has_treeRowAt(d.tableOfNumbers));
+ static_assert(!has_treeValueAt(d.tableOfNumbers));
+ }
+
+ { // table: std::vector<std::vector<Item *>>
+ using row_type = std::vector<Item *>;
+ using row_ref = decltype(rowref_type(d.tableOfPointers));
+ using data_ref = decltype(dataref_type(d.tableOfPointers));
+ static_assert(has_with_type(at, d.tableOfPointers, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.tableOfPointers), const row_type &));
+ static_assert(has_with_type(subscript, d.tableOfPointers, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.tableOfPointers), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.tableOfPointers, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.tableOfPointers), const Item *));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.tableOfPointers, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.tableOfPointers), const Item *));
+#endif
+
+ static_assert(!has_treeRowAt(d.tableOfPointers));
+ static_assert(!has_treeValueAt(d.tableOfPointers));
+ }
+
+ { // table: std::vector<std::ref<Row>>
+ using row_type = std::reference_wrapper<Row>;
+ using row_ref = decltype(rowref_type(d.tableOfRowRefs));
+ using data_ref = decltype(dataref_type(d.tableOfRowRefs));
+ static_assert(has_with_type(at, d.tableOfRowRefs, row_ref));
+ static_assert(has_with_type(at, std::as_const(d.tableOfRowRefs), const row_type &));
+ static_assert(has_with_type(subscript, d.tableOfRowRefs, row_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.tableOfRowRefs), const row_type &));
+
+ static_assert(has_with_type(tableAt, d.tableOfRowRefs, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(d.tableOfRowRefs), QVariant));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.tableOfRowRefs, data_ref));
+ static_assert(has_with_type(tableSubscript, std::as_const(d.tableOfRowRefs), QVariant));
+#endif
+
+ static_assert(!has_treeRowAt(d.tableOfRowRefs));
+ static_assert(!has_treeValueAt(d.tableOfRowRefs));
+ }
+
+ { // table of shared rows holding shared objects
+ using data_type = std::shared_ptr<Object>;
+ using row_type = std::shared_ptr<std::vector<data_type>>;
+ std::vector<row_type> table;
+ using row_ref = decltype(rowref_type(table));
+ using data_ref = decltype(dataref_type(table));
+ static_assert(has_with_type(at, table, row_ref));
+
+ static_assert(has_with_type(at, std::as_const(table), const row_type &));
+ static_assert(has_with_type(tableAt, table, data_ref));
+ static_assert(has_with_type(tableAt, std::as_const(table), std::shared_ptr<const Object>));
+ }
+
+ { // table of raw rows holding raw objects
+ using data_type = Object *;
+ using row_type = std::vector<data_type> *;
+ std::vector<row_type> table;
+ using row_ref = decltype(rowref_type(&table));
+ using data_ref = decltype(dataref_type(&table));
+ static_assert(has_with_type(at, &table, row_ref));
+ static_assert(has_with_type(at, &std::as_const(table), const row_type &));
+ static_assert(has_with_type(tableAt, &table, data_ref));
+ static_assert(has_with_type(tableAt, &std::as_const(table), const Object *));
+
+ static_assert(has_getCellProperty(table));
+ // we turn row pointers into pointers to const rows, but we don't make
+ // the element of that pointer also const... ###
+ static_assert(has_setCellProperty(table));
+ }
+
+ { // table of rows holding shared pointers
+ using data_type = Object *;
+ using row_type = std::vector<data_type>;
+ std::vector<row_type> table;
+
+ QRangeModelAdapter adapter(std::ref(table));
+ adapter.at(0).at(0)->number();
+ static_assert(has_getCellRefProperty(table));
+ static_assert(!has_setCellRefProperty(table));
+ }
+
+ { // list: std::vector<QVariantMap>
+ using row_type = QVariantMap;
+ using data_ref = decltype(dataref_type(d.listOfNamedRoles));
+ static_assert(has_with_type(at, d.listOfNamedRoles, data_ref));
+ static_assert(has_with_type(at, std::as_const(d.listOfNamedRoles), row_type));
+ static_assert(has_with_type(subscript, d.listOfNamedRoles, data_ref));
+ static_assert(has_with_type(subscript, std::as_const(d.listOfNamedRoles), row_type));
+
+ static_assert(!has_tableAt(d.listOfNamedRoles));
+ static_assert(!has_treeRowAt(d.listOfNamedRoles));
+ static_assert(!has_treeValueAt(d.listOfNamedRoles));
+ }
+
+ { // tree: std::vector<tree_row>
+ const value_tree const_tree;
+ using row_type = tree_row;
+ using row_ref = decltype(QRangeModelAdapter(std::move(d.m_tree)).at(0));
+ using data_ref = decltype(dataref_type(std::move(d.m_tree)));
+
+ static_assert(has_with_type(at, d.m_tree, row_ref));
+ static_assert(has_with_type(at, const_tree, const row_type &));
+ static_assert(has_with_type(subscript, d.m_tree, row_ref));
+ static_assert(has_with_type(subscript, const_tree, const row_type &));
+
+ static_assert(has_with_type(tableAt, d.m_tree, data_ref));
+ static_assert(has_with_type(tableAt, const_tree, QString));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(tableSubscript, d.m_tree, data_ref));
+ static_assert(has_with_type(tableSubscript, const_tree, QString));
+#endif
+
+ static_assert(has_with_type(treeRowAt, d.m_tree, row_ref));
+ // not a const ref, but a view of the row
+ static_assert(has_with_type(treeRowAt, const_tree, const row_type &));
+
+ static_assert(has_with_type(treeValueAt, d.m_tree, data_ref));
+ static_assert(has_with_type(treeValueAt, const_tree, QString));
+#if defined(__cpp_multidimensional_subscript)
+ static_assert(has_with_type(treeValueSubscript, d.m_tree, data_ref));
+ static_assert(has_with_type(treeValueSubscript, const_tree, QString));
+#endif
+ }
+}
+
+
+void tst_QRangeModelAdapter::insertRows_API()
+{
+ Data d;
+ static_assert(!has_insertTableRows(d.fixedArrayOfNumbers));
+ static_assert(has_insertTableRows(d.vectorOfGadgets));
+ static_assert(has_insertTableRows(d.listOfMultiRoleGadgets));
+ static_assert(has_insertTableRows(d.listOfNamedRoles));
+ static_assert(has_insertTableRows(d.listOfObjects));
+ static_assert(has_insertTableRows(d.stdTableOfIntRoles));
+
+ static_assert(has_insertTableRowWithData(d.vectorOfFixedColumns));
+}
+
+void tst_QRangeModelAdapter::removeRow_API()
+{
+ Data d;
+ static_assert(!has_removeRow(d.fixedArrayOfNumbers));
+ static_assert(has_removeRow(d.vectorOfGadgets));
+ static_assert(!has_removeRow(d.constListOfNumbers));
+ static_assert(has_removeRow(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::removeRows_API()
+{
+ Data d;
+ static_assert(!has_removeRows(d.fixedArrayOfNumbers));
+ static_assert(has_removeRows(d.vectorOfGadgets));
+ static_assert(!has_removeRows(d.constListOfNumbers));
+ static_assert(has_removeRows(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::moveRow_API()
+{
+ Data d;
+ static_assert(has_moveRow(d.fixedArrayOfNumbers));
+ static_assert(has_moveRow(d.vectorOfGadgets));
+ static_assert(!has_moveRow(d.constListOfNumbers));
+ static_assert(has_moveRow(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::moveRows_API()
+{
+ Data d;
+ static_assert(has_moveRows(d.fixedArrayOfNumbers));
+ static_assert(has_moveRows(d.vectorOfGadgets));
+ static_assert(!has_moveRows(d.constListOfNumbers));
+ static_assert(has_moveRows(d.m_tree));
+ static_assert(!has_moveTreeRows(d.vectorOfGadgets));
+ static_assert(has_moveTreeRows(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::insertColumn_API()
+{
+ Data d;
+ static_assert(!has_insertColumn(d.fixedArrayOfNumbers));
+ static_assert(!has_insertColumn(d.vectorOfFixedColumns));
+ static_assert(!has_insertColumn(d.vectorOfArrays));
+ static_assert(!has_insertColumn(d.vectorOfGadgets));
+ static_assert(!has_insertColumn(d.vectorOfConstStructs));
+
+ static_assert(has_insertColumn(d.tableOfNumbers));
+ static_assert(!has_insertColumn(d.constTableOfNumbers));
+ static_assert(has_insertColumn(d.tableOfPointers));
+ static_assert(!has_insertColumn(d.tableOfRowPointers));
+ static_assert(!has_insertColumn(d.listOfNamedRoles));
+ static_assert(!has_insertColumn(d.m_tree));
+
+ static_assert(has_insertColumnWithData(d.tableOfNumbers));
+ static_assert(!has_insertColumnWithData(d.constTableOfNumbers));
+ static_assert(has_insertColumnWithData(d.tableOfPointers));
+}
+
+void tst_QRangeModelAdapter::insertColumns_API()
+{
+ Data d;
+ static_assert(!has_insertColumns(d.fixedArrayOfNumbers));
+ static_assert(!has_insertColumns(d.vectorOfFixedColumns));
+ static_assert(!has_insertColumns(d.vectorOfArrays));
+ static_assert(!has_insertColumns(d.vectorOfGadgets));
+ static_assert(!has_insertColumns(d.vectorOfConstStructs));
+
+ static_assert(has_insertColumns(d.tableOfNumbers));
+ static_assert(!has_insertColumns(d.constTableOfNumbers));
+ static_assert(has_insertColumns(d.tableOfPointers));
+ static_assert(!has_insertColumns(d.tableOfRowPointers));
+ static_assert(!has_insertColumns(d.listOfNamedRoles));
+ static_assert(!has_insertColumns(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::removeColumn_API()
+{
+ Data d;
+ static_assert(!has_removeColumn(d.fixedArrayOfNumbers));
+ static_assert(!has_removeColumn(d.vectorOfFixedColumns));
+ static_assert(!has_removeColumn(d.vectorOfArrays));
+ static_assert(!has_removeColumn(d.vectorOfGadgets));
+ static_assert(!has_removeColumn(d.vectorOfConstStructs));
+
+ static_assert(has_removeColumn(d.tableOfNumbers));
+ static_assert(!has_removeColumn(d.constTableOfNumbers));
+ static_assert(has_removeColumn(d.tableOfPointers));
+ static_assert(!has_removeColumn(d.tableOfRowPointers));
+ static_assert(!has_removeColumn(d.listOfNamedRoles));
+ static_assert(!has_removeColumn(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::removeColumns_API()
+{
+ Data d;
+ static_assert(!has_removeColumns(d.fixedArrayOfNumbers));
+ static_assert(!has_removeColumns(d.vectorOfFixedColumns));
+ static_assert(!has_removeColumns(d.vectorOfArrays));
+ static_assert(!has_removeColumns(d.vectorOfGadgets));
+ static_assert(!has_removeColumns(d.vectorOfConstStructs));
+
+ static_assert(has_removeColumns(d.tableOfNumbers));
+ static_assert(!has_removeColumns(d.constTableOfNumbers));
+ static_assert(has_removeColumns(d.tableOfPointers));
+ static_assert(!has_removeColumns(d.tableOfRowPointers));
+ static_assert(!has_removeColumns(d.listOfNamedRoles));
+ static_assert(!has_removeColumns(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::moveColumn_API()
+{
+ Data d;
+ static_assert(!has_moveColumn(d.fixedArrayOfNumbers));
+ static_assert(!has_moveColumn(d.vectorOfFixedColumns));
+ static_assert(!has_moveColumn(d.vectorOfGadgets));
+ static_assert(!has_moveColumn(d.vectorOfConstStructs));
+
+ static_assert(has_moveColumn(d.vectorOfArrays));
+ static_assert(has_moveColumn(d.tableOfNumbers));
+ static_assert(!has_moveColumn(d.constTableOfNumbers));
+ static_assert(has_moveColumn(d.tableOfPointers));
+ static_assert(!has_moveColumn(d.tableOfRowPointers));
+ static_assert(!has_moveColumn(d.listOfNamedRoles));
+ static_assert(!has_moveColumn(d.m_tree));
+
+ static_assert(!has_moveTreeColumn(d.m_tree));
+}
+
+void tst_QRangeModelAdapter::moveColumns_API()
+{
+ Data d;
+ static_assert(!has_moveColumns(d.fixedArrayOfNumbers));
+ static_assert(!has_moveColumns(d.vectorOfFixedColumns));
+ static_assert(!has_moveColumns(d.vectorOfGadgets));
+ static_assert(!has_moveColumns(d.vectorOfConstStructs));
+
+ static_assert(has_moveColumns(d.vectorOfArrays));
+ static_assert(has_moveColumns(d.tableOfNumbers));
+ static_assert(!has_moveColumns(d.constTableOfNumbers));
+ static_assert(has_moveColumns(d.tableOfPointers));
+ static_assert(!has_moveColumns(d.tableOfRowPointers));
+ static_assert(!has_moveColumns(d.listOfNamedRoles));
+ static_assert(!has_moveColumns(d.m_tree));
+
+ static_assert(!has_moveTreeColumns(d.m_tree));
+}
+
+
+void tst_QRangeModelAdapter::modelLifetime()
+{
+ std::vector<int> data;
+ QPointer<QRangeModel> model;
+ QPointer<QRangeModel> model2;
+
+ {
+ QRangeModelAdapter adapter(&data);
+ model = adapter.model();
+ QVERIFY(model);
+ }
+ QVERIFY(!model);
+
+ {
+ auto adapter = QRangeModelAdapter(&data);
+ model = adapter.model();
+ QVERIFY(model);
+
+ {
+ auto adapterCopy = adapter;
+ QVERIFY(model);
+ QCOMPARE(adapterCopy.model(), adapter.model());
+
+ {
+ std::vector<int> data2;
+ adapterCopy = QRangeModelAdapter(&data2);
+ model2 = adapterCopy.model();
+ QVERIFY(model2);
+ QCOMPARE_NE(adapterCopy.model(), adapter.model());
+ }
+ QVERIFY(model2);
+ }
+ QVERIFY(!model2);
+ QVERIFY(model);
+
+ auto movedToAdapter = std::move(adapter);
+ QVERIFY(!adapter.model());
+ QVERIFY(movedToAdapter.model());
+ QVERIFY(model);
+ }
+ QVERIFY(!model);
+}
+
+void tst_QRangeModelAdapter::valueBehavior()
+{
+ QRangeModelAdapter adapter(QList<int>{});
+ // make sure we don't construct from range, but make a copy
+ QRangeModelAdapter adapter2(adapter);
+ static_assert(std::is_same_v<decltype(adapter), decltype(adapter2)>);
+ QCOMPARE(adapter.model(), adapter2.model());
+ auto copy = adapter;
+ static_assert(std::is_same_v<decltype(adapter), decltype(copy)>);
+ QCOMPARE(adapter, copy);
+ QCOMPARE(copy.model(), adapter.model());
+ auto movedTo = std::move(adapter);
+ QCOMPARE(movedTo, copy);
+ QCOMPARE_NE(movedTo, adapter);
+ QVERIFY(!adapter.model());
+}
+
+void tst_QRangeModelAdapter::modelReset()
+{
+ {
+ QRangeModelAdapter adapter(std::vector<int>{});
+ QSignalSpy modelAboutToBeResetSpy(adapter.model(), &QAbstractItemModel::modelAboutToBeReset);
+ QSignalSpy modelResetSpy(adapter.model(), &QAbstractItemModel::modelReset);
+
+ QCOMPARE(adapter.range(), std::vector<int>());
+
+ adapter.setRange(std::vector<int>{1, 2, 3, 4, 5});
+ QCOMPARE(modelAboutToBeResetSpy.count(), 1);
+ QCOMPARE(modelResetSpy.count(), 1);
+
+ QCOMPARE(adapter.rowCount(), 5);
+ QCOMPARE(adapter[0], 1);
+
+ adapter.setRange({3, 2, 1});
+ QCOMPARE(modelAboutToBeResetSpy.count(), 2);
+ QCOMPARE(modelResetSpy.count(), 2);
+ QCOMPARE(adapter.rowCount(), 3);
+ QCOMPARE(adapter[0], 3);
+
+ QCOMPARE(adapter, (std::vector<int>{3, 2, 1}));
+
+ std::vector<int> modifiedData = adapter;
+ }
+
+ {
+ Object *object = new Object;
+ QPointer<Object> watcher = object;
+
+ QRangeModelAdapter adapter(QList<Object *>{object});
+ adapter = {};
+ QVERIFY(!watcher);
+ }
+
+ {
+ QRangeModelAdapter adapter(createValueTree());
+ adapter.at(0) = tree_row{};
+ QCOMPARE(std::as_const(adapter).at(0, 0), "");
+ QCOMPARE(std::as_const(adapter).at(0, 1), "");
+ adapter.setRange(createValueTree());
+ QCOMPARE(std::as_const(adapter).at(0, 0), "1");
+ QCOMPARE(std::as_const(adapter).at(0, 1), "one");
+ }
+
+ {
+ QStringList list;
+ QRangeModelAdapter adapter(list);
+ auto setList = [](const QStringList &) {};
+ setList(adapter);
+ QVariant var = list;
+ }
+}
+
+void tst_QRangeModelAdapter::listIterate()
+{
+ {
+ std::vector<int> data = {0, 1, 2, 3, 4};
+ QRangeModelAdapter adapter(std::ref(data));
+
+ QCOMPARE(adapter.end() - adapter.begin(), 5);
+ QCOMPARE(adapter.end() - adapter.end(), 0);
+ QCOMPARE(adapter.begin() - adapter.end(), -5);
+
+ // test special handling of moving back from end()
+ auto end = adapter.end();
+ QCOMPARE(*(--end), 4);
+ end = adapter.end();
+ QCOMPARE(end--, adapter.end());
+ QCOMPARE(*end, 4);
+ end = adapter.end();
+ end -= 2;
+ QCOMPARE(*end, 3);
+ QCOMPARE(*(adapter.end() - 1), 4);
+
+ std::vector<int> values;
+ for (const auto &d : std::as_const(adapter))
+ values.push_back(d);
+ QCOMPARE(values, data);
+
+ for (auto d : adapter)
+ d = d + 1;
+ QCOMPARE(data, (std::vector{1, 2, 3, 4, 5}));
+ }
+}
+
+void tst_QRangeModelAdapter::listAccess()
+{
+ {
+ std::vector<int> data = {0, 1, 2, 3, 4};
+ const int size = int(data.size());
+
+ {
+ QRangeModelAdapter adapter(data);
+ QCOMPARE(adapter.at(1), 1);
+ QCOMPARE(adapter.data(1).metaType(), QMetaType::fromType<int>());
+ QCOMPARE(adapter.data(1), 1);
+ QCOMPARE(adapter[1], 1);
+ QCOMPARE(adapter.at(4), 4);
+ QCOMPARE(adapter.data(4), 4);
+ swap(adapter[0], adapter[4]);
+ QCOMPARE(adapter.data(4), 0);
+ QCOMPARE(adapter.data(0), 4);
+ QVERIFY(adapter.setData(0, QVariant(0)));
+ QVERIFY(adapter.setData(4, QVariant(4)));
+ expectInvalidIndex(3); // out-of-bounds access of vector and DataRef
+ QCOMPARE(adapter.at(size), 0);
+ }
+ {
+ QRangeModelAdapter adapter(std::as_const(data));
+ QCOMPARE(adapter.at(1), 1);
+ QCOMPARE(adapter.data(1), 1);
+ QCOMPARE(adapter[1], 1);
+ QCOMPARE(adapter.at(4), 4);
+ expectInvalidIndex(1); // out-of-bounds access of vector
+ QCOMPARE(adapter.at(size), 0);
+ }
+ {
+ const QRangeModelAdapter adapter(data);
+ QCOMPARE(adapter.at(1), 1);
+ QCOMPARE(adapter.data(1), 1);
+ QCOMPARE(adapter[1], 1);
+ QCOMPARE(adapter.at(4), 4);
+ expectInvalidIndex(1); // out-of-bounds access of vector
+ QCOMPARE(adapter.at(size), 0);
+ }
+ {
+ const QRangeModelAdapter adapter(std::as_const(data));
+ QCOMPARE(adapter.at(1), 1);
+ QCOMPARE(adapter.data(1), 1);
+ QCOMPARE(adapter[1], 1);
+ QCOMPARE(adapter.at(4), 4);
+ expectInvalidIndex(1); // out-of-bounds access of vector
+ QCOMPARE(adapter[size], 0);
+ }
+ }
+
+ { // this is a table (std::vector<Item>)
+ QList<Item> gadgets = {m_data->vectorOfGadgets.begin(), m_data->vectorOfGadgets.end()};
+
+ {
+ const QRangeModelAdapter adapter(gadgets);
+ QCOMPARE(adapter.at(1), gadgets.at(1));
+ QCOMPARE(adapter.data(1, 0).metaType(), QMetaType::fromType<QString>());
+ QCOMPARE(adapter.data(1, 1).metaType(), QMetaType::fromType<QColor>());
+ QCOMPARE(adapter.data(1, 2).metaType(), QMetaType::fromType<QString>());
+ QCOMPARE(adapter[1], gadgets[1]);
+ QCOMPARE(adapter.at(2), gadgets.at(2));
+ }
+ }
+
+ {
+ auto gadgets = m_data->listOfMultiRoleGadgets;
+ const int size = int(gadgets.size());
+
+ {
+ const QRangeModelAdapter adapter(gadgets);
+ QCOMPARE(adapter.at(0), gadgets.at(0));
+ QCOMPARE(adapter.data(0).metaType(), QMetaType::fromType<MultiRoleGadget>());
+ QCOMPARE(adapter.data(0).value<MultiRoleGadget>(), gadgets.at(0));
+ QCOMPARE(adapter.data(0, Qt::DisplayRole), gadgets.at(0).m_display);
+ QCOMPARE(adapter.data(1, Qt::DecorationRole), gadgets.at(1).m_decoration);
+ QCOMPARE(adapter.data(2, Qt::UserRole), gadgets.at(2).number());
+ QCOMPARE(adapter.data(2, Qt::UserRole + 1), gadgets.at(2).m_user);
+ QCOMPARE(adapter.at(size - 1), gadgets.at(size - 1));
+ expectInvalidIndex(1); // access of vector
+ QCOMPARE(adapter.at(size), MultiRoleGadget{});
+ }
+ }
+}
+
+void tst_QRangeModelAdapter::listWriteAccess()
+{
+ auto gadgets = m_data->listOfMultiRoleGadgets;
+ const int size = int(gadgets.size());
+
+ QRangeModelAdapter adapter(&gadgets);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ MultiRoleGadget first = adapter.at(0);
+ MultiRoleGadget last = adapter.at(size - 1);
+ QCOMPARE(first, gadgets.at(0));
+ QCOMPARE(last, gadgets.at(size - 1));
+ QCOMPARE(dataChangedSpy.size(), 0);
+
+ adapter[0] = last;
+ QCOMPARE(dataChangedSpy.size(), 1);
+ adapter[size - 1] = first;
+ QCOMPARE(dataChangedSpy.size(), 2);
+ QCOMPARE(last, gadgets.at(0));
+ QCOMPARE(first, gadgets.at(size - 1));
+ QCOMPARE(dataChangedSpy.size(), 2);
+
+ swap(adapter.at(0), adapter.at(size - 1));
+ QCOMPARE(dataChangedSpy.size(), 4);
+ QCOMPARE(first, gadgets.at(0));
+ QCOMPARE(last, gadgets.at(size - 1));
+ QCOMPARE(dataChangedSpy.size(), 4);
+ dataChangedSpy.clear();
+
+ // DataRef(const DataRef &) should set the value on the model
+ adapter[size - 1] = adapter.at(0);
+ QCOMPARE(dataChangedSpy.size(), 1);
+}
+
+void tst_QRangeModelAdapter::tableIterate()
+{
+ {
+ auto table = m_data->vectorOfFixedColumns;
+ QRangeModelAdapter adapter(std::ref(table));
+ QCOMPARE(adapter.end() - adapter.begin(), adapter.rowCount());
+
+ QVariantList rowValues;
+ QVariantList itemValues;
+ { // const access
+ for (const auto &row : std::as_const(adapter)) {
+ std::tuple<int, QString> rowTuple = row;
+ auto [number, string] = rowTuple;
+ rowValues << number;
+ rowValues << string;
+ QCOMPARE(row.size(), 2);
+ QCOMPARE(row.at(0), number);
+ QCOMPARE(row.at(1), string);
+ for (const auto &value : row)
+ itemValues << value;
+ }
+ QCOMPARE(rowValues, (QList<QVariant>{
+ 0, "null", 1, "one", 2, "two", 3, "three", 4, "four"
+ }));
+ QCOMPARE(itemValues, rowValues);
+ rowValues.clear();
+ itemValues.clear();
+ }
+
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ { // read access via mutable iterators
+ for (auto row : adapter) {
+ std::tuple<int, QString> rowTuple = row;
+ auto [number, string] = rowTuple;
+ rowValues << number;
+ rowValues << string;
+ for (auto value : row)
+ itemValues << value;
+ }
+ QCOMPARE(rowValues, (QList<QVariant>{
+ 0, "null", 1, "one", 2, "two", 3, "three", 4, "four"
+ }));
+ QCOMPARE(itemValues, rowValues);
+ }
+
+ { // write access via mutable iterators
+ for (auto row : adapter) {
+ row = {0, "0"};
+ for (auto value : row) {
+ QCOMPARE(value, 0);
+ value = 42;
+ }
+ }
+ for (auto tableRow : table) {
+ QCOMPARE(tableRow, std::tuple(42, u"42"_s));
+ }
+ }
+ }
+}
+
+template <typename Adapter, typename Table>
+void verifyTupleTable(Adapter &&adapter, const Table &table)
+{
+ const int size = int(table.size());
+
+ QCOMPARE(adapter.at(0), table.at(0));
+ // QCOMPARE(adapter.at(size), {}); // asserts, as it should
+ QCOMPARE(adapter.at(0, 0), std::get<0>(table.at(0)));
+ QCOMPARE(adapter.data(0, 0), adapter.at(0, 0));
+ QCOMPARE(adapter.at(1, 1), std::get<1>(table.at(1)));
+ QCOMPARE(adapter.at(size, 1), QVariant{});
+ QCOMPARE(adapter.at(1, 2), QVariant{});
+}
+
+template <typename Adapter, typename Table>
+void verifyGadgetTable(const Adapter &adapter, const Table &table)
+{
+ const int size = int(table.size());
+
+ QCOMPARE(adapter.at(0), table.at(0));
+ // QCOMPARE(adapter.at(size), {}); // asserts, as it should
+ QCOMPARE(adapter.at(0, 0), table.at(0).display());
+ QCOMPARE(adapter.data(0, 0).metaType(), QMetaType::fromType<QString>());
+ QCOMPARE(adapter.data(0, 1).metaType(), QMetaType::fromType<QColor>());
+ QCOMPARE(adapter.data(0, 0), table.at(0).display());
+ QCOMPARE(adapter.at(1, 1), table.at(1).decoration());
+ QCOMPARE(adapter.at(2, 2), table.at(2).toolTip());
+ QCOMPARE(adapter.at(size, 1), QVariant{});
+ QCOMPARE(adapter.at(0, 3), QVariant{});
+}
+
+template <typename Adapter, typename Table>
+void verifyPointerTable(const Adapter &adapter, const Table &table)
+{
+ [[maybe_unused]] const int size = int(table.size());
+
+ using ItemType = std::remove_reference_t<decltype(*table.at(0).at(0))>;
+
+ // row
+ QCOMPARE(adapter.at(0), table.at(0));
+
+ // cell
+ QCOMPARE(adapter.data(0, 0).metaType(), QMetaType::fromType<ItemType *>());
+ QCOMPARE(adapter.data(0, 0), QVariant::fromValue(table.at(0).at(0)));
+ QCOMPARE(adapter.at(0, 0), table.at(0).at(0));
+}
+
+void tst_QRangeModelAdapter::tableAccess()
+{
+ {
+ auto table = m_data->vectorOfFixedColumns;
+ {
+ QRangeModelAdapter adapter(table);
+ expectInvalidIndex(6); // at and DataRef accesses when testing out-of-bounds
+ verifyTupleTable(adapter, table);
+ }
+
+ {
+ QRangeModelAdapter adapter(std::as_const(table));
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyTupleTable(adapter, table);
+ }
+
+ {
+ const QRangeModelAdapter adapter(table);
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyTupleTable(adapter, table);
+ }
+
+ {
+ const QRangeModelAdapter adapter(std::as_const(table));
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyTupleTable(adapter, table);
+ }
+ }
+
+ {
+ auto table = m_data->vectorOfGadgets;
+ {
+ QRangeModelAdapter adapter(table);
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyGadgetTable(adapter, table);
+ }
+
+ {
+ QRangeModelAdapter adapter(std::as_const(table));
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyGadgetTable(adapter, table);
+ }
+
+ {
+ const QRangeModelAdapter adapter(table);
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyGadgetTable(adapter, table);
+ }
+
+ {
+ const QRangeModelAdapter adapter(std::as_const(table));
+ expectInvalidIndex(2); // at and DataRef accesses when testing out-of-bounds
+ verifyGadgetTable(adapter, table);
+ }
+ }
+
+ {
+ auto table = m_data->tableOfPointers;
+ {
+ QRangeModelAdapter adapter(table);
+ verifyPointerTable(adapter, table);
+ }
+ {
+ QRangeModelAdapter adapter(std::as_const(table));
+ verifyPointerTable(adapter, table);
+ }
+ {
+ const QRangeModelAdapter adapter(table);
+ verifyPointerTable(adapter, table);
+ }
+ {
+ const QRangeModelAdapter adapter(std::as_const(table));
+ verifyPointerTable(adapter, table);
+ }
+ }
+
+ {
+ std::vector<std::vector<Object *>> table = {
+ {new Object, new Object},
+ {new Object, new Object}
+ };
+ {
+ QRangeModelAdapter adapter(table);
+ verifyPointerTable(adapter, table);
+ }
+ }
+}
+
+void tst_QRangeModelAdapter::tableWriteAccess()
+{
+ using std::swap;
+ {
+ auto table = m_data->vectorOfFixedColumns;
+ const int size = int(table.size());
+
+ QRangeModelAdapter adapter(&table);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ adapter[0] = {0, "null"};
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, 1));
+
+ dataChangedSpy.clear();
+ QCOMPARE(adapter.at(0, 0), 0);
+ QCOMPARE(adapter.at(0, 1), "null");
+
+ { // model outlives adapter
+ QRangeModelAdapter adapterCopy = adapter;
+ adapterCopy.at(0) = {-1, "dirty"};
+ adapterCopy.at(0) = {0, "dirty"};
+ }
+ QCOMPARE(dataChangedSpy.size(), 2);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, 1));
+ dataChangedSpy.clear();
+
+ { // all modifications result in notification
+ QRangeModelAdapter adapterCopy = adapter;
+ adapterCopy.at(0) = {0, "null"};
+ adapter.at(1) = {1, "dirty"};
+ }
+ QCOMPARE(dataChangedSpy.size(), 2);
+
+ // order of signal emissions is defined
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, 1));
+ QCOMPARE(dataChangedSpy.at(1).at(0).value<QModelIndex>(), adapter.index(1, 0));
+ QCOMPARE(dataChangedSpy.at(1).at(1).value<QModelIndex>(), adapter.index(1, 1));
+ dataChangedSpy.clear();
+
+ swap(adapter[0], adapter[size - 1]);
+ QCOMPARE(dataChangedSpy.size(), 2);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, 1));
+ QCOMPARE(dataChangedSpy.at(1).at(0).value<QModelIndex>(), adapter.index(size - 1, 0));
+ QCOMPARE(dataChangedSpy.at(1).at(1).value<QModelIndex>(), adapter.index(size - 1, 1));
+ dataChangedSpy.clear();
+
+ QVERIFY(adapter.setData(0, 0, -1, Qt::DisplayRole));
+ QVERIFY(adapter.setData(0, 1, "Minus one", Qt::DisplayRole));
+ QCOMPARE(dataChangedSpy.size(), 2);
+ }
+
+ {
+ auto table = m_data->tableOfNumbers;
+ const int lastRow = int(table.size() - 1);
+ const int lastColumn = int(table.at(0).size() - 1);
+
+ QRangeModelAdapter adapter(&table);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ QCOMPARE(adapter[0], table.at(0));
+
+ adapter[lastRow] = adapter[0];
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(lastRow, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(lastRow, lastColumn));
+ dataChangedSpy.clear();
+
+ adapter[lastRow] = {21.1, 22.1, 23.1, 24.1, 25.1};
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(lastRow, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(lastRow, lastColumn));
+ dataChangedSpy.clear();
+
+ // this breaks table topology, and would assert; we have to do it last
+#ifndef QT_NO_DEBUG
+ QTest::ignoreMessage(QtCriticalMsg,
+ QRegularExpression(".* The new row has the wrong size!"));
+#endif
+ adapter[0] = std::vector<double>{1.0};
+ }
+
+ { // table with raw row pointers
+ std::vector<Object *> table = {
+ new Object,
+ new Object,
+ };
+ QRangeModelAdapter adapter(std::ref(table));
+ QCOMPARE(adapter.rowCount(), 2);
+ QCOMPARE(adapter.columnCount(), 2);
+
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ adapter.at(0, 0) = "1/1";
+ adapter.at(0, 1) = 10;
+ QCOMPARE(table.at(0)->string(), "1/1");
+ QCOMPARE(table.at(0)->number(), 10);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ QVERIFY(adapter.at(0) != nullptr);
+ QCOMPARE(dataChangedSpy.count(), 0); // nothing written to the wrapper
+
+ adapter.at(0) = new Object;
+ QCOMPARE(dataChangedSpy.count(), 1);
+ // data in entire row changed
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, 1));
+ }
+
+ { // table with item pointers
+ std::vector<std::vector<Object *>> table = {
+ {new Object, new Object},
+ {new Object, new Object},
+ };
+ QRangeModelAdapter adapter(&table);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ QVERIFY(adapter.at(0, 0) != nullptr);
+ QCOMPARE(dataChangedSpy.count(), 0);
+#ifndef QT_NO_DEBUG
+ // we can't replace items that are pointers
+ QTest::ignoreMessage(QtCriticalMsg,
+ QRegularExpression("Not able to assign QVariant"));
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Writing value of type Object\\* to "
+ "role Qt::RangeModelAdapterRole at index .* of the model failed"));
+#endif
+ adapter.at(0, 0) = new Object;
+ QCOMPARE(dataChangedSpy.count(), 0);
+ }
+
+ { // table with smart item pointers
+ std::vector<std::vector<std::shared_ptr<Object>>> table = {
+ {std::make_shared<Object>("1.1", 1), std::make_shared<Object>("1.2", 2)},
+ {std::make_shared<Object>("2.1", 3), std::make_shared<Object>("2.2", 4)},
+ };
+ QRangeModelAdapter adapter(&table);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ // we only allow read-access to objects, as otherwise we'd not update
+ // the model
+ std::shared_ptr<const Object> topLeft = adapter.at(0, 0);
+ QCOMPARE(topLeft, table.at(0).at(0));
+ QCOMPARE(dataChangedSpy.count(), 0);
+ adapter.at(0, 0) = std::make_shared<Object>("0", 0);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ QCOMPARE(table.at(0).at(0)->string(), "0");
+ QCOMPARE(table.at(0).at(0)->number(), 0);
+
+ // we get a shared_ptr<const Object> and want to assign as a
+ // shared_ptr<Object>. This is not possible - and that's ok, because
+ // we'd end up with the same object in multiple places.
+ // adapter.at(0, 0) = adapter.at(1, 1);
+ // adapter.at(0, 0) = topLeft;
+
+ // Explicitly getting a row yields a view-like wrapper around the
+ // vector, preventing direct write access to the objects stored in the
+ // table.
+ auto row = adapter.at(0).get();
+ QCOMPARE(row.at(0)->number(), table.at(0).at(0)->number());
+ // row.at(0)->setNumber(3);
+ auto begin = row.begin();
+ QCOMPARE(begin->number(), row.at(0)->number());
+ // not allowed (P2836R1)
+ // std::vector<std::shared_ptr<Object>>::const_iterator it = begin;
+ // (*it)->setNumber(3); // this would be possible
+
+ int column = 0;
+ for (const auto &cell : row) {
+ QCOMPARE(row.at(column)->string(), cell->string());
+ ++column;
+ // cell.setNumber(3);
+ }
+ }
+}
+
+template <typename Adapter>
+QStringList rowValues(Adapter &&adapter)
+{
+ QStringList result;
+ for (auto row : adapter) {
+ result << row->value() << row->description();
+ if (row.hasChildren())
+ result << rowValues(row.children());
+ }
+ return result;
+}
+
+template <typename Adapter>
+QStringList itemValues(Adapter &&adapter)
+{
+ QStringList result;
+ for (auto row : adapter) {
+ for (auto value : row)
+ result << value;
+ if (row.hasChildren())
+ result << itemValues(row.children());
+ }
+ return result;
+}
+
+void tst_QRangeModelAdapter::treeIterate()
+{
+ const QStringList expectedValues = {
+ "1", "one",
+ "2", "two",
+ "2.1", "two.one",
+ "2.2", "two.two",
+ "2.3", "two.three",
+ "2.3.1", "two.three.one",
+ "2.3.2", "two.three.two",
+ "3", "three",
+ "4", "four",
+ "5", "five"
+ };
+
+ { // read from const adapter over const tree
+ const auto tree = createValueTree();
+ auto printTreeOnError = qScopeGuard([&tree]{
+ tree_row::prettyPrint(qDebug().nospace() << "tree at test failure:\n", tree);
+ });
+
+ const QRangeModelAdapter adapter(std::cref(tree));
+
+ auto top = adapter.begin();
+ QCOMPARE(top->value(), expectedValues.front());
+ QCOMPARE(top, adapter.cbegin());
+
+ auto topLeft = (*top).cbegin();
+ QCOMPARE(topLeft, (*top).begin());
+ QVERIFY(!topLeft->isEmpty());
+ QCOMPARE(*topLeft, top->value());
+
+ QStringList allRows = rowValues(adapter);
+ QStringList allItems = itemValues(adapter);
+
+ QCOMPARE(allRows, expectedValues);
+ QCOMPARE(allItems, expectedValues);
+
+ printTreeOnError.dismiss();
+ }
+
+ { // read from const adapter over mutable tree
+ auto tree = createValueTree();
+ auto printTreeOnError = qScopeGuard([&tree]{
+ tree_row::prettyPrint(qDebug().nospace() << "tree at test failure:\n", tree);
+ });
+
+ const QRangeModelAdapter adapter(std::ref(tree));
+
+ auto top = adapter.begin();
+ QCOMPARE(top->value(), expectedValues.front());
+ QCOMPARE(top, adapter.cbegin());
+
+ auto topLeft = (*top).cbegin();
+ QCOMPARE(topLeft, (*top).begin());
+ QVERIFY(!topLeft->isEmpty());
+ QCOMPARE(*topLeft, top->value());
+
+ QStringList allRows = rowValues(adapter);
+ QStringList allItems = itemValues(adapter);
+
+ QCOMPARE(allRows, expectedValues);
+ QCOMPARE(allItems, expectedValues);
+
+ printTreeOnError.dismiss();
+ }
+
+ { // mutable adapter over const tree
+ const auto tree = createValueTree();
+ auto printTreeOnError = qScopeGuard([&tree]{
+ tree_row::prettyPrint(qDebug().nospace() << "tree at test failure:\n", tree);
+ });
+
+ QRangeModelAdapter adapter(std::ref(tree));
+
+ auto top = adapter.begin();
+ QCOMPARE(top->value(), expectedValues.front());
+ QCOMPARE(top, adapter.cbegin());
+
+ auto topLeft = (*top).cbegin();
+ QCOMPARE(topLeft, (*top).begin());
+ QVERIFY(!topLeft->isEmpty());
+ QCOMPARE(*topLeft, top->value());
+
+ QStringList allRows = rowValues(adapter);
+ QStringList allItems = itemValues(adapter);
+
+ QCOMPARE(allRows, expectedValues);
+ QCOMPARE(allItems, expectedValues);
+
+ // We can safely access children on a const model, even if there is no
+ // range to back it up.
+ const auto &topRow = *top;
+ QVERIFY(!topRow.hasChildren());
+ QCOMPARE(topRow.children().size(), 0);
+ int iterCount = 0;
+ for (const auto &child : topRow.children()) {
+ Q_UNUSED(child);
+ ++iterCount;
+ }
+ QCOMPARE(iterCount, 0);
+
+ ++top;
+ const auto &secondRow = *top;
+ QVERIFY(secondRow.hasChildren());
+ QCOMPARE_NE(secondRow.children().size(), 0);
+
+ printTreeOnError.dismiss();
+ }
+
+ { // mutable adapter over mutable tree
+ auto tree = createValueTree();
+ auto printTreeOnError = qScopeGuard([&tree]{
+ tree_row::prettyPrint(qDebug().nospace() << "tree at test failure:\n", tree);
+ });
+
+ QRangeModelAdapter adapter(std::ref(tree));
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+ QSignalSpy rowsRemovedSpy(adapter.model(), &QAbstractItemModel::rowsRemoved);
+ QSignalSpy rowsInsertedSpy(adapter.model(), &QAbstractItemModel::rowsInserted);
+
+ auto top = adapter.begin();
+ QCOMPARE(top->value(), expectedValues.front());
+ QCOMPARE(top, adapter.cbegin());
+ QCOMPARE((*top).at(0), top->value());
+
+ auto topLeft = (*top).cbegin();
+ QCOMPARE(topLeft, (*top).begin());
+ QVERIFY(!topLeft->isEmpty());
+ QCOMPARE(*topLeft, top->value());
+
+ QStringList allRows = rowValues(adapter);
+ QStringList allItems = itemValues(adapter);
+
+ QCOMPARE(allRows, expectedValues);
+ QCOMPARE(allItems, expectedValues);
+
+ // nothing changed so far
+ QCOMPARE(dataChangedSpy.count(), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+
+ // add zero children - no change to rows
+ auto topRow = *top;
+ QVERIFY(!topRow.hasChildren());
+ topRow.children() = {};
+ QVERIFY(!topRow.hasChildren());
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+
+ // replace children
+ auto secondRow = *(top + 1);
+ QVERIFY(secondRow.hasChildren());
+
+ secondRow.at(0) = "reset";
+ QCOMPARE(dataChangedSpy.count(), 1);
+ secondRow[1] = "clear";
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ secondRow.children() = createValueTree();
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+
+ // clear children
+ secondRow.children() = {};
+ QCOMPARE(rowsRemovedSpy.count(), 2);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+
+ // add children
+ secondRow.children() = createValueTree();
+ QCOMPARE(rowsRemovedSpy.count(), 2);
+ QCOMPARE(rowsInsertedSpy.count(), 2);
+
+ printTreeOnError.dismiss();
+ }
+}
+
+template <typename Adapter, typename Tree>
+void verifyTree(const Adapter &adapter, Tree &&tree)
+{
+ using QRangeModelDetails::refTo;
+ const int size = int(tree.size());
+
+ QVERIFY(!adapter.hasChildren(0));
+ QVERIFY(adapter.hasChildren(1));
+ QVERIFY(!adapter.hasChildren(2));
+ QVERIFY(!adapter.hasChildren(3));
+
+ // row access
+ QCOMPARE(refTo(adapter.at(0)).value(), refTo(tree.at(0)).value());
+ QVERIFY(!refTo(adapter.at({1, 1})).description().isEmpty());
+ QCOMPARE(refTo(adapter.at(1)).description(), refTo(tree.at(1)).description());
+ // QCOMPARE(adapter.at(size), {}); // asserts, as it should
+
+ // value access
+ QCOMPARE(adapter.at(0, 0), refTo(tree.at(0)).value());
+ QCOMPARE(adapter.data(0, 0).metaType(), QMetaType::fromType<QString>());
+ QCOMPARE(adapter.data(0, 0), refTo(tree.at(0)).value());
+ QCOMPARE(adapter.at(1, 1), refTo(tree.at(1)).description());
+ QCOMPARE(adapter.at(size, 0), QString{});
+ QCOMPARE(adapter.at(0, adapter.columnCount()), QString{});
+
+ QVERIFY(!adapter.data({0, 0}, 0).isValid());
+ QCOMPARE(adapter.at({0, 0}, 0), QString{});
+ QCOMPARE(adapter.at(0, 0), "1");
+ QCOMPARE(adapter.at(0, 1), "one");
+ QCOMPARE(adapter.at({1, 0}, 0), "2.1");
+ QVERIFY(adapter.data({1, 0}, 0).isValid());
+ QCOMPARE(adapter.at({1, 0}, 1), "two.one");
+ QCOMPARE(adapter.at({1, 2, 0}, 0), "2.3.1");
+ QCOMPARE(adapter.at({1, 2, 1}, 1), "two.three.two");
+}
+
+void tst_QRangeModelAdapter::treeAccess()
+{
+ {
+ auto tree = createValueTree();
+ QRangeModelAdapter adapter(std::ref(tree));
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ // adapter.at(0).value() = u"123"_s;
+ adapter.at(0) = tree_row{"1", "eins"};
+ adapter.at(0, 1) = "1";
+ }
+
+ {
+ auto tree = createValueTree();
+ QRangeModelAdapter adapter(std::ref(std::as_const(tree)));
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ {
+ auto tree = createValueTree();
+ const QRangeModelAdapter adapter(std::ref(tree));
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ {
+ auto tree = createValueTree();
+ const QRangeModelAdapter adapter(std::ref(std::as_const(tree)));
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ using PointerProtocol = tree_row::ProtocolPointerImpl;
+ {
+ auto tree = createPointerTree();
+ QRangeModelAdapter adapter(std::ref(tree), PointerProtocol{});
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ {
+ auto tree = createPointerTree();
+ QRangeModelAdapter adapter(std::ref(std::as_const(tree)), PointerProtocol{});
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ {
+ auto tree = createPointerTree();
+ const QRangeModelAdapter adapter(std::ref(tree), PointerProtocol{});
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+
+ {
+ auto tree = createPointerTree();
+ const QRangeModelAdapter adapter(std::ref(std::as_const(tree)), PointerProtocol{});
+ expectInvalidIndex(4); // row, column, and non-existing children
+ verifyTree(adapter, tree);
+ }
+}
+
+void tst_QRangeModelAdapter::treeWriteAccess()
+{
+ {
+ auto tree = createValueTree();
+ QRangeModelAdapter adapter(std::ref(tree));
+ const int lastColumn = adapter.columnCount() - 1;
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ adapter.at(0) = tree_row{};
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(adapter.at(0, 0), "");
+ QCOMPARE(adapter.at(0, 1), "");
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index(0, lastColumn));
+ dataChangedSpy.clear();
+
+ adapter.at({1, 0}) = {"x", "X"};
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(adapter.at({1, 0}, 0), "x");
+ QCOMPARE(adapter.at({1, 0}, 1), "X");
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), adapter.index({1, 0}, 0));
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), adapter.index({1, 0}, lastColumn));
+ dataChangedSpy.clear();
+
+ adapter.at({1, 2, 1}) = {"y", "Y"};
+ const auto changedLeft = adapter.index({1, 2, 1}, 0);
+ const QPersistentModelIndex trackedLeft = changedLeft;
+ const auto changedRight = adapter.index({1, 2, 1}, lastColumn);
+ const QPersistentModelIndex trackedRight = changedRight;
+ QVERIFY(adapter.removeRow({1, 2, 0}));
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), changedLeft);
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), changedRight);
+ QCOMPARE_NE(changedLeft, trackedLeft);
+ QCOMPARE_NE(changedRight, trackedRight);
+ dataChangedSpy.clear();
+
+ adapter.at({1, 2, 0}, 0) = "z";
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QCOMPARE(dataChangedSpy.at(0).at(0).value<QModelIndex>(), trackedLeft);
+ QCOMPARE(dataChangedSpy.at(0).at(1).value<QModelIndex>(), trackedLeft);
+ adapter.at({1, 2, 0}, 1) = "Z";
+ QCOMPARE(dataChangedSpy.size(), 2);
+ QCOMPARE(dataChangedSpy.at(1).at(0).value<QModelIndex>(), trackedRight);
+ QCOMPARE(dataChangedSpy.at(1).at(1).value<QModelIndex>(), trackedRight);
+ dataChangedSpy.clear();
+
+ QVERIFY(adapter.setData({1, 2, 0}, 0, "y"));
+ QCOMPARE(dataChangedSpy.size(), 1);
+ QVERIFY(adapter.setData({1, 2, 0}, 1, "Y"));
+ QCOMPARE(dataChangedSpy.size(), 2);
+ dataChangedSpy.clear();
+ }
+
+ {
+ auto tree = createPointerTree();
+ // use a special protocol to check for row deletion
+ struct MarkDirtyProtocol : tree_row::ProtocolPointerImpl {
+ void deleteRow(tree_row *row) {
+ row->value() = "deleted";
+ row->description() = "deleted";
+ }
+ };
+
+ QRangeModelAdapter adapter(std::ref(tree), MarkDirtyProtocol{});
+ const QRangeModelAdapter constAdapter = adapter;
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+
+ QCOMPARE(constAdapter.at(0, 0), "1");
+ QCOMPARE(constAdapter.at(0, 1), "one");
+
+ // adapter.at(0) = nullptr; // would corrupt the tree, so not allowed
+
+ // overwriting the tree row value would not inform the model
+ // *adapter.at(0) = {};
+
+ // but we can overwrite individual items
+ adapter.at(0, 0) = "";
+ adapter.at(0, 1) = "";
+ QCOMPARE(constAdapter.at(0, 0), "");
+ QCOMPARE(constAdapter.at(0, 1), "");
+
+ auto row = constAdapter.at(4);
+ QCOMPARE(row->value(), "5");
+ QCOMPARE(row->description(), "five");
+
+ // not allowed, as we get a const tree_row * and can't assign to
+ // a tree_row *. Good, as otherwise we'd have the same pointer twice!
+ // adapter.at(0) = row;
+
+ // we can replace the old tree row with a new one
+ row = adapter.at(0);
+ adapter.at(0) = new tree_row{"new", "row"};
+ QCOMPARE(constAdapter.at(0, 0), "new");
+ QCOMPARE(constAdapter.at(0, 1), "row");
+
+ // and the old row got deleted
+ QCOMPARE(row->value(), "deleted");
+ QCOMPARE(row->description(), "deleted");
+
+ }
+}
+
+void tst_QRangeModelAdapter::insertRow()
+{
+ {
+ QList<int> data;
+ QRangeModelAdapter adapter(std::ref(data));
+
+ for (int i = 0; i < 2; ++i) {
+ QVERIFY(adapter.insertRow(data.size(), i));
+ if (i)
+ QVERIFY(adapter.insertRow(0, -i));
+ }
+
+ QCOMPARE(data, (QList<int>{-1, 0, 1}));
+ }
+
+ {
+ auto data = m_data->vectorOfFixedColumns;
+ auto oldSize = data.size();
+
+ QRangeModelAdapter adapter(std::ref(data));
+ // append
+ QVERIFY(adapter.insertRow(int(oldSize), {5, "five"}));
+ QCOMPARE(data.size(), ++oldSize);
+
+ // inserted
+ std::tuple<int, QString> newRow = {6, "six"};
+ QVERIFY(adapter.insertRow(int(oldSize / 2), newRow));
+ // not moved
+ QVERIFY(!std::get<QString>(newRow).isEmpty());
+ QCOMPARE(data.size(), ++oldSize);
+
+ // prepend
+ QVERIFY(adapter.insertRow(0, newRow));
+ QCOMPARE(data.size(), ++oldSize);
+
+ // move
+ QVERIFY(adapter.insertRow(0, std::move(newRow)));
+ QCOMPARE(data.size(), ++oldSize);
+ QVERIFY(std::get<QString>(newRow).isEmpty());
+ }
+}
+
+void tst_QRangeModelAdapter::insertRows()
+{
+#if defined Q_CC_MSVC && _MSC_VER < 1944
+ QSKIP("Internal compiler error with older MSVC versions");
+#else
+ {
+ QList<QString> data;
+ QList<QString> newData = {u"one"_s, u"two"_s, u"three"_s};
+ QRangeModelAdapter adapter(&data);
+
+ QVERIFY(adapter.insertRows(0, newData));
+ QCOMPARE(data, newData);
+ data.clear();
+
+ // move newData into data
+ const auto oldNewData = newData;
+ QVERIFY(adapter.insertRows(0, std::move(newData)));
+ QVERIFY(newData.at(0).isEmpty());
+ QCOMPARE(data, oldNewData);
+ }
+
+ {
+ auto data = m_data->vectorOfFixedColumns;
+ QRangeModelAdapter adapter(std::ref(data));
+
+ // std::vector has insert(pos, first, last)
+ for (int i = 0; i < 10; ++i) {
+ auto localCopy = data;
+ const size_t oldSize = data.size();
+ QVERIFY(adapter.insertRows(0, localCopy));
+ QCOMPARE(data.size(), oldSize * 2);
+ }
+
+ // inserting into self is UB, so verify that we handle that gracefully. However,
+ // the inner inserter returning false doesn't abort the begin/endInsertRows, as we
+ // don't have a way of canceling such an operation - so expect_fail here until we
+ // have a solution.
+ QEXPECT_FAIL("", "QAIM has no way to cancel an ongoing insertion operation", Continue);
+ QVERIFY(!adapter.insertRows(0, data));
+ }
+#endif
+}
+
+void tst_QRangeModelAdapter::removeRow()
+{
+ QList<int> data = {0, 1, 2, 3, 4};
+ QRangeModelAdapter adapter(&data);
+ QVERIFY(adapter.removeRow(0));
+ QCOMPARE(data, (QList<int>{1, 2, 3, 4}));
+}
+
+void tst_QRangeModelAdapter::removeRows()
+{
+ std::vector<std::vector<int>> data = {
+ {0},
+ {1},
+ {2},
+ {3},
+ {4},
+ };
+ QRangeModelAdapter adapter(&data);
+ QVERIFY(adapter.removeRows(1, 3));
+ QVERIFY(!adapter.removeRows(1, 7));
+ QCOMPARE(data, (std::vector<std::vector<int>>{{0},{4}}));
+}
+
+void tst_QRangeModelAdapter::moveRow()
+{
+ std::list<int> data = {0, 1, 2, 3, 4};
+ QRangeModelAdapter adapter(&data);
+ QVERIFY(adapter.moveRow(0, 4));
+ QCOMPARE(data, (std::list<int>{1, 2, 3, 0, 4}));
+}
+
+void tst_QRangeModelAdapter::moveRows()
+{
+ std::list<int> data = {0, 1, 2, 3, 4};
+ QRangeModelAdapter adapter(&data);
+ QVERIFY(adapter.moveRows(3, 2, 0));
+ QCOMPARE(data, (std::list<int>{3, 4, 0, 1, 2}));
+}
+
+void tst_QRangeModelAdapter::insertColumn()
+{
+ std::vector<std::vector<QString>> table = {
+ {"1"},
+ {"11"},
+ {"21"}
+ };
+ QRangeModelAdapter adapter(std::ref(table));
+ QVERIFY(adapter.insertColumn(0));
+
+ QCOMPARE(table, (std::vector<std::vector<QString>>{
+ {"", "1"},
+ {"", "11"},
+ {"", "21"}
+ }));
+
+ QVERIFY(adapter.insertColumn(2, u"100"_s));
+ QCOMPARE(table, (std::vector<std::vector<QString>>{
+ {"", "1", "100"},
+ {"", "11", "100"},
+ {"", "21", "100"}
+ }));
+
+ QVERIFY(adapter.insertColumn(1, QList<QString>{
+ "one", "eleven"
+ }));
+ QCOMPARE(table, (std::vector<std::vector<QString>>{
+ {"", "one", "1", "100"},
+ {"", "eleven", "11", "100"},
+ {"", "one", "21", "100"}
+ }));
+}
+
+void tst_QRangeModelAdapter::insertColumns()
+{
+ { // with insert(range)
+ std::vector<std::vector<int>> table = {
+ {0},
+ {10},
+ {20}
+ };
+ QRangeModelAdapter adapter(std::ref(table));
+ QVERIFY(adapter.insertColumns(1, QList{1, 2}));
+ QCOMPARE(table, (std::vector<std::vector<int>>{
+ {0, 1, 2},
+ {10, 1, 2},
+ {20, 1, 2}
+ }));
+ }
+
+ { // without insert(range)
+ QList<QList<int>> table = {
+ {0},
+ {10},
+ {20}
+ };
+
+ QRangeModelAdapter adapter(std::ref(table));
+ QVERIFY(adapter.insertColumns(1, QList{1, 2}));
+ QCOMPARE(table, (QList<QList<int>>{
+ {0, 1, 2},
+ {10, 1, 2},
+ {20, 1, 2}
+ }));
+
+ QVERIFY(adapter.insertColumns(0, QList<QList<int>>{
+ {-2, -1},
+ {-12, -11}
+ }));
+
+ QCOMPARE(table, (QList<QList<int>>{
+ {-2, -1, 0, 1, 2},
+ {-12, -11, 10, 1, 2},
+ {-2, -1, 20, 1, 2}
+ }));
+ }
+}
+
+void tst_QRangeModelAdapter::removeColumn()
+{
+ {
+ QList<QList<QString>> table = {
+ {"1"},
+ {"11"},
+ {"21"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(adapter.removeColumn(0));
+ QVERIFY(!adapter.removeColumn(0));
+ QCOMPARE(table, (QList<QList<QString>>{{}, {}, {}}));
+ }
+ {
+ QList<QList<QString>> table = {
+ {"01", "02"},
+ {"11", "12"},
+ {"21", "22"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(adapter.removeColumn(1));
+ QCOMPARE(table, (QList<QList<QString>>{
+ {"01"},
+ {"11"},
+ {"21"}
+ }));
+ }
+}
+
+void tst_QRangeModelAdapter::removeColumns()
+{
+ {
+ QList<QList<QString>> table = {
+ {"1"},
+ {"11"},
+ {"21"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(!adapter.removeColumns(0, 5));
+ QVERIFY(adapter.removeColumns(0, 1));
+ QCOMPARE(table, (QList<QList<QString>>{{}, {}, {}}));
+ }
+ {
+ QList<QList<QString>> table = {
+ {"01", "02"},
+ {"11", "12"},
+ {"21", "22"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(adapter.removeColumns(0, 2));
+ QCOMPARE(table, (QList<QList<QString>>{{}, {}, {}}));
+ }
+ {
+ QList<QList<QString>> table = {
+ {"01", "02", "03", "04"},
+ {"11", "12", "13", "14"},
+ {"21", "22", "23", "24"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(adapter.removeColumns(1, 2));
+ QCOMPARE(table, (QList<QList<QString>>{
+ {"01", "04"},
+ {"11", "14"},
+ {"21", "24"}
+ }));
+ }
+}
+
+void tst_QRangeModelAdapter::moveColumn()
+{
+ QList<QList<QString>> table = {
+ {"01", "02", "03", "04"},
+ {"11", "12", "13", "14"},
+ {"21", "22", "23", "24"}
+ };
+ QRangeModelAdapter adapter(&table);
+ QVERIFY(adapter.moveColumn(0, 2));
+ QCOMPARE(table, (QList<QList<QString>>{
+ {"02", "01", "03", "04"},
+ {"12", "11", "13", "14"},
+ {"22", "21", "23", "24"}
+ }));
+}
+
+void tst_QRangeModelAdapter::moveColumns()
+{
+ std::vector<std::vector<int>> table = {
+ {1, 2, 3, 4},
+ {11, 12, 13, 14},
+ {21, 22, 23, 24}
+ };
+ QRangeModelAdapter adapter(&table);
+ adapter.moveColumns(0, 2, 3);
+ QCOMPARE(table, (std::vector<std::vector<int>>{
+ {3, 1, 2, 4},
+ {13, 11, 12, 14},
+ {23, 21, 22, 24}
+ }));
+}
+
+void tst_QRangeModelAdapter::buildValueTree()
+{
+ auto tree = std::make_unique<value_tree>();
+ auto printTreeOnError = qScopeGuard([&tree]{
+ tree_row::prettyPrint(qDebug().nospace() << "tree at test failure:\n", *tree);
+ });
+
+ QRangeModelAdapter adapter(std::ref(*tree));
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+ QSignalSpy rowsRemovedSpy(adapter.model(), &QAbstractItemModel::rowsRemoved);
+ QSignalSpy rowsInsertedSpy(adapter.model(), &QAbstractItemModel::rowsInserted);
+
+ auto oldCount = tree->size();
+
+ // create top level item
+ QVERIFY(adapter.insertRow(0));
+ QCOMPARE(tree->size(), ++oldCount);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), QModelIndex()); // parent
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0); // first
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 0); // last
+ QCOMPARE(dataChangedSpy.count(), 0);
+ rowsInsertedSpy.clear();
+
+ // append one more, explicitly constructed
+ QVERIFY(adapter.insertRow(int(tree->size()), {"1", "one"}));
+ QCOMPARE(tree->size(), ++oldCount);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), QModelIndex()); // parent
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 1);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ rowsInsertedSpy.clear();
+
+#if defined Q_CC_MSVC && _MSC_VER < 1944
+ printTreeOnError.dismiss();
+ QSKIP("Buggy compiler, get a later version of MSVC 2022");
+#else
+ // append two more, implicitly constructed
+ QVERIFY(adapter.insertRows(int(tree->size()), std::array{
+ u"2"_s,
+ u"3"_s
+ }));
+ QCOMPARE(tree->size(), oldCount += 2);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), QModelIndex());
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 2);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 3);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ rowsInsertedSpy.clear();
+
+ QVERIFY(!adapter.hasChildren(0));
+ QVERIFY(adapter.insertRow({0, 0}));
+ QVERIFY(adapter.hasChildren(0));
+ QCOMPARE(adapter.rowCount(0), 1);
+
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 0);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ rowsInsertedSpy.clear();
+
+ {
+ auto firstChild = adapter.at({0, 0});
+
+ QVERIFY(firstChild->parentRow());
+ QVERIFY(firstChild->value().isEmpty());
+ QVERIFY(firstChild->description().isEmpty());
+
+ adapter.at({0, 0}, 0) = "0.0";
+ QCOMPARE(dataChangedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ adapter.at({0, 0}, 1) = "zero.null";
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ QCOMPARE(adapter.at({0, 0}, 0), firstChild->value());
+ QCOMPARE(adapter.at({0, 0}, 1), firstChild->description());
+
+ adapter.at({0, 0}) = {"0,0", "null.nix"};
+ QCOMPARE(firstChild->value(), "0,0");
+ QCOMPARE(firstChild->description(), "null.nix");
+ QCOMPARE(dataChangedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 0);
+ dataChangedSpy.clear();
+
+ adapter.at({0, 0}, 0) = "1.0";
+ adapter.at({0, 0}, 1) = "one.zero";
+ QCOMPARE(firstChild->value(), "1.0");
+ QCOMPARE(firstChild->description(), "one.zero");
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+#if defined(__cpp_multidimensional_subscript)
+/*!
+ Current state of support
+ * MSVC chokes on initializer list within [] operator, so have to call operator
+ explicitly as a member function
+ * gcc 13.3 compiles, but the returned DataRef is default-constructed
+ * gcc 14.2.0 works
+ * (Apple) clang 17 works fine
+*/
+#if (!defined(Q_CC_GNU_ONLY) || Q_CC_GNU > 1303)
+#if defined(Q_CC_MSVC_ONLY)
+ adapter.operator[]({0, 0}, 0) = "1.0";
+ adapter.operator[]({0, 0}, 1) = "one.null";
+#else
+ adapter[{0, 0}, 0] = "1.0";
+ adapter[{0, 0}, 1] = "one.null";
+#endif
+
+ QCOMPARE(firstChild->value(), "1.0");
+ QCOMPARE(firstChild->description(), "one.null");
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+#else
+ qInfo("C++23 multidimensional subscript support available, but broken.");
+#endif
+#else
+ qInfo("C++23 multidimensional subscript support not available.");
+#endif // __cpp_multidimensional_subscript
+ }
+
+ // insert move-only rows
+ QVERIFY(adapter.insertRows({0, 1}, std::array{
+ tree_row{u"1.1"_s, u"one.one"_s},
+ tree_row{u"1.2"_s, u"one.two"_s},
+ }));
+ QCOMPARE(adapter.rowCount(0), 3);
+ QCOMPARE(adapter.index({0, 1}, 0).parent(), adapter.index(0, 0));
+ QVERIFY((adapter.at({0, 1}))->parentRow());
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 2);
+ QCOMPARE(dataChangedSpy.count(), 0);
+ rowsInsertedSpy.clear();
+
+ adapter.moveRow(2, 1);
+ // adapter.moveRow({0, 0}, {1, 1}); // out of bounds -> crash
+ while (adapter.hasChildren(0))
+ adapter.moveRow({0, 0}, {1, 0});
+ QCOMPARE(adapter.rowCount(0), 0);
+ QCOMPARE(adapter.rowCount(1), 3);
+ adapter.moveRows({1, 0}, 3, {2, 0});
+ QCOMPARE(adapter.rowCount(1), 0);
+ QCOMPARE(adapter.rowCount(2), 3);
+
+ QPersistentModelIndex firstRowPMI;
+ QPersistentModelIndex firstChildPMI;
+ QPersistentModelIndex firstGrandchildPMI;
+
+ { // replace existing row with branch
+ tree_row newRow = {u"0"_s, u"zero"_s};
+ tree_row &firstChild = newRow.addChild(u"0.1"_s, u"zero.one"_s);
+ firstChild.addChild("0.1.1", u"zero.one.one"_s);
+
+ adapter.at(0) = std::move(newRow);
+ QCOMPARE(dataChangedSpy.count(), 1); // whole row data changed
+ QCOMPARE(dataChangedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).value(1), adapter.index(0, 1));
+ QCOMPARE(rowsInsertedSpy.count(), 1); // and a new row was added underneath
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 0);
+ QCOMPARE(rowsRemovedSpy.count(), 0); // no rows removed
+ dataChangedSpy.clear();
+ rowsInsertedSpy.clear();
+
+ firstRowPMI = adapter.index(0, 0);
+ QVERIFY(firstRowPMI.isValid());
+ QCOMPARE(firstRowPMI.data(), "0");
+ firstChildPMI = adapter.index({0, 0}, 1);
+ QVERIFY(firstChildPMI.isValid());
+ QCOMPARE(firstChildPMI.data(), "zero.one");
+ firstGrandchildPMI = adapter.index({0, 0, 0}, 0);
+ QVERIFY(firstGrandchildPMI.isValid());
+ QCOMPARE(firstGrandchildPMI.data(), "0.1.1");
+ }
+
+ { // replace existing branch with new branch
+ tree_row newRow = {"0", u"null"_s};
+ tree_row &firstChild = newRow.addChild(u"0.1"_s, u"null.one"_s);
+ firstChild.addChild(u"0.1.1"_s, u"null.one.one"_s);
+
+ adapter.at(0) = std::move(newRow);
+ QCOMPARE(dataChangedSpy.count(), 1); // whole row data changed
+ QCOMPARE(dataChangedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).value(1), adapter.index(0, 1));
+ QCOMPARE(rowsRemovedSpy.count(), 1); // old child row was removed
+ QCOMPARE(rowsRemovedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsRemovedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsRemovedSpy.at(0).value(2), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 1); // old child row was removed
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 0);
+ dataChangedSpy.clear();
+ rowsInsertedSpy.clear();
+ rowsRemovedSpy.clear();
+
+ // only data has changed
+ QVERIFY(firstRowPMI.isValid());
+ // (grand)children are replaced
+ QVERIFY(!firstChildPMI.isValid());
+ firstChildPMI = adapter.index({0, 0}, 0);
+ QVERIFY(firstChildPMI.isValid());
+ QVERIFY(!firstGrandchildPMI.isValid());
+ firstGrandchildPMI = adapter.index({0, 0, 0}, 0);
+ QVERIFY(firstGrandchildPMI.isValid());
+ }
+
+ { // replace existing branch with new row
+ tree_row newRow = {"0", u"zero.zero"_s};
+ adapter.at(0) = std::move(newRow);
+ QCOMPARE(dataChangedSpy.count(), 1); // whole row data changed
+ QCOMPARE(dataChangedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(dataChangedSpy.at(0).value(1), adapter.index(0, 1));
+ QCOMPARE(rowsRemovedSpy.count(), 1); // old child row was removed
+ QCOMPARE(rowsRemovedSpy.at(0).value(0), adapter.index(0, 0));
+ QCOMPARE(rowsRemovedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsRemovedSpy.at(0).value(2), 0);
+ QCOMPARE(rowsInsertedSpy.count(), 0); // no new children inserted
+ dataChangedSpy.clear();
+ rowsRemovedSpy.clear();
+
+ // only data has changed
+ QVERIFY(firstRowPMI.isValid());
+ // (grand)children are replaced
+ QVERIFY(!firstChildPMI.isValid());
+ QVERIFY(!firstGrandchildPMI.isValid());
+ }
+
+ dataChangedSpy.clear();
+ rowsInsertedSpy.clear();
+#endif // old Q_CC_MSVC
+
+ printTreeOnError.dismiss();
+}
+
+void tst_QRangeModelAdapter::buildPointerTree()
+{
+ struct MarkDirtyProtocol : tree_row::ProtocolPointerImpl {
+ void deleteRow(tree_row *row) {
+ row->value() = "deleted";
+ row->description() = "deleted";
+ deletedRows << row;
+ }
+ QList<tree_row *> deletedRows;
+
+ ~MarkDirtyProtocol()
+ {
+ qDeleteAll(deletedRows);
+ }
+ };
+
+ auto tree = createPointerTree();
+ QRangeModelAdapter adapter(std::move(tree), MarkDirtyProtocol{});
+
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+ QSignalSpy rowsRemovedSpy(adapter.model(), &QAbstractItemModel::rowsRemoved);
+ QSignalSpy rowsInsertedSpy(adapter.model(), &QAbstractItemModel::rowsInserted);
+
+ {
+ const tree_row *secondRow = adapter.at(1);
+ QVERIFY(secondRow);
+ QCOMPARE(secondRow->value(), adapter.data(1, 0));
+ const tree_row *row21 = adapter.at({1, 0});
+ QVERIFY(row21);
+ const tree_row *row230 = adapter.at({1, 2, 0});
+ QVERIFY(row230);
+
+ tree_row *newRow = new tree_row{"0", "null"};
+ newRow->addChildPointer("0.0", "");
+ newRow->addChildPointer("0.1", "");
+ tree_row *newChild = newRow->addChildPointer("0.2", "");
+ newChild->addChildPointer("0.2.0", "");
+ newChild->addChildPointer("0.2.1", "");
+ newChild->addChildPointer("0.2.2", "");
+ newRow->addChildPointer("0.3", "");
+
+ // replace branch with new branch
+ adapter.at(1) = newRow;
+ QCOMPARE(dataChangedSpy.count(), 1); // top row changed - ### actually, replaced - should we invalidate?
+ QCOMPARE(dataChangedSpy.at(0).value(0), adapter.index(1, 0));
+ QCOMPARE(dataChangedSpy.at(0).value(1), adapter.index(1, 1));
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.at(0).value(0), adapter.index(1, 0)); // parent
+ QCOMPARE(rowsRemovedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsRemovedSpy.at(0).value(2), 2); // three children removed
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(1, 0)); // parent
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 3); // four children added
+ dataChangedSpy.clear();
+ rowsRemovedSpy.clear();
+ rowsInsertedSpy.clear();
+
+ // all old rows marked as deleted
+ QCOMPARE(secondRow->value(), "deleted");
+ QCOMPARE(row21->value(), "deleted");
+ QCOMPARE(row230->value(), "deleted");
+ }
+
+ // now do the same thing with iterator access
+ {
+ auto secondRow = *(adapter.begin() + 1);
+ QVERIFY(secondRow.hasChildren());
+ secondRow.children() = createPointerTree();
+
+ QCOMPARE(dataChangedSpy.count(), 0); // no existing row was changed
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.at(0).value(0), adapter.index(1, 0)); // parent
+ QCOMPARE(rowsRemovedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsRemovedSpy.at(0).value(2), 3); // four children removed
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.at(0).value(0), adapter.index(1, 0)); // parent
+ QCOMPARE(rowsInsertedSpy.at(0).value(1), 0);
+ QCOMPARE(rowsInsertedSpy.at(0).value(2), 4); // five children added
+ }
+}
+
+class ObjectTreeItem;
+using ObjectTree = std::vector<ObjectTreeItem>;
+
+class ObjectTreeItem : public ObjectRow
+{
+public:
+ ObjectTreeItem(Object *item = nullptr)
+ {
+ m_objects[0] = item;
+ }
+
+ ObjectTreeItem *parentRow() const { return m_parentRow; }
+ void setParentRow(ObjectTreeItem *parentRow) { m_parentRow = parentRow; }
+ const auto &childRows() const { return m_children; }
+ auto &childRows() { return m_children; }
+
+private:
+ template <std::size_t I, typename Item,
+ std::enable_if_t<std::is_same_v<q20::remove_cvref_t<Item>, ObjectTreeItem>, bool> = true>
+ friend decltype(auto) get(Item &&row) { return q23::forward_like<Item>(row.m_objects[I]); }
+
+ ObjectTreeItem *m_parentRow = nullptr;
+ std::optional<ObjectTree> m_children = std::nullopt;
+};
+
+namespace std {
+ template <> struct tuple_size<ObjectTreeItem> : tuple_size<ObjectRow> {};
+ template <std::size_t I> struct tuple_element<I, ObjectTreeItem> : tuple_element<I, ObjectRow> {};
+}
+
+void tst_QRangeModelAdapter::insertAutoConnectObjects()
+{
+ ObjectTree emptyTree;
+
+ QRangeModelAdapter adapter(emptyTree);
+ QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged);
+ adapter.model()->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full);
+
+ Object *newObject = new Object;
+ adapter.insertRow(0, ObjectTreeItem{newObject});
+ newObject->setString("0");
+ newObject->setNumber(0);
+
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ Object *newChild = new Object;
+ auto firstRow = adapter.begin();
+ (*firstRow).children() = ObjectTree{
+ ObjectTreeItem(newChild),
+ ObjectTreeItem(),
+ ObjectTreeItem()
+ };
+ QCOMPARE(dataChangedSpy.count(), 0);
+ QVERIFY(adapter.hasChildren(0));
+ newChild->setString("0.0");
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedSpy.clear();
+
+ newChild = new Object;
+ newChild->setString("0.1");
+ adapter.at({0, 1}) = ObjectTreeItem(newChild);
+ QCOMPARE(dataChangedSpy.count(), 1);
+ newChild->setNumber(1);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ newChild = new Object;
+ Object *newGrandChild = new Object;
+ ObjectTreeItem newBranch(newChild);
+ newBranch.childRows() = ObjectTree{
+ ObjectTreeItem(), // skip the first row to verify that we continue through nullptr
+ ObjectTreeItem(newGrandChild),
+ ObjectTreeItem()
+ };
+ adapter.at({0, 2}) = newBranch;
+ QCOMPARE(dataChangedSpy.count(), 1);
+ newChild->setNumber(1);
+ QCOMPARE(dataChangedSpy.count(), 2);
+ dataChangedSpy.clear();
+
+ newGrandChild->setString("0.2.1");
+ QCOMPARE(dataChangedSpy.count(), 1);
+ dataChangedSpy.clear();
+
+ newGrandChild = new Object;
+ adapter.at({0, 2, 0}, 0) = newGrandChild;
+ QCOMPARE(dataChangedSpy.count(), 1);
+ newGrandChild->setString("0.2.0");
+ QCOMPARE(dataChangedSpy.count(), 2);
+}
+
+QTEST_MAIN(tst_QRangeModelAdapter)
+#include "tst_qrangemodeladapter.moc"
+
+#undef HAS_API
+#undef ADD_COPY
+#undef ADD_POINTER
+#undef ADD_UPTR
+#undef ADD_SPTR
+#undef ADD_HELPER
+#undef ADD_ALL
diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp
index 696bcdc07d7..0fc7538c515 100644
--- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp
+++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp
@@ -291,6 +291,8 @@ class tst_QMetaObject : public QObject
Q_PROPERTY(int value8 READ value8)
Q_PROPERTY(int value9 READ value9 CONSTANT)
Q_PROPERTY(int value10 READ value10 FINAL)
+ Q_PROPERTY(int value11 READ value10 VIRTUAL)
+ Q_PROPERTY(int value12 READ value10 OVERRIDE)
public:
enum EnumType { EnumType1 };
@@ -358,6 +360,8 @@ private slots:
void propertyNotify();
void propertyConstant();
void propertyFinal();
+ void propertyVirtual();
+ void propertyOverride();
void metaType();
@@ -2727,6 +2731,32 @@ void tst_QMetaObject::propertyFinal()
QVERIFY(!prop.isFinal());
}
+void tst_QMetaObject::propertyVirtual()
+{
+ const QMetaObject *mo = metaObject();
+
+ QMetaProperty prop = mo->property(mo->indexOfProperty("value11"));
+ QVERIFY(prop.isValid());
+ QVERIFY(prop.isVirtual());
+
+ prop = mo->property(mo->indexOfProperty("value9"));
+ QVERIFY(prop.isValid());
+ QVERIFY(!prop.isVirtual());
+}
+
+void tst_QMetaObject::propertyOverride()
+{
+ const QMetaObject *mo = metaObject();
+
+ QMetaProperty prop = mo->property(mo->indexOfProperty("value12"));
+ QVERIFY(prop.isValid());
+ QVERIFY(prop.isOverride());
+
+ prop = mo->property(mo->indexOfProperty("value9"));
+ QVERIFY(prop.isValid());
+ QVERIFY(!prop.isOverride());
+}
+
void tst_QMetaObject::metaType()
{
QCOMPARE(QObject::staticMetaObject.metaType(), QMetaType::fromType<QObject>());
diff --git a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
index 67643606fa3..a441ed8f7ee 100644
--- a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
+++ b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp
@@ -77,6 +77,9 @@ class SomethingOfEverything : public QObject
Q_PROPERTY(SomethingEnum eprop READ eprop)
Q_PROPERTY(SomethingFlagEnum fprop READ fprop)
Q_PROPERTY(QLocale::Language language READ language)
+ Q_PROPERTY(QString virtualP READ prop VIRTUAL)
+ // Doesn't override anything, used only to verify MOC handling of OVERRIDE keyword
+ Q_PROPERTY(QString overrideP READ prop OVERRIDE)
public:
Q_INVOKABLE SomethingOfEverything() {}
~SomethingOfEverything() {}
@@ -577,6 +580,8 @@ void tst_QMetaObjectBuilder::property()
QVERIFY(!nullProp.isEnumOrFlag());
QVERIFY(!nullProp.isConstant());
QVERIFY(!nullProp.isFinal());
+ QVERIFY(!nullProp.isVirtual());
+ QVERIFY(!nullProp.isOverride());
QCOMPARE(nullProp.index(), 0);
QCOMPARE(nullProp.revision(), 0);
@@ -596,6 +601,8 @@ void tst_QMetaObjectBuilder::property()
QVERIFY(!prop1.isEnumOrFlag());
QVERIFY(!prop1.isConstant());
QVERIFY(!prop1.isFinal());
+ QVERIFY(!prop1.isVirtual());
+ QVERIFY(!prop1.isOverride());
QCOMPARE(prop1.revision(), 0);
QCOMPARE(prop1.index(), 0);
QCOMPARE(builder.propertyCount(), 1);
@@ -616,6 +623,8 @@ void tst_QMetaObjectBuilder::property()
QVERIFY(!prop2.isEnumOrFlag());
QVERIFY(!prop2.isConstant());
QVERIFY(!prop2.isFinal());
+ QVERIFY(!prop2.isVirtual());
+ QVERIFY(!prop2.isOverride());
QCOMPARE(prop2.revision(), 0);
QCOMPARE(prop2.index(), 1);
QCOMPARE(builder.propertyCount(), 2);
@@ -669,6 +678,8 @@ void tst_QMetaObjectBuilder::property()
QVERIFY(!prop2.isEnumOrFlag());
QVERIFY(!prop2.isConstant());
QVERIFY(!prop2.isFinal());
+ QVERIFY(!prop2.isVirtual());
+ QVERIFY(!prop2.isOverride());
QCOMPARE(prop2.revision(), 0);
// Remove prop1 and check that prop2 becomes index 0.
@@ -686,6 +697,8 @@ void tst_QMetaObjectBuilder::property()
QVERIFY(!prop2.isEnumOrFlag());
QVERIFY(!prop2.isConstant());
QVERIFY(!prop2.isFinal());
+ QVERIFY(!prop2.isVirtual());
+ QVERIFY(!prop2.isOverride());
QCOMPARE(prop2.revision(), 0);
QCOMPARE(prop2.index(), 0);
@@ -711,6 +724,8 @@ void tst_QMetaObjectBuilder::property()
prop2.setEnumOrFlag(false); \
prop2.setConstant(false); \
prop2.setFinal(false); \
+ prop2.setVirtual(false); \
+ prop2.setOverride(false); \
prop2.setBindable(false); \
prop2.setRequired(false); \
} while (0)
@@ -727,6 +742,8 @@ void tst_QMetaObjectBuilder::property()
prop2.setEnumOrFlag(true); \
prop2.setConstant(true); \
prop2.setFinal(true); \
+ prop2.setVirtual(true); \
+ prop2.setOverride(true); \
prop2.setBindable(true); \
prop2.setRequired(true); \
} while (0)
@@ -742,6 +759,8 @@ void tst_QMetaObjectBuilder::property()
(prop2.isEnumOrFlag() ? 1 : 0) + \
(prop2.isConstant() ? 1 : 0) + \
(prop2.isFinal() ? 1 : 0) + \
+ (prop2.isVirtual() ? 1 : 0) + \
+ (prop2.isOverride() ? 1 : 0) + \
(prop2.isBindable() ? 1 : 0) + \
(prop2.isRequired() ? 1 : 0))
#define CHECK_FLAG(setFunc,isFunc) \
@@ -766,6 +785,8 @@ void tst_QMetaObjectBuilder::property()
CHECK_FLAG(setConstant, isConstant);
CHECK_FLAG(setBindable, isBindable);
CHECK_FLAG(setFinal, isFinal);
+ CHECK_FLAG(setVirtual, isVirtual);
+ CHECK_FLAG(setOverride, isOverride);
CHECK_FLAG(setRequired, isRequired);
SET_ALL_FLAGS();
QCOMPARE(COUNT_FLAGS(), flagCounter);
@@ -782,6 +803,22 @@ void tst_QMetaObjectBuilder::property()
QCOMPARE(prototypeProp.notifySignal().signature(), QByteArray("propChanged(QString)"));
QCOMPARE(builder.methodCount(), 1);
QCOMPARE(builder.method(0).signature(), QByteArray("propChanged(QString)"));
+
+ // virt specifiers
+ { //Q_PROPERTY(int virtualP READ prop VIRTUAL)
+ QMetaProperty prototype = SomethingOfEverything::staticMetaObject.property(7);
+ QMetaPropertyBuilder prototypeProp = builder.addProperty(prototype);
+ QCOMPARE(prototypeProp.isVirtual(), true);
+ QCOMPARE(prototypeProp.isOverride(), false);
+ QCOMPARE(prototypeProp.isFinal(), false);
+ }
+ { // Q_PROPERTY(int overrideP READ prop OVERRIDE)
+ QMetaProperty prototype = SomethingOfEverything::staticMetaObject.property(8);
+ QMetaPropertyBuilder prototypeProp = builder.addProperty(prototype);
+ QCOMPARE(prototypeProp.isVirtual(), false);
+ QCOMPARE(prototypeProp.isOverride(), true);
+ QCOMPARE(prototypeProp.isFinal(), false);
+ }
}
void tst_QMetaObjectBuilder::variantProperty()
diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
index b05a055252b..2fcfd056882 100644
--- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
+++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp
@@ -56,7 +56,7 @@ CHECK_GET(MyVariant, const &&);
#include <QtGui/qtransform.h>
// QtCore:
-#include <QAssociativeIterable>
+#include <QMetaAssociation>
#include <QBitArray>
#include <QBuffer>
#include <QByteArrayList>
@@ -74,7 +74,7 @@ CHECK_GET(MyVariant, const &&);
#include <QQueue>
#include <QRegularExpression>
#include <QScopeGuard>
-#include <QSequentialIterable>
+#include <QMetaSequence>
#include <QSet>
#include <QStack>
#include <QTimeZone>
@@ -398,6 +398,7 @@ private slots:
void iterateAssociativeContainerElements() { runTestFunction(); }
void iterateContainerElements();
void emptyContainerInterface();
+ void modifyContainerElements();
void pairElements_data();
void pairElements() { runTestFunction(); }
@@ -5015,7 +5016,7 @@ struct KeyGetter<std::unordered_map<T, U> >
};
template<typename Iterator>
-void sortIterable(QSequentialIterable *iterable)
+void sortIterable(QMetaSequence::Iterable *iterable)
{
std::sort(Iterator(iterable->mutableBegin()), Iterator(iterable->mutableEnd()),
[&](const QVariant &a, const QVariant &b) {
@@ -5026,6 +5027,10 @@ void sortIterable(QSequentialIterable *iterable)
template<typename Container>
static void testSequentialIteration()
{
+ QFETCH(bool, hasSizeAccessor);
+ QFETCH(bool, hasIndexedAccessors);
+ QTest::failOnWarning();
+
int numSeen = 0;
Container sequence;
ContainerAPI<Container>::insert(sequence, 1);
@@ -5036,15 +5041,17 @@ static void testSequentialIteration()
QVERIFY(listVariant.canConvert<QVariantList>());
QVariantList varList = listVariant.value<QVariantList>();
QCOMPARE(varList.size(), (int)std::distance(sequence.begin(), sequence.end()));
- QSequentialIterable listIter = listVariant.view<QSequentialIterable>();
- QCOMPARE(varList.size(), listIter.size());
+ QMetaSequence::Iterable listIter = listVariant.view<QMetaSequence::Iterable>();
+ if (hasSizeAccessor)
+ QCOMPARE(listIter.size(), varList.size());
typename Container::iterator containerIter = sequence.begin();
const typename Container::iterator containerEnd = sequence.end();
- for (int i = 0; i < listIter.size(); ++i, ++containerIter, ++numSeen)
+ for (int i = 0, end = varList.size(); i < end; ++i, ++containerIter, ++numSeen)
{
- QVERIFY(ContainerAPI<Container >::compare(listIter.at(i), *containerIter));
- QVERIFY(ContainerAPI<Container >::compare(listIter.at(i), varList.at(i)));
+ QVERIFY(ContainerAPI<Container>::compare(*containerIter, varList.at(i)));
+ if (hasIndexedAccessors)
+ QVERIFY(ContainerAPI<Container>::compare(listIter.at(i), varList.at(i)));
}
QCOMPARE(numSeen, (int)std::distance(sequence.begin(), sequence.end()));
QCOMPARE(containerIter, containerEnd);
@@ -5059,9 +5066,8 @@ static void testSequentialIteration()
}
QCOMPARE(numSeen, (int)std::distance(sequence.begin(), sequence.end()));
- auto compareLists = [&]() {
+ auto compareLists = [&](const QVariantList &varList) {
int numSeen = 0;
- auto varList = listVariant.value<QVariantList>();
auto varIter = varList.begin();
for (const QVariant &v : std::as_const(listIter)) {
QVERIFY(ContainerAPI<Container>::compare(v, *varIter));
@@ -5077,49 +5083,53 @@ static void testSequentialIteration()
++numSeen;
}
QCOMPARE(numSeen, (int)std::distance(varList.begin(), varList.end()));
+
+ if (hasSizeAccessor)
+ QCOMPARE(listIter.size(), varList.size());
+
+ if (!hasIndexedAccessors)
+ return;
+
+ for (qsizetype i = 0, end = varList.size(); i < end; ++i)
+ QCOMPARE(listIter.at(i), varList.at(i));
};
- compareLists();
+ compareLists(varList);
+
+ QVariant first = varList.at(0);
+ QVariant second = varList.at(1);
+ QVariant third = varList.at(2);
+ QCOMPARE(varList.size(), 3);
+ compareLists(varList);
+
+ listIter.metaContainer().addValue(listIter.mutableIterable(), third.constData());
+ varList = listVariant.value<QVariantList>();
+ QCOMPARE(varList.size(), 4);
+ compareLists(varList);
- QVariant first = listIter.at(0);
- QVariant second = listIter.at(1);
- QVariant third = listIter.at(2);
- compareLists();
- listIter.addValue(third);
- compareLists();
- listIter.addValue(second);
- compareLists();
- listIter.addValue(first);
- compareLists();
+ listIter.metaContainer().addValue(listIter.mutableIterable(), second.constData());
+ varList = listVariant.value<QVariantList>();
+ QCOMPARE(varList.size(), 5);
+ compareLists(varList);
- QCOMPARE(listIter.size(), 6);
+ listIter.metaContainer().addValue(listIter.mutableIterable(), first.constData());
+ varList = listVariant.value<QVariantList>();
+ QCOMPARE(varList.size(), 6);
+ compareLists(varList);
if (listIter.canRandomAccessIterate())
- sortIterable<QSequentialIterable::RandomAccessIterator>(&listIter);
+ sortIterable<QMetaSequence::Iterable::RandomAccessIterator>(&listIter);
else if (listIter.canReverseIterate())
- sortIterable<QSequentialIterable::BidirectionalIterator>(&listIter);
+ sortIterable<QMetaSequence::Iterable::BidirectionalIterator>(&listIter);
else if (listIter.canForwardIterate())
return; // std::sort cannot sort with only forward iterators.
else
QFAIL("The container has no meaningful iterators");
- compareLists();
- QCOMPARE(listIter.size(), 6);
- QCOMPARE(listIter.at(0), first);
- QCOMPARE(listIter.at(1), first);
- QCOMPARE(listIter.at(2), second);
- QCOMPARE(listIter.at(3), second);
- QCOMPARE(listIter.at(4), third);
- QCOMPARE(listIter.at(5), third);
-
- if (listIter.metaContainer().canRemoveValue()) {
- listIter.removeValue();
- compareLists();
- QCOMPARE(listIter.size(), 5);
- QCOMPARE(listIter.at(0), first);
- QCOMPARE(listIter.at(1), first);
- QCOMPARE(listIter.at(2), second);
- QCOMPARE(listIter.at(3), second);
- QCOMPARE(listIter.at(4), third);
+ compareLists({first, first, second, second, third, third});
+
+ if (listIter.metaContainer().canRemoveValueAtEnd()) {
+ listIter.removeLast();
+ compareLists({first, first, second, second, third});
} else {
// QString and QByteArray have no pop_back or pop_front and it's unclear what other
// method we should use to remove an item.
@@ -5130,16 +5140,20 @@ static void testSequentialIteration()
QVERIFY(i != listIter.mutableEnd());
*i = QStringLiteral("17");
+ QVariant at0 = hasIndexedAccessors ? listIter.at(0) : *listIter.constBegin();
+
if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<int>())
- QCOMPARE(listIter.at(0).toInt(), 17);
+ QCOMPARE(at0.toInt(), 17);
else if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<bool>())
- QCOMPARE(listIter.at(0).toBool(), false);
+ QCOMPARE(at0.toBool(), false);
*i = QStringLiteral("true");
+ at0 = hasIndexedAccessors ? listIter.at(0) : *listIter.constBegin();
+
if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<int>())
- QCOMPARE(listIter.at(0).toInt(), 0);
+ QCOMPARE(at0.toInt(), 0);
else if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<bool>())
- QCOMPARE(listIter.at(0).toBool(), true);
+ QCOMPARE(at0.toBool(), true);
}
template<typename Container>
@@ -5156,7 +5170,7 @@ static void testAssociativeIteration()
QVariant mappingVariant = QVariant::fromValue(mapping);
QVariantMap varMap = mappingVariant.value<QVariantMap>();
QVariantMap varHash = mappingVariant.value<QVariantMap>();
- QAssociativeIterable mappingIter = mappingVariant.view<QAssociativeIterable>();
+ QMetaAssociation::Iterable mappingIter = mappingVariant.view<QMetaAssociation::Iterable>();
typename Container::const_iterator containerIter = mapping.begin();
const typename Container::const_iterator containerEnd = mapping.end();
@@ -5168,7 +5182,7 @@ static void testAssociativeIteration()
QCOMPARE(qvariant_cast<Mapped>(varMap.value(QString::number(key))), expected);
QCOMPARE(qvariant_cast<Mapped>(varHash.value(QString::number(key))), expected);
QCOMPARE(actual, expected);
- const QAssociativeIterable::const_iterator it = mappingIter.find(key);
+ const QMetaAssociation::Iterable::const_iterator it = mappingIter.find(key);
QVERIFY(it != mappingIter.end());
QCOMPARE(it.value().value<Mapped>(), expected);
}
@@ -5209,7 +5223,7 @@ static void testAssociativeIteration()
container[0] = true;
QVariant containerVariant = QVariant::fromValue(container);
- QAssociativeIterable iter = containerVariant.value<QAssociativeIterable>();
+ QMetaAssociation::Iterable iter = containerVariant.value<QMetaAssociation::Iterable>();
auto f = iter.constFind(QStringLiteral("anything"));
QCOMPARE(f, iter.constEnd());
}
@@ -5217,7 +5231,9 @@ static void testAssociativeIteration()
void tst_QVariant::iterateSequentialContainerElements_data()
{
QTest::addColumn<QFunctionPointer>("testFunction");
-#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T>
+ QTest::addColumn<bool>("hasSizeAccessor");
+ QTest::addColumn<bool>("hasIndexedAccessors");
+#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << true << true
ADD(QQueue<int>);
ADD(QQueue<QVariant>);
ADD(QQueue<QString>);
@@ -5231,14 +5247,19 @@ void tst_QVariant::iterateSequentialContainerElements_data()
ADD(std::vector<int>);
ADD(std::vector<QVariant>);
ADD(std::vector<QString>);
- ADD(std::list<int>);
- ADD(std::list<QVariant>);
- ADD(std::list<QString>);
ADD(QStringList);
ADD(QByteArrayList);
ADD(QString);
ADD(QByteArray);
+#undef ADD
+#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << true << false
+ ADD(std::list<int>);
+ ADD(std::list<QVariant>);
+ ADD(std::list<QString>);
+
+#undef ADD
+#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << false << false
#ifdef TEST_FORWARD_LIST
ADD(std::forward_list<int>);
ADD(std::forward_list<QVariant>);
@@ -5265,12 +5286,16 @@ void tst_QVariant::iterateContainerElements()
QVariantList ints;
ints << 1 << 2 << 3;
QVariant var = QVariant::fromValue(ints);
- QSequentialIterable iter = var.value<QSequentialIterable>();
- QSequentialIterable::const_iterator it = iter.begin();
- QSequentialIterable::const_iterator end = iter.end();
+ QMetaSequence::Iterable iter = var.value<QMetaSequence::Iterable>();
+ QMetaSequence::Iterable::const_iterator it = iter.begin();
+ QMetaSequence::Iterable::const_iterator end = iter.end();
QCOMPARE(ints.at(1), *(it + 1));
- int i = 0;
- for ( ; it != end; ++it, ++i) {
+
+ for (int i = 0, end = ints.size(); i != end; ++i) {
+ QCOMPARE(ints.at(i), it[i]);
+ }
+
+ for (int i = 0; it != end; ++it, ++i) {
QCOMPARE(ints.at(i), *it);
}
@@ -5289,12 +5314,12 @@ void tst_QVariant::iterateContainerElements()
mapping.insert(2, "two");
mapping.insert(3, "three");
QVariant var = QVariant::fromValue(mapping);
- QAssociativeIterable iter = var.value<QAssociativeIterable>();
- QAssociativeIterable::const_iterator it = iter.begin();
- QAssociativeIterable::const_iterator end = iter.end();
+ QMetaAssociation::Iterable iter = var.value<QMetaAssociation::Iterable>();
+ QMetaAssociation::Iterable::const_iterator it = iter.begin();
+ QMetaAssociation::Iterable::const_iterator end = iter.end();
QCOMPARE(*(++mapping.begin()), (*(it + 1)).toString());
- int i = 0;
- for ( ; it != end; ++it, ++i) {
+
+ for (int i = 0; it != end; ++it, ++i) {
QCOMPARE(*(std::next(mapping.begin(), i)), (*it).toString());
}
@@ -5316,7 +5341,7 @@ void tst_QVariant::iterateContainerElements()
container["one"] = 1;
auto containerVariant = QVariant::fromValue(container);
- auto iter = containerVariant.value<QAssociativeIterable>();
+ auto iter = containerVariant.value<QMetaAssociation::Iterable>();
auto value = iter.value("one");
QCOMPARE(value, QVariant(1));
@@ -5345,6 +5370,72 @@ void tst_QVariant::emptyContainerInterface()
QCOMPARE(mutableEnd - mutableBegin, 0);
}
+void tst_QVariant::modifyContainerElements()
+{
+ {
+ QList<int> ints({1, 2, 3});
+ QMetaSequence::Iterable iter;
+ QVERIFY(QMetaType::view(
+ QMetaType::fromType<QList<int>>(), &ints,
+ QMetaType::fromType<QMetaSequence::Iterable>(), &iter));
+ QMetaSequence::Iterable::iterator it = iter.mutableBegin();
+ QMetaSequence::Iterable::iterator end = iter.mutableEnd();
+
+ *(it + 1) = 4;
+ QCOMPARE(ints.at(1), 4);
+
+ for (int i = 0, end = ints.size(); i != end; ++i) {
+ it[i] = i + 10;
+ QCOMPARE(ints.at(i), i + 10);
+ }
+
+ for (int i = 0; it != end; ++it, ++i) {
+ *it = i + 20;
+ QCOMPARE(ints.at(i), i + 20);
+ }
+
+ it = iter.mutableBegin();
+ QCOMPARE(it[0], QVariant(20));
+ QCOMPARE(it[1], QVariant(21));
+ QCOMPARE(it[2], QVariant(22));
+ }
+
+ {
+ QMap<int, QString> mapping({ {1, "one"}, {2, "two"}, {3, "three"} });
+ QMetaAssociation::Iterable iter;
+ QVERIFY(QMetaType::view(
+ QMetaType::fromType<QMap<int, QString>>(), &mapping,
+ QMetaType::fromType<QMetaAssociation::Iterable>(), &iter));
+ QMetaAssociation::Iterable::iterator it = iter.mutableBegin();
+ QMetaAssociation::Iterable::iterator end = iter.mutableEnd();
+ *(it + 1) = QStringLiteral("four");
+ QCOMPARE(*(++mapping.begin()), "four");
+
+ for (int i = 0; it != end; ++it, ++i) {
+ *it = QString::number(i + 10);
+ QCOMPARE(*std::next(mapping.begin(), i), QString::number(i + 10));
+ }
+
+ it = iter.mutableBegin();
+ *(it++) = "one";
+ *(it++) = "two";
+ *(it++) = "three";
+
+ QCOMPARE(mapping, (QMap<int, QString>({ {1, "one"}, {2, "two"}, {3, "three"} })));
+ }
+
+ {
+ QVariantMap container({{"one", 1}});
+ QMetaAssociation::Iterable iter;
+ QMetaType::view(
+ QMetaType::fromType<QVariantMap>(), &container,
+ QMetaType::fromType<QMetaAssociation::Iterable>(), &iter);
+ iter.setValue("one", 5);
+ const auto f = iter.constFind("one");
+ QCOMPARE(*f, QVariant(5));
+ }
+}
+
template <typename Pair> static void testVariantPairElements()
{
QFETCH(std::function<void(void *)>, makeValue);
@@ -5602,9 +5693,9 @@ void tst_QVariant::accessSequentialContainerKey()
QVariant variant = QVariant::fromValue(mapping);
- QAssociativeIterable iterable = variant.value<QAssociativeIterable>();
- QAssociativeIterable::const_iterator iit = iterable.begin();
- const QAssociativeIterable::const_iterator end = iterable.end();
+ QMetaAssociation::Iterable iterable = variant.value<QMetaAssociation::Iterable>();
+ QMetaAssociation::Iterable::const_iterator iit = iterable.begin();
+ const QMetaAssociation::Iterable::const_iterator end = iterable.end();
for ( ; iit != end; ++iit) {
nameResult += iit.key().toString();
}
@@ -5651,7 +5742,7 @@ void tst_QVariant::shouldDeleteVariantDataWorksForSequential()
};
metaSequence.valueMetaType = QtPrivate::qMetaTypeInterfaceForType<MyType>();
- QSequentialIterable iterable(QMetaSequence(&metaSequence), nullptr);
+ QMetaSequence::Iterable iterable(QMetaSequence(&metaSequence), nullptr);
QVariant value1 = iterable.at(0);
QVERIFY(value1.canConvert<MyType>());
QCOMPARE(value1.value<MyType>().number, 1);
@@ -5673,21 +5764,26 @@ void tst_QVariant::shouldDeleteVariantDataWorksForAssociative()
iterator.keyMetaType = QtPrivate::qMetaTypeInterfaceForType<MyType>();
iterator.createConstIteratorFn = [](
const void *, QtMetaContainerPrivate::QMetaContainerInterface::Position) -> void * {
- return nullptr;
+ return new int(21);
};
iterator.advanceConstIteratorFn = [](void *, qsizetype) {};
- iterator.destroyConstIteratorFn = [](const void *){};
- iterator.compareConstIteratorFn = [](const void *, const void *) {
- return true; /*all iterators are nullptr*/
+ iterator.destroyConstIteratorFn = [](const void *it) {
+ delete static_cast<const int *>(it);
+ };
+ iterator.compareConstIteratorFn = [](const void *a, const void *b) {
+ return *static_cast<const int *>(a) == *static_cast<const int *>(b);
};
+
iterator.createConstIteratorAtKeyFn = [](const void *, const void *) -> void * {
- return reinterpret_cast<void *>(quintptr(42));
+ return new int(42);
+ };
+ iterator.copyConstIteratorFn = [](void *a, const void *b) {
+ *static_cast<int *>(a) = *static_cast<const int *>(b);
};
- iterator.copyConstIteratorFn = [](void *, const void *) {};
iterator.diffConstIteratorFn = [](const void *, const void *) -> qsizetype { return 0; };
iterator.keyAtConstIteratorFn = [](const void *iterator, void *dataPtr) -> void {
MyType mytype {1, "key"};
- if (reinterpret_cast<quintptr>(iterator) == 42) {
+ if (*static_cast<const int *>(iterator) == 42) {
mytype.number = 42;
mytype.text = "find_key";
}
@@ -5695,13 +5791,13 @@ void tst_QVariant::shouldDeleteVariantDataWorksForAssociative()
};
iterator.mappedAtConstIteratorFn = [](const void *iterator, void *dataPtr) -> void {
MyType mytype {2, "value"};
- if (reinterpret_cast<quintptr>(iterator) == 42) {
+ if (*static_cast<const int *>(iterator) == 42) {
mytype.number = 42;
mytype.text = "find_value";
}
*static_cast<MyType *>(dataPtr) = mytype;
};
- QAssociativeIterable iterable(QMetaAssociation(&iterator), nullptr);
+ QMetaAssociation::Iterable iterable(QMetaAssociation(&iterator), nullptr);
auto it = iterable.begin();
QVariant value1 = it.key();
QVERIFY(value1.canConvert<MyType>());
@@ -5834,8 +5930,8 @@ void tst_QVariant::sequentialIterableAppend()
{
QList<int> container { 1, 2 };
auto variant = QVariant::fromValue(container);
- QVERIFY(variant.canConvert<QSequentialIterable>());
- QSequentialIterable asIterable = variant.view<QSequentialIterable>();
+ QVERIFY(variant.canConvert<QMetaSequence::Iterable>());
+ QMetaSequence::Iterable asIterable = variant.view<QMetaSequence::Iterable>();
const int i = 3, j = 4;
void *mutableIterable = asIterable.mutableIterable();
asIterable.metaContainer().addValueAtEnd(mutableIterable, &i);
@@ -5854,8 +5950,8 @@ void tst_QVariant::sequentialIterableAppend()
{
QSet<QByteArray> container { QByteArray{"hello"}, QByteArray{"world"} };
auto variant = QVariant::fromValue(std::move(container));
- QVERIFY(variant.canConvert<QSequentialIterable>());
- QSequentialIterable asIterable = variant.view<QSequentialIterable>();
+ QVERIFY(variant.canConvert<QMetaSequence::Iterable>());
+ QMetaSequence::Iterable asIterable = variant.view<QMetaSequence::Iterable>();
QByteArray qba1 {"goodbye"};
QByteArray qba2 { "moon" };
void *mutableIterable = asIterable.mutableIterable();
@@ -5875,15 +5971,15 @@ void tst_QVariant::preferDirectConversionOverInterfaces()
static bool calledCorrectConverter = false;
calledCorrectConverter = false;
- QMetaType::registerConverter<MyType, QSequentialIterable>([](const MyType &) {
- return QSequentialIterable {};
+ QMetaType::registerConverter<MyType, QMetaSequence::Iterable>([](const MyType &) {
+ return QMetaSequence::Iterable {};
});
QMetaType::registerConverter<MyType, QVariantList>([&](const MyType &) {
calledCorrectConverter = true;
return QVariantList {};
});
- QMetaType::registerConverter<MyType, QAssociativeIterable>([](const MyType &) {
- return QAssociativeIterable {};
+ QMetaType::registerConverter<MyType, QMetaAssociation::Iterable>([](const MyType &) {
+ return QMetaAssociation::Iterable {};
});
QMetaType::registerConverter<MyType, QVariantHash>([&](const MyType &) {
calledCorrectConverter = true;
@@ -5895,9 +5991,9 @@ void tst_QVariant::preferDirectConversionOverInterfaces()
});
auto holder = QVariant::fromValue(MyType {});
- QVERIFY(holder.canConvert<QSequentialIterable>());
+ QVERIFY(holder.canConvert<QMetaSequence::Iterable>());
QVERIFY(holder.canConvert<QVariantList>());
- QVERIFY(holder.canConvert<QAssociativeIterable>());
+ QVERIFY(holder.canConvert<QMetaAssociation::Iterable>());
QVERIFY(holder.canConvert<QVariantHash>());
QVERIFY(holder.canConvert<QVariantMap>());
diff --git a/tests/auto/gui/kernel/qguivariant/test/tst_qguivariant.cpp b/tests/auto/gui/kernel/qguivariant/test/tst_qguivariant.cpp
index ff78d1a1d1f..0ca09ed0ba2 100644
--- a/tests/auto/gui/kernel/qguivariant/test/tst_qguivariant.cpp
+++ b/tests/auto/gui/kernel/qguivariant/test/tst_qguivariant.cpp
@@ -359,7 +359,7 @@ void tst_QGuiVariant::toString_data()
#endif
QFont font( "times", 12 );
- QTest::newRow("qfont") << QVariant::fromValue(font) << QString("times,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0");
+ QTest::newRow("qfont") << QVariant::fromValue(font) << QString("times,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,0");
QTest::newRow( "qcolor" ) << QVariant::fromValue( QColor( 10, 10, 10 ) ) << QString( "#0a0a0a" );
}
diff --git a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp
index 3272ffac0ee..18d8b604dff 100644
--- a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp
+++ b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp
@@ -4,6 +4,9 @@
#include <QVector2D>
#include <QVector3D>
#include <QVector4D>
+
+#include <QtCore/qdatastream.h>
+
#ifdef QVARIANT_H
# error "This test requires qvector{2,3,4}d.h to not include qvariant.h"
#endif
@@ -162,6 +165,8 @@ private slots:
void metaTypes();
void structuredBinding();
+ void nonFiniteValuesStreamingRoundTrip_data();
+ void nonFiniteValuesStreamingRoundTrip();
};
// Test the creation of QVector2D objects in various ways:
@@ -2759,6 +2764,78 @@ void tst_QVectorND::structuredBinding()
}
}
+void tst_QVectorND::nonFiniteValuesStreamingRoundTrip_data()
+{
+ QTest::addColumn<float>("value");
+
+ constexpr auto inf = std::numeric_limits<float>::infinity();
+ constexpr auto NaN = std::numeric_limits<float>::quiet_NaN();
+
+ QTest::addRow("+∞") << +inf;
+ QTest::addRow("-∞") << -inf;
+ QTest::addRow("NaN") << NaN;
+
+}
+
+void tst_QVectorND::nonFiniteValuesStreamingRoundTrip()
+{
+ QFETCH(const float, value);
+
+ const QVector2D i2{value, value};
+ const QVector3D i3{value, value, value};
+ const QVector4D i4{value, value, value, value};
+
+ QByteArray buffer;
+
+ {
+ QDataStream s(&buffer, QIODevice::WriteOnly);
+ s << i2 << i3 << i4;
+ QCOMPARE(s.status(), QDataStream::Status::Ok);
+ }
+
+ {
+ QVector2D o2 = {0, 0};
+ QVector3D o3 = {1, 0, -1};
+ QVector4D o4 = {0, 1, 2, 3};
+
+ QDataStream s(&buffer, QIODevice::ReadOnly);
+ s >> o2;
+ QCOMPARE(s.status(), QDataStream::Status::Ok);
+ s >> o3;
+ QCOMPARE(s.status(), QDataStream::Status::Ok);
+ s >> o4;
+ QCOMPARE(s.status(), QDataStream::Status::Ok);
+
+ constexpr auto convert_to_binary = [](float v) {
+ uint r;
+ static_assert(sizeof v == sizeof r);
+ memcpy(&r, &v, sizeof v);
+ return r;
+ };
+
+ #define CHECK(n, what) \
+ do { \
+ const auto i ## n ## what = convert_to_binary(i ## n . what ()); \
+ const auto o ## n ## what = convert_to_binary(o ## n . what ()); \
+ QCOMPARE(i ## n ## what, o ## n ## what); \
+ } while (false)
+
+ CHECK(2, x);
+ CHECK(2, y);
+
+ CHECK(3, x);
+ CHECK(3, y);
+ CHECK(3, z);
+
+ CHECK(4, x);
+ CHECK(4, y);
+ CHECK(4, z);
+ CHECK(4, w);
+
+ #undef CHECK
+ }
+}
+
QTEST_APPLESS_MAIN(tst_QVectorND)
#include "tst_qvectornd.moc"
diff --git a/tests/auto/gui/text/qfont/tst_qfont.cpp b/tests/auto/gui/text/qfont/tst_qfont.cpp
index 8c4b8c75a26..9771071a749 100644
--- a/tests/auto/gui/text/qfont/tst_qfont.cpp
+++ b/tests/auto/gui/text/qfont/tst_qfont.cpp
@@ -53,7 +53,7 @@ private slots:
void fromStringCompatibility_data();
void fromStringCompatibility();
void fromStringWithoutStyleName();
- void fromStringWithoutFeatures();
+ void fromStringWithoutFeaturesOrVariableAxes();
void fromDegenerateString_data();
void fromDegenerateString();
@@ -672,12 +672,22 @@ void tst_QFont::fromStringCompatibility_data()
QTest::addRow("Times New Roman, Qt 6.0") << false << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular") << fontFrom60;
QFont fontFrom611 = fontFrom60;
- QTest::addRow("Times New Roman, Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,0") << fontFrom611;
+ QTest::addRow("Times New Roman (without font features and variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,0,0") << fontFrom611;
QFont fontFrom611WithFeatures = fontFrom60;
fontFrom611WithFeatures.setFeature("frac", 1);
fontFrom611WithFeatures.setFeature("liga", 0);
- QTest::addRow("Times New Roman (with features), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,2,frac=1,liga=0") << fontFrom611WithFeatures;
+ QTest::addRow("Times New Roman (with features), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,2,frac=1,liga=0,0") << fontFrom611WithFeatures;
+
+ QFont fontFrom611WithVariableAxes = fontFrom60;
+ fontFrom611WithVariableAxes.setVariableAxis("wght", 12.34f);
+ QTest::addRow("Times New Roman (with variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,0,1,wght=12.34") << fontFrom611WithVariableAxes;
+
+ QFont fontFrom611WithFontFeaturesAndVariableAxes = fontFrom60;
+ fontFrom611WithFontFeaturesAndVariableAxes.setFeature("frac", 1);
+ fontFrom611WithFontFeaturesAndVariableAxes.setFeature("liga", 0);
+ fontFrom611WithFontFeaturesAndVariableAxes.setVariableAxis("wght", 12.34f);
+ QTest::addRow("Times New Roman (with font features and variable axes), Qt 6.11") << true << QStringLiteral("Times New Roman,18,-1,5,700,1,0,0,1,0,1,0,150.5,2.5,50,2,Regular,2,frac=1,liga=0,1,wght=12.34") << fontFrom611WithFontFeaturesAndVariableAxes;
}
void tst_QFont::fromStringCompatibility()
@@ -745,17 +755,46 @@ void tst_QFont::fromStringWithoutStyleName()
}
}
-void tst_QFont::fromStringWithoutFeatures()
+void tst_QFont::fromStringWithoutFeaturesOrVariableAxes()
{
- // This test verifies that the font feature list will be reset if the from string contains no features.
+ // This test verifies that the font feature and the variable axis list will be reset if the string
+ // contains no features or variable axes, respectively.
- const QString fontStringWithoutFeatures = QStringLiteral("Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1");
+ const QString fontStringWithoutFeaturesAndVariableAxes = QStringLiteral("Noto Sans,12,-1,5,400,0,0,0,0,0,0,0,0,0,0,1");
const QString fontStringWithFeatures = QStringLiteral("Noto Sans,18,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,2,calt=0,frac=1");
+ const QString fontStringWithVariableAxes = QStringLiteral("Noto Sans,18,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,,0,1,wght=12.34");
- QFont font;
- font.fromString(fontStringWithFeatures);
- font.fromString(fontStringWithoutFeatures);
- QVERIFY(font.featureTags().isEmpty());
+ {
+ QFont font;
+ font.fromString(fontStringWithFeatures);
+ font.fromString(fontStringWithoutFeaturesAndVariableAxes);
+ QVERIFY(font.featureTags().isEmpty());
+ QVERIFY(font.variableAxisTags().isEmpty());
+ }
+
+ {
+ QFont font;
+ font.fromString(fontStringWithVariableAxes);
+ font.fromString(fontStringWithoutFeaturesAndVariableAxes);
+ QVERIFY(font.featureTags().isEmpty());
+ QVERIFY(font.variableAxisTags().isEmpty());
+ }
+
+ {
+ QFont font;
+ font.fromString(fontStringWithFeatures);
+ font.fromString(fontStringWithVariableAxes);
+ QVERIFY(font.featureTags().isEmpty());
+ QVERIFY(!font.variableAxisTags().isEmpty());
+ }
+
+ {
+ QFont font;
+ font.fromString(fontStringWithVariableAxes);
+ font.fromString(fontStringWithFeatures);
+ QVERIFY(!font.featureTags().isEmpty());
+ QVERIFY(font.variableAxisTags().isEmpty());
+ }
}
void tst_QFont::fromDegenerateString_data()
diff --git a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp
index 2814045bfbf..f21fb37f0e0 100644
--- a/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp
+++ b/tests/auto/network/socket/qtcpsocket/tst_qtcpsocket.cpp
@@ -128,6 +128,7 @@ private slots:
void readChunks();
void waitForBytesWritten();
void waitForBytesWrittenMinusOne();
+ void waitForBytesWrittenWriteInReadyReadSlot();
void waitForReadyRead();
void waitForReadyReadMinusOne();
void flush();
@@ -1682,6 +1683,52 @@ void tst_QTcpSocket::waitForBytesWrittenMinusOne()
}
//----------------------------------------------------------------------------------
+void tst_QTcpSocket::waitForBytesWrittenWriteInReadyReadSlot()
+{
+ QFETCH_GLOBAL(bool, setProxy);
+ if (setProxy)
+ return;
+
+ SocketPair socketPair;
+ QVERIFY(socketPair.create());
+ QTcpSocket *client = socketPair.endPoints[0];
+ QTcpSocket *server = socketPair.endPoints[1];
+
+ QCOMPARE(client->state(), QTcpSocket::ConnectedState);
+ QCOMPARE(server->state(), QTcpSocket::ConnectedState);
+
+ server->write("ServerHello");
+ server->waitForBytesWritten();
+
+ // Make sure that the data from server has made it to client, but only read one byte from
+ // the OS buffer so that future polling for read will find more data ready.
+ client->setReadBufferSize(1);
+ QVERIFY(client->waitForReadyRead());
+
+ uint readyReadCount = 0;
+ connect(client, &QAbstractSocket::readyRead, [client, &readyReadCount]() {
+ // The commented out code is not necessary, but typically part of a real scenario
+ // client->readAll();
+ // client->write("response to whatever was read");
+ readyReadCount++;
+ client->flush();
+ });
+
+ client->write("ClientHello");
+
+ // Allow to fetch more data from the OS buffer ("new data", causes emission of readyRead())
+ client->setReadBufferSize(0);
+ QCOMPARE(readyReadCount, 0); // we missed the one from the first waitForReadyRead()
+
+ // If there is incoming data, waitForBytesWritten() emits readyRead() even *before* writing
+ // data, so if the readyRead handler already flushes outgoing data, the subsequent attempt
+ // in waitForBytesWritten() to flush outgoing data will fail.
+ // This tests that that doesn't happen anymore.
+ QVERIFY(client->waitForBytesWritten());
+ QCOMPARE(readyReadCount, 1);
+}
+
+//----------------------------------------------------------------------------------
void tst_QTcpSocket::waitForReadyRead()
{
QTcpSocket *socket = newSocket();
diff --git a/tests/auto/network/ssl/qsslsocket/certs/no_common_name.crt b/tests/auto/network/ssl/qsslsocket/certs/no_common_name.crt
new file mode 100644
index 00000000000..201519431ca
--- /dev/null
+++ b/tests/auto/network/ssl/qsslsocket/certs/no_common_name.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDTDCCAjSgAwIBAgIUHVbLylWxSla5Ip55WjqQ767yHaYwDQYJKoZIhvcNAQEL
+BQAwKzETMBEGA1UECgwKQ2xpZW50Q2VydDEUMBIGA1UECwwLTG9jYWxDbGllbnQw
+HhcNMjUxMjAxMTAzNTExWhcNMjYxMjAxMTAzNTExWjArMRMwEQYDVQQKDApDbGll
+bnRDZXJ0MRQwEgYDVQQLDAtMb2NhbENsaWVudDCCASIwDQYJKoZIhvcNAQEBBQAD
+ggEPADCCAQoCggEBAMzTT65W+Cq769PwteURV0hAjdGMb8eJZ9sb+msw3kxr/y/F
+k0qz1ksIimuyxq/eJzIX691ntCflYwPYWXRy1jvG+k9X9rYdFOggVzcyWwpW/jMV
+XeWAiSL6gqRzgBuD4AkU+qNgz6sLTZHaPim7zM3P/Vpz90Vkl+oN6schll0g4jEn
+KUTrIeu5WQukeLPofHRPAZZzP5PFbJRUnOYqjd1ohwLtpGGVj4yvJJBDOXxuwOHw
+Nxww8+u4Y9o1Wo1yGaaKMgr7lZSt85sHkITFTWBuDF8Z8MhpFDtZsk1Vx+7hC4py
+zaPBQqC9vjGBQJzDr9Knv7MSMHArvC8ly2uRAZcCAwEAAaNoMGYwHQYDVR0OBBYE
+FPEdy10KbujGEsb5Zd6a4Al24ZqwMB8GA1UdIwQYMBaAFPEdy10KbujGEsb5Zd6a
+4Al24ZqwMA8GA1UdEwEB/wQFMAMBAf8wEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJ
+KoZIhvcNAQELBQADggEBAGnGmVA8IyMRHLRCMwHNFnuOSDaAwtKWxnhZZcdGYkeU
+kvR52vsi+Wo6/AAX90St8lYUSssEzi7HgH1Reaju45/X7SGxs/39qAz/J5c7tLRy
+M+F9gY6qEv8Yzrvn7Kje20xwg7PVeJEdn45Zg5HILBO+xkGWBVgrVhrX61HkSjRt
+nHbZH04Bq85VEKlFIauD801cwi84B0xShosV67OkY6i0cnqC634QacJH5XVeQR3s
+UTXsfN6/aCBcKAJpKaERv2ISVq57SkvCvLs3vTzIXKMFVRUjOMOF5z8lD/An3hZ8
+RnIHOmjPYG4UdW+G5yITIog5eRVF918cjmDGZNRMklk=
+-----END CERTIFICATE-----
diff --git a/tests/auto/network/ssl/qsslsocket/certs/no_common_name.key b/tests/auto/network/ssl/qsslsocket/certs/no_common_name.key
new file mode 100644
index 00000000000..661d03b84be
--- /dev/null
+++ b/tests/auto/network/ssl/qsslsocket/certs/no_common_name.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDM00+uVvgqu+vT
+8LXlEVdIQI3RjG/HiWfbG/prMN5Ma/8vxZNKs9ZLCIprssav3icyF+vdZ7Qn5WMD
+2Fl0ctY7xvpPV/a2HRToIFc3MlsKVv4zFV3lgIki+oKkc4Abg+AJFPqjYM+rC02R
+2j4pu8zNz/1ac/dFZJfqDerHIZZdIOIxJylE6yHruVkLpHiz6Hx0TwGWcz+TxWyU
+VJzmKo3daIcC7aRhlY+MrySQQzl8bsDh8DccMPPruGPaNVqNchmmijIK+5WUrfOb
+B5CExU1gbgxfGfDIaRQ7WbJNVcfu4QuKcs2jwUKgvb4xgUCcw6/Sp7+zEjBwK7wv
+JctrkQGXAgMBAAECggEAFuXWphBN7QUWH5rs0r9mCQtCb3cqNd3cEOgnTh1n9JYs
+MIx/Y14Ialn5k4GoaZfFvPlkoltKDh28PH1OvtBpt8QOTplwWLqWkD6xUVfdSqIg
+B9jvJs2ARztHKJhK7YiIHqvMO0CC5sW8Nb52rZazlhyW36pQLd9Jhl5o7TsJgr6L
+kEb7YGV62/vWwVE0oVBooqbejep1geU/VTteheICroLa4+toPIgGWWJ9upg995Nw
+xoS4/DWNBoXKs3T7Awjvfn1qsxWvSO8T14Z2XMgyMJOuVhEp6X4DpX8NU+M+D6NB
+TKIPuXpktAR6gRU3mvh7f+uxbMwIgtbyPHfcHPRFbQKBgQDzMQ/JzPDBL6xSbqrl
+hY6xD0p86iu5P2tfPBpaizGhFvP0OHG8j4mffuzaphvViIZ/ektws70hmxvYWs8q
+MSXxxDXHHDcNX5haencU/wSAz7dQqrl8LOnVeBzlmeObtgDLw9OmLUXNHoFNuY9D
+AqmkpwRz49ZPmdEAHr1GegQx3QKBgQDXnPXS1TCdqBefTYBhs/ddmxb10lAbB13M
+5T65Cxg2gFf6I4W8dcYp1TqbOIvl30cFvlEFD49Cj/eT24Q467MpqzHn/SH88Xoi
+E9FJJUJ8asWNMEelzL/4QAHXe4lyF5m4uvS/qtIbpFUx2dgTJ4ZmExWqYSJcL4/c
+WmlNeu1cAwKBgQCuXkkhuk4NVi9KU4s5Uo/DKGGSOxzqkCxedmu27ALDq/9y5l22
+g3x73bfZ9iwS6Pb2xCr/PgCn7d0DPek4KVE5jiO5BeP7NMW6agCkD02dRlH8Bs1D
+2bg3lQ2zGqn15YOglmJUzjU0I2E254tu0qPsKMyqg3wQSwtt+Jxhwe7sCQKBgCOg
+duoQegkC9mxHNRhv0UbxUnjp+HyO2gv6MUQINkcDLAZUCkwatdTBu/5b+JnSK/0h
+9mc8q/JWsZUH57A0GhWfiQ6JQC14hTLOTX2ln3fJeL0cpioaS/osMWG2sv5cMfVZ
+RwnIoxEYNU+YbGC13jpNmv3dMP1EiqPheJbp4gCbAoGAc/4sgk8ySkpJJNaAydlo
+vZEp+UZGmVmvG9cw75qVWOWgQ2C8ExmLnxh2DEHzEJs+Y8DQgLzrZLD4rZfndWNC
+1DLE0WH69KJXhA/yu6GQgkQqZDXvMV3Cts2ofjVKyP8esGnsv+dCxR1uRxfKL0+m
+LSD+QHdz3uex3ip2TQA4Tm8=
+-----END PRIVATE KEY-----
diff --git a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
index 000a75a6284..ee1043bc725 100644
--- a/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
+++ b/tests/auto/network/ssl/qsslsocket/tst_qsslsocket.cpp
@@ -157,6 +157,7 @@ private slots:
void connectToHostEncrypted();
void connectToHostEncryptedWithVerificationPeerName();
void sessionCipher();
+ void localCertificate_data();
void localCertificate();
void mode();
void peerCertificate();
@@ -1208,11 +1209,22 @@ void tst_QSslSocket::sessionCipher()
QVERIFY(socket->waitForDisconnected());
}
+void tst_QSslSocket::localCertificate_data()
+{
+ QTest::addColumn<QString>("certificatePath");
+ QTest::addColumn<QString>("keyPath");
+ QTest::newRow("fluke") << (testDataDir + "certs/fluke.cert") << (testDataDir + "certs/fluke.key");
+ QTest::newRow("no-common-name") << (testDataDir + "certs/no_common_name.crt") << (testDataDir + "certs/no_common_name.key");
+}
+
void tst_QSslSocket::localCertificate()
{
if (!QSslSocket::supportsSsl())
return;
+ QFETCH(QString, certificatePath);
+ QFETCH(QString, keyPath);
+
// This test does not make 100% sense yet. We just set some local CA/cert/key and use it
// to authenticate ourselves against the server. The server does not actually check this
// values. This test should just run the codepath inside qsslsocket_openssl.cpp
@@ -1224,8 +1236,10 @@ void tst_QSslSocket::localCertificate()
sslConfig.setCaCertificates(localCert);
socket->setSslConfiguration(sslConfig);
- socket->setLocalCertificate(testDataDir + "certs/fluke.cert");
- socket->setPrivateKey(testDataDir + "certs/fluke.key");
+ socket->setLocalCertificate(certificatePath);
+ socket->setPrivateKey(keyPath);
+ QVERIFY(!socket->localCertificateChain().isEmpty());
+ QVERIFY(!socket->privateKey().isNull());
socket->connectToHostEncrypted(QtNetworkSettings::httpServerName(), 443);
QFETCH_GLOBAL(bool, setProxy);
diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
index 305f48c95ee..c65f6645d01 100644
--- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
+++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp
@@ -3582,6 +3582,22 @@ void tst_QAccessibility::tableTest()
tableView->horizontalHeader()->setVisible(false);
}
+ {
+ QTestAccessibility::clearEvents();
+ auto cell0 = table2->cellAt(0, 2);
+ auto cell1 = table2->cellAt(1, 2);
+ auto cell2 = table2->cellAt(2, 2);
+ auto cell3 = table2->cellAt(3, 2);
+ QAccessibleObjectDestroyedEvent event0(cell0);
+ QAccessibleObjectDestroyedEvent event1(cell1);
+ QAccessibleObjectDestroyedEvent event2(cell2);
+ QAccessibleObjectDestroyedEvent event3(cell3);
+ tableView->removeColumn(2);
+ QVERIFY_EVENT(&event0);
+ QVERIFY_EVENT(&event1);
+ QVERIFY_EVENT(&event2);
+ QVERIFY_EVENT(&event3);
+ }
tvHolder.reset();
QVERIFY(!QAccessible::accessibleInterface(id00));
QTestAccessibility::clearEvents();
diff --git a/tests/auto/tools/moc/allmocs_baseline_in.json b/tests/auto/tools/moc/allmocs_baseline_in.json
index d8e6c4df538..d36bdb907ae 100644
--- a/tests/auto/tools/moc/allmocs_baseline_in.json
+++ b/tests/auto/tools/moc/allmocs_baseline_in.json
@@ -23,6 +23,9 @@
]
}
],
+ "hashes": {
+ "BackslashNewlines": "0$VAkStqKr6pw5W81yZ3rCFn98h9U"
+ },
"inputFile": "backslash-newlines.h",
"outputRevision": 69
},
@@ -41,6 +44,9 @@
]
}
],
+ "hashes": {
+ "IfdefedClass": "0$OochknExuVUAqq2zc5gt2Ldj4+4"
+ },
"inputFile": "c-comments.h",
"outputRevision": 69
},
@@ -75,6 +81,9 @@
"qualifiedClassName": "CStyleEnums"
}
],
+ "hashes": {
+ "CStyleEnums": "0$fqbHabuJUT79FJr/S/6WsAiFcJI"
+ },
"inputFile": "cstyle-enums.h",
"outputRevision": 69
},
@@ -325,6 +334,11 @@
]
}
],
+ "hashes": {
+ "CXX11Enums": "0$SPzFG1raX8GCqiq6g21PoU/7ixs",
+ "CXX11Enums2": "0$uYFCI2+nT8TI8jLcKAvO8vp9OYA",
+ "CXX11Enums3": "0$bRezVo4UbDNuGatmLEd0/n8K8FA"
+ },
"inputFile": "cxx11-enums.h",
"outputRevision": 69
},
@@ -727,6 +741,17 @@
]
}
],
+ "hashes": {
+ "ExplicitOverrideControlBase": "0$PFsRqLGh0wP5gfqwF9CVeUi9EVU",
+ "ExplicitOverrideControlFinalCxx11": "0$RQTN9wEJCVZ1N4IcieHs2LuRKY8",
+ "ExplicitOverrideControlFinalCxx11OverrideCxx11": "0$ZEBCUhi5oEGiDVdmoqD+x4n1KJk",
+ "ExplicitOverrideControlFinalQt": "0$GuS6YD8Kcf/9KyehmFsrmDWW42M",
+ "ExplicitOverrideControlFinalQtOverrideQt": "0$q48mSBftA2LJFVSB4uqTmKs2gOc",
+ "ExplicitOverrideControlOverrideCxx11": "0$he1BJyFAtIcrEf74bZY1d54aCbc",
+ "ExplicitOverrideControlOverrideQt": "0$dWQ+iDw9oKEYXiSgy2izIISyRMg",
+ "ExplicitOverrideControlSealed": "0$3nq5psg7nFNDlPnBpaYraCr3CHQ",
+ "ExplicitOverrideControlSealedOverride": "0$I7ySe3SgUSARroswRh74wJoZkkA"
+ },
"inputFile": "cxx11-explicit-override-control.h",
"outputRevision": 69
},
@@ -850,6 +875,17 @@
]
}
],
+ "hashes": {
+ "ExportedFinalTestClassCpp11": "0$eUEIDQi+/lB9KOjB+oul4rZe9xE",
+ "ExportedFinalTestClassCpp11X": "0$g03sLKAEeX8BLjKjFsG3MMfunaY",
+ "ExportedFinalTestClassQt": "0$fYFeeRSmEnokVTW4eIiCU6PR5HM",
+ "ExportedFinalTestClassQtX": "0$7+xgvb70xVV3Xot9q7FhSm8mUew",
+ "ExportedSealedTestClass": "0$jzcARHlbTm1whxXOtg9edtN1ctg",
+ "ExportedSealedTestClassX": "0$tEmYiad6hqJMlGaCnNc2GhZtDgM",
+ "FinalTestClassCpp11": "0$Opb/8DnbfVjpH2CZvHo9Rv93iV8",
+ "FinalTestClassQt": "0$DtnHOKRNbQ5Y3QbO5AeLM4qYDW0",
+ "SealedTestClass": "0$VUU19XJgT+KhqRBlwQmXkd0yVgQ"
+ },
"inputFile": "cxx11-final-classes.h",
"outputRevision": 69
},
@@ -937,6 +973,9 @@
]
}
],
+ "hashes": {
+ "CXX11TrailingReturn": "0$UrmUM8dix1r2pcj94cUzTALq0x8"
+ },
"inputFile": "cxx11-trailing-return.h",
"outputRevision": 69
},
@@ -977,6 +1016,10 @@
"qualifiedClassName": "CXX17Namespace::A::B::C::D"
}
],
+ "hashes": {
+ "CXX17Namespace::A::B::C::D": "0$WwEpzp6jKMGQfcvBrtsAJOy01KM",
+ "CXX17Namespace::A::B::C::D::ClassInNamespace": "0$/g09nq8R/tK4tC37pIgfRw/mBog"
+ },
"inputFile": "cxx17-namespaces.h",
"outputRevision": 69
},
@@ -1007,6 +1050,9 @@
]
}
],
+ "hashes": {
+ "DirInIncludePath": "0$Yj0uFD5gTp9Ie67oMAyjAEUetbo"
+ },
"inputFile": "dir-in-include-path.h",
"outputRevision": 69
},
@@ -1025,6 +1071,9 @@
]
}
],
+ "hashes": {
+ "Foo": "0$CB6VdqlszayDKadxVcYxPz1KzvI"
+ },
"inputFile": "enum_with_include.h",
"outputRevision": 69
},
@@ -1057,6 +1106,9 @@
]
}
],
+ "hashes": {
+ "StringLiterals": "0$ZMhjewm0f9PVVN6Nf31Slot+IRo"
+ },
"inputFile": "escapes-in-string-literals.h",
"outputRevision": 69
},
@@ -1295,6 +1347,9 @@
]
}
],
+ "hashes": {
+ "ForwardDeclaredParamClass": "0$1v+gvQyQz9IWyRMSBZJfmhZpCw4"
+ },
"inputFile": "forward-declared-param.h",
"outputRevision": 69
},
@@ -1329,6 +1384,9 @@
]
}
],
+ "hashes": {
+ "FunctionWithAttributes": "0$PHwt9wN4wGfmeCWQSXJ4CFyLKCk"
+ },
"inputFile": "function-with-attributes.h",
"outputRevision": 69
},
@@ -1364,6 +1422,10 @@
]
}
],
+ "hashes": {
+ "DerivedGadgetWithEnums": "0$ziLf2XE3jIzXb4mHc1AtXn2Pr+w",
+ "GadgetWithNoEnums": "0$Jahs+W0ABVb+g0vSoUQ80hLQCzk"
+ },
"inputFile": "gadgetwithnoenums.h",
"outputRevision": 69
},
@@ -1400,6 +1462,11 @@
]
}
],
+ "hashes": {
+ "GrandParentGadget::BaseGadget": "0$edRI9jp0xvXAhzuu71v5ByBmvbw",
+ "GrandParentGadget::CRTPDerivedGadget": "0$Y3W4552o/BUqjdHf2NxgqKGkRmI",
+ "GrandParentGadget::DerivedGadget": "0$AhUcr6i4Hne/y216sE3UnzvaBks"
+ },
"inputFile": "grand-parent-gadget-class.h",
"outputRevision": 69
},
@@ -1471,6 +1538,10 @@
"qualifiedClassName": "SomeRandomNamespace"
}
],
+ "hashes": {
+ "SomeRandomNamespace": "0$haTJ0XROzXzF0eXH4sreUExdPxg",
+ "TestFwdProperties": "0$3+84uwm9jhX5WgZzqClAZM6h7Hg"
+ },
"inputFile": "moc_include.h",
"outputRevision": 69
},
@@ -1541,6 +1612,11 @@
"qualifiedClassName": "FooNamespace::FooNestedNamespace::FooMoreNestedNamespace"
}
],
+ "hashes": {
+ "FooNamespace": "0$Z0uaI0MMFqu0YzU7vExYNRUcBhs",
+ "FooNamespace::FooNestedNamespace": "0$c0mGYICDOoC4QPdVm5HRH9ApH68",
+ "FooNamespace::FooNestedNamespace::FooMoreNestedNamespace": "0$FHRRWVs1L+xEcXWY318xVxYlx+Y"
+ },
"inputFile": "namespace.h",
"outputRevision": 69
},
@@ -1572,6 +1648,10 @@
]
}
],
+ "hashes": {
+ "QTBUG_101141::Base": "0$U366MOIqv3M+uAirFGzc/YkYdoA",
+ "QTBUG_101141::Derived": "0$RhyA1CgCTHJhP+oLRVIDYv85oNQ"
+ },
"inputFile": "namespaced-base-class.h",
"outputRevision": 69
},
@@ -1664,6 +1744,10 @@
]
}
],
+ "hashes": {
+ "Foo::Bar": "0$bYnwxHCP+WvK/RySc0Cz7hJ/Mv4",
+ "Foo::Baz": "0$/xEtUkDT353vJHNrTTLCovKNWyo"
+ },
"inputFile": "namespaced-flags.h",
"outputRevision": 69
},
@@ -1700,6 +1784,9 @@
]
}
],
+ "hashes": {
+ "MyBooooooostishClass": "0$RDRAZTtVcMrrVnaW3ccG5tm5kvU"
+ },
"inputFile": "no-keywords.h",
"outputRevision": 69
},
@@ -1718,6 +1805,9 @@
]
}
],
+ "hashes": {
+ "NonGadgetParent::Derived": "0$3bsnxOwlHgO7cAngQLFI5JRtpDE"
+ },
"inputFile": "non-gadget-parent-class.h",
"outputRevision": 69
},
@@ -1784,6 +1874,9 @@
]
}
],
+ "hashes": {
+ "OldStyleCast": "0$tg567Jxb/wczCGYgDo7CHjLQaF8"
+ },
"inputFile": "oldstyle-casts.h",
"outputRevision": 69
},
@@ -2027,6 +2120,9 @@
]
}
],
+ "hashes": {
+ "PD::ParseDefine": "0$gtPGvhjjReF7/9ujAvwWNaRWEdk"
+ },
"inputFile": "parse-defines.h",
"outputRevision": 69
},
@@ -2045,6 +2141,9 @@
]
}
],
+ "hashes": {
+ "TestPluginMetaData": "0$D43pha4BOvdLMUbKSF0Pw0b2rfQ"
+ },
"inputFile": "plugin_metadata.h",
"outputRevision": 69
},
@@ -2149,6 +2248,9 @@
]
}
],
+ "hashes": {
+ "TestPointeeCanBeIncomplete": "0$BXzN6PCuKk/qhZeYZaaNyUNKW/8"
+ },
"inputFile": "pointery_to_incomplete.h",
"outputRevision": 69
},
@@ -2230,6 +2332,10 @@
]
}
],
+ "hashes": {
+ "PureVirtualSignalsImpl": "0$+RHy7vy0RgivuAtQW3Dohe0R+qk",
+ "PureVirtualSignalsTest": "0$31whJazqnFwWMc3cO9MaT9wBSy8"
+ },
"inputFile": "pure-virtual-signals.h",
"outputRevision": 69
},
@@ -2302,6 +2408,10 @@
]
}
],
+ "hashes": {
+ "QEnum64Object": "0$mPIUjAUmfYvqxkkkXghMo9RAiZA",
+ "QFlags64Object": "0$o86jnlU0tZ0uyjOrEZ4/oOXAKfk"
+ },
"inputFile": "qflags64object.h",
"outputRevision": 69
},
@@ -2358,6 +2468,10 @@
]
}
],
+ "hashes": {
+ "InvokableBeforeInline": "0$t5nSESnZz1Liw62inFXd2SZTQTg",
+ "InvokableBeforeReturnType": "0$q7unyP9EifVhdxdSVIaFweQMDs4"
+ },
"inputFile": "qinvokable.h",
"outputRevision": 69
},
@@ -2397,6 +2511,9 @@
]
}
],
+ "hashes": {
+ "QmlMacro": "0$ohmDXSSvwPL9cUf7TItBqGRGOXM"
+ },
"inputFile": "qmlmacro.h",
"outputRevision": 69
},
@@ -2433,6 +2550,9 @@
]
}
],
+ "hashes": {
+ "TestQPrivateSlots": "0$akM0QTpV2o3AF3v6YBUh+iY1NsM"
+ },
"inputFile": "qprivateslots.h",
"outputRevision": 69
},
@@ -2456,6 +2576,9 @@
"qualifiedClassName": "QTBUG_35657::A"
}
],
+ "hashes": {
+ "QTBUG_35657::A": "0$ExnqnOcM0keSitP69QEKBZXt4Es"
+ },
"inputFile": "qtbug-35657-gadget.h",
"outputRevision": 69
},
@@ -2490,6 +2613,9 @@
]
}
],
+ "hashes": {
+ "QTBUG_35657::B": "0$72RlEvuVajxk55rzeym5e5E7LmA"
+ },
"inputFile": "related-metaobjects-in-gadget.h",
"outputRevision": 69
},
@@ -2547,6 +2673,10 @@
]
}
],
+ "hashes": {
+ "QTBUG_2151::A": "0$wwzOwB54lloQnRalZ9IiAf2oCII",
+ "QTBUG_2151::B": "0$b4fk7Aaf9GubBohh4VCfABMTEJM"
+ },
"inputFile": "related-metaobjects-in-namespaces.h",
"outputRevision": 69
},
@@ -3029,6 +3159,28 @@
]
}
],
+ "hashes": {
+ "NS1::DependingNestedGadget": "0$AjMR5Q0D2DupU0l/8Fl2wE0HX/w",
+ "NS1::DependingNestedObject": "0$DopB5FLdxxGgVBjg2pb8LDui8hA",
+ "NS1::DependingObject": "0$NBnkXMTHy3+HN77VtSKqsQrn2Ds",
+ "NS1::Gadget": "0$x+7FBtXOnCa+gBCDQYYJELL4q6I",
+ "NS1::Nested::Gadget": "0$QrFH5wzXn6x40CgbEt6o8+GwAEw",
+ "NS1::Nested::Object": "0$pMIVvuQb0tyLZ5mwRFw8QDUTsPE",
+ "NS1::NestedUnsused::Gadget": "0$ksUC+FXicub96bqLdCnYlMjTIQg",
+ "NS1::NestedUnsused::Object": "0$Cu9dTusUon5OJbsbCNDszsTWsk4",
+ "NS1::Object": "0$FkizYy4XPEX9QPIyL2eE9bFYc5E",
+ "NS2::DependingNestedGadget": "0$DMnk8/Na+tVbOCW2bDplJtp+qfI",
+ "NS2::DependingNestedObject": "0$us8HSPRnoTuPP+yEIj9VoF0+wgY",
+ "NS2::DependingObject": "0$Y9OXadBw3CKbCLiiIcwarWKy4Ko",
+ "NS2::Gadget": "0$1c2uSbAFT5taj/PGDXyGdXgWhk0",
+ "NS2::Nested::Gadget": "0$1Xmv0ccjZFLq/Y68TxyTQNopa8E",
+ "NS2::Nested::Object": "0$usRIIUM8ix6bMuqFyxtLIGnTAew",
+ "NS2::NestedUnsused::Gadget": "0$V8PumQY6aJmatQ/V72bPVRUyPtM",
+ "NS2::NestedUnsused::Object": "0$HJl5p4/beto2yDugYIrnCfTIRm0",
+ "NS2::Object": "0$qx8DIVSOVAgRXwqE1JjvDXnuM44",
+ "Unsused::Gadget": "0$9DYMYjqKdVlC6OknSGIF2TCoZJo",
+ "Unsused::Object": "0$KW85gaAnMYh4a0phjBcM1vGAyKw"
+ },
"inputFile": "related-metaobjects-name-conflict.h",
"outputRevision": 69
},
@@ -3070,6 +3222,9 @@
]
}
],
+ "hashes": {
+ "SignalWithDefaultArg": "0$nxiVjK8ieRlRO/uDdq0gKtPSgTU"
+ },
"inputFile": "signal-with-default-arg.h",
"outputRevision": 69
},
@@ -3099,6 +3254,9 @@
]
}
],
+ "hashes": {
+ "KDAB": "0$grAoknUYC0BkMxt4HLH2mGR4Tu8"
+ },
"inputFile": "single-quote-digit-separator-n3781.h",
"outputRevision": 69
},
@@ -3180,6 +3338,9 @@
]
}
],
+ "hashes": {
+ "SlotsWithVoidTemplateTest": "0$/08YvkDzSSJmzKO0L6t3PWzQTpA"
+ },
"inputFile": "slots-with-void-template.h",
"outputRevision": 69
},
@@ -3198,6 +3359,9 @@
]
}
],
+ "hashes": {
+ "Task192552": "0$QGM+ZcuSQWGxY2IolngyVZy8sy4"
+ },
"inputFile": "task192552.h",
"outputRevision": 69
},
@@ -3228,6 +3392,10 @@
]
}
],
+ "hashes": {
+ "NS_A::NS_B::TestObject": "0$IMqa1PfGS0EaXXK+9Ch24g/nP2A",
+ "NS_A::NS_Main::TestMain": "0$0z3ZNVKocxpdZHzRFtoTKQimUm0"
+ },
"inputFile": "task234909.h",
"outputRevision": 69
},
@@ -3403,6 +3571,9 @@
]
}
],
+ "hashes": {
+ "TypenameWithUnsigned": "0$jwklF5QEzeV58Ui5C0aT9ZFwDVo"
+ },
"inputFile": "task240368.h",
"outputRevision": 69
},
@@ -3421,6 +3592,9 @@
]
}
],
+ "hashes": {
+ "Task87883": "0$wz5QuoSTJ+0peRmMDYqL9p8q5q4"
+ },
"inputFile": "task87883.h",
"outputRevision": 69
},
@@ -3507,6 +3681,9 @@
]
}
],
+ "hashes": {
+ "MyTechPreviewObject": "0$70fB9sh1BICrgFbeYIYRC8QX+PQ"
+ },
"inputFile": "tech-preview.h",
"outputRevision": 69
},
@@ -3591,6 +3768,9 @@
]
}
],
+ "hashes": {
+ "BBB::Foo": "0$QBU7ysZZuCx+IZ4+vUQEQ84tqu8"
+ },
"inputFile": "trigraphs.h",
"outputRevision": 69
},
diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp
index fb30d66e6ec..7001d676878 100644
--- a/tests/auto/tools/moc/tst_moc.cpp
+++ b/tests/auto/tools/moc/tst_moc.cpp
@@ -2576,6 +2576,26 @@ void tst_Moc::warnings_data()
<< QString()
<< u"standard input:2:1: error: Parse error at \"NONSENSE\""_s;
+ QTest::newRow("VIRTUAL FINAL property")
+ << "class X { \n Q_PROPERTY(int p READ p VIRTUAL FINAL) \n };"_ba << QStringList() << 1
+ << QString()
+ << u"standard input:2:1: error: Issue with property declaration p: "
+ u"The VIRTUAL cannot be combined with FINAL, as these attributes are mutually exclusive"_s;
+
+ QTest::newRow("FINAL OVERRIDE property")
+ << "class X { \n Q_PROPERTY(int p READ p FINAL OVERRIDE) \n };"_ba << QStringList() << 1
+ << QString()
+ << u"standard input:2:1: error: Issue with property declaration p: "
+ u"OVERRIDE is redundant when property is marked FINAL"_s;
+
+ QTest::newRow("VIRTUAL OVERRIDE property")
+ << "class X { \n Q_PROPERTY(int p READ p VIRTUAL OVERRIDE) \n };"_ba << QStringList()
+ << 1 << QString()
+ << u"standard input:2:1: error: Issue with property declaration p: VIRTUAL is "
+ u"redundant when overriding a property."
+ u" The OVERRIDE must only be used when actually overriding an existing property;"
+ u" using it on a new property is an error."_s;
+
#ifdef Q_OS_UNIX // Limit to Unix because the error message is platform-dependent
QTest::newRow("Q_PLUGIN_METADATA: unreadable file")
<< QByteArray("class X { \n Q_PLUGIN_METADATA(FILE \".\") \n };")
diff --git a/tests/auto/tools/mochelpers/tst_mochelpers.cpp b/tests/auto/tools/mochelpers/tst_mochelpers.cpp
index 7e5d18d160e..ae80f0c2b58 100644
--- a/tests/auto/tools/mochelpers/tst_mochelpers.cpp
+++ b/tests/auto/tools/mochelpers/tst_mochelpers.cpp
@@ -104,7 +104,7 @@ void tst_MocHelpers::classinfoDataGroup()
{
constexpr auto data = QtMocHelpers::metaObjectData<void, void>(0, dummyStringData,
QtMocHelpers::UintData{}, QtMocHelpers::UintData{},
- QtMocHelpers::UintData{}, QtMocHelpers::UintData{},
+ QtMocHelpers::UintData{}, -1, QtMocHelpers::UintData{},
QtMocHelpers::ClassInfos({{1, 2}, {3, 4}}));
checkClassInfos(data.staticData.data);
}
@@ -612,7 +612,7 @@ void tst_MocHelpers::constructorUintGroup()
constexpr auto data = QtMocHelpers::metaObjectData<void, void>(0, dummyStringData,
QtMocHelpers::UintData{}, QtMocHelpers::UintData{},
- QtMocHelpers::UintData{}, constructors);
+ QtMocHelpers::UintData{}, -1, constructors);
checkConstructors(data.staticData.data, data.relocatingData.metaTypes);
}
@@ -676,7 +676,7 @@ void tst_MocHelpers::uintArrayNoMethods()
QtMocHelpers::EnumData<E2>(7, 6, EnumIsFlag | EnumIsScoped)
.add({ { 7, E2::V0 }, { 10, E2::V1 }, }),
QtMocHelpers::EnumData<QFlags<E1>>(11, 1, EnumIsFlag).add({ { 3, E1::AnEnumValue } }),
- }, QtMocHelpers::UintData{}, QtMocHelpers::ClassInfos({{1, 2}, {3, 4}}));
+ }, -1, QtMocHelpers::UintData{}, QtMocHelpers::ClassInfos({{1, 2}, {3, 4}}));
auto &data = mo.staticData.data;
auto &metaTypes = mo.relocatingData.metaTypes;
@@ -724,6 +724,7 @@ void tst_MocHelpers::uintArray()
.add({ { 7, E2::V0 }, { 10, E2::V1 }, }),
QtMocHelpers::EnumData<QFlags<E1>>(11, 1, EnumIsFlag).add({ { 3, E1::AnEnumValue } }),
},
+ -1,
QtMocHelpers::UintData{
QtMocHelpers::ConstructorData<NoType(QObject *)>(1, QtMocConstants::AccessPublic,
{{ { QMetaType::QObjectStar, 2 } }}
diff --git a/tests/auto/wasm/CMakeLists.txt b/tests/auto/wasm/CMakeLists.txt
index ffa2b9ca98c..6fac23b8d17 100644
--- a/tests/auto/wasm/CMakeLists.txt
+++ b/tests/auto/wasm/CMakeLists.txt
@@ -3,7 +3,6 @@
add_subdirectory(fetchapi)
add_subdirectory(localfileapi)
-add_subdirectory(qwasmkeytranslator)
add_subdirectory(qwasmwindowstack)
add_subdirectory(qwasmwindowtreenode)
add_subdirectory(qwasmpromise)
diff --git a/tests/auto/wasm/qwasmkeytranslator/tst_qwasmkeytranslator.cpp b/tests/auto/wasm/qwasmkeytranslator/tst_qwasmkeytranslator.cpp
deleted file mode 100644
index a5aa6dcd43b..00000000000
--- a/tests/auto/wasm/qwasmkeytranslator/tst_qwasmkeytranslator.cpp
+++ /dev/null
@@ -1,404 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include "../../../../src/plugins/platforms/wasm/qwasmkeytranslator.h"
-
-#include "../../../../src/plugins/platforms/wasm/qwasmevent.h"
-
-#include <QTest>
-
-#include <emscripten/val.h>
-
-namespace {
-emscripten::val makeDeadKeyJsEvent(QString code, Qt::KeyboardModifiers modifiers)
-{
- auto jsEvent = emscripten::val::object();
- jsEvent.set("code", emscripten::val(code.toStdString()));
- jsEvent.set("key", emscripten::val("Dead"));
- jsEvent.set("shiftKey", emscripten::val(modifiers.testFlag(Qt::ShiftModifier)));
- jsEvent.set("ctrlKey", emscripten::val(false));
- jsEvent.set("altKey", emscripten::val(false));
- jsEvent.set("metaKey", emscripten::val(false));
-
- return jsEvent;
-}
-
-emscripten::val makeKeyJsEvent(QString code, QString key, Qt::KeyboardModifiers modifiers)
-{
- auto jsEvent = emscripten::val::object();
- jsEvent.set("code", emscripten::val(code.toStdString()));
- jsEvent.set("key", emscripten::val(key.toStdString()));
- jsEvent.set("shiftKey", emscripten::val(modifiers.testFlag(Qt::ShiftModifier)));
- jsEvent.set("ctrlKey", emscripten::val(modifiers.testFlag(Qt::ControlModifier)));
- jsEvent.set("altKey", emscripten::val(modifiers.testFlag(Qt::AltModifier)));
- jsEvent.set("metaKey", emscripten::val(modifiers.testFlag(Qt::MetaModifier)));
-
- return jsEvent;
-}
-} // unnamed namespace
-
-class tst_QWasmKeyTranslator : public QObject
-{
- Q_OBJECT
-
-public:
- tst_QWasmKeyTranslator() = default;
-
-private slots:
- void init();
-
- void modifyByDeadKey_data();
- void modifyByDeadKey();
- void deadKeyModifiesOnlyOneKeyPressAndUp();
- void deadKeyIgnoresKeyUpPrecedingKeyDown();
- void onlyKeysProducingTextAreModifiedByDeadKeys();
-};
-
-void tst_QWasmKeyTranslator::init() { }
-
-void tst_QWasmKeyTranslator::modifyByDeadKey_data()
-{
- QTest::addColumn<QString>("deadKeyCode");
- QTest::addColumn<Qt::KeyboardModifiers>("deadKeyModifiers");
- QTest::addColumn<QString>("targetKeyCode");
- QTest::addColumn<QString>("targetKey");
- QTest::addColumn<Qt::Key>("targetQtKey");
- QTest::addColumn<Qt::KeyboardModifiers>("modifiers");
- QTest::addColumn<QString>("expectedModifiedKey");
-
- QTest::newRow("à (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Agrave << Qt::KeyboardModifiers() << "à";
- QTest::newRow("À (Backquote)")
- << "Backquote" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Agrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "À";
- QTest::newRow("à (IntlBackslash)") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Agrave << Qt::KeyboardModifiers() << "à";
- QTest::newRow("À (IntlBackslash)")
- << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Agrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "À";
- QTest::newRow("á (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Aacute << Qt::KeyboardModifiers() << "á";
- QTest::newRow("Á (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Aacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Á";
- QTest::newRow("á") << "KeyE" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Aacute << Qt::KeyboardModifiers() << "á";
- QTest::newRow("Á") << "KeyE" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Aacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Á";
- QTest::newRow("ä (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers() << "ä";
- QTest::newRow("Ä (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ä";
- QTest::newRow("ä (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA"
- << "a" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers() << "ä";
- QTest::newRow("Ä (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA"
- << "A" << Qt::Key_Adiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ä";
- QTest::newRow("â") << "KeyI" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Acircumflex << Qt::KeyboardModifiers() << "â";
- QTest::newRow("Â") << "KeyI" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Acircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Â";
- QTest::newRow("â (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA"
- << "a" << Qt::Key_Acircumflex << Qt::KeyboardModifiers() << "â";
- QTest::newRow("Â (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyA"
- << "A" << Qt::Key_Acircumflex
- << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Â";
- QTest::newRow("ã") << "KeyN" << Qt::KeyboardModifiers() << "KeyA"
- << "a" << Qt::Key_Atilde << Qt::KeyboardModifiers() << "ã";
- QTest::newRow("Ã") << "KeyN" << Qt::KeyboardModifiers() << "KeyA"
- << "A" << Qt::Key_Atilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ã";
-
- QTest::newRow("è (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Egrave << Qt::KeyboardModifiers() << "è";
- QTest::newRow("È (Backquote)")
- << "Backquote" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Egrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "È";
- QTest::newRow("è") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Egrave << Qt::KeyboardModifiers() << "è";
- QTest::newRow("È") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Egrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "È";
- QTest::newRow("é") << "KeyE" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Eacute << Qt::KeyboardModifiers() << "é";
- QTest::newRow("É") << "KeyE" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Eacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "É";
- QTest::newRow("é (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Eacute << Qt::KeyboardModifiers() << "é";
- QTest::newRow("É (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Eacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "É";
- QTest::newRow("ë (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers() << "ë";
- QTest::newRow("Ë (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ë";
- QTest::newRow("ë (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE"
- << "e" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers() << "ë";
- QTest::newRow("Ë (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE"
- << "E" << Qt::Key_Ediaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ë";
- QTest::newRow("ê") << "KeyI" << Qt::KeyboardModifiers() << "KeyE"
- << "e" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers() << "ê";
- QTest::newRow("Ê") << "KeyI" << Qt::KeyboardModifiers() << "KeyE"
- << "E" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Ê";
- QTest::newRow("ê (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE"
- << "e" << Qt::Key_Ecircumflex << Qt::KeyboardModifiers() << "ê";
- QTest::newRow("Ê (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyE"
- << "E" << Qt::Key_Ecircumflex
- << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ê";
-
- QTest::newRow("ì (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Igrave << Qt::KeyboardModifiers() << "ì";
- QTest::newRow("Ì (Backquote)")
- << "Backquote" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Igrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ì";
- QTest::newRow("ì") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Igrave << Qt::KeyboardModifiers() << "ì";
- QTest::newRow("Ì") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Igrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ì";
- QTest::newRow("í") << "KeyE" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Iacute << Qt::KeyboardModifiers() << "í";
- QTest::newRow("Í") << "KeyE" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Iacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Í";
- QTest::newRow("í (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Iacute << Qt::KeyboardModifiers() << "í";
- QTest::newRow("Í (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Iacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Í";
- QTest::newRow("ï (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers() << "ï";
- QTest::newRow("Ï (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ï";
- QTest::newRow("ï (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyI"
- << "i" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers() << "ï";
- QTest::newRow("Ï (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyI"
- << "I" << Qt::Key_Idiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ï";
- QTest::newRow("î") << "KeyI" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Icircumflex << Qt::KeyboardModifiers() << "î";
- QTest::newRow("Î") << "KeyI" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Icircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Î";
- QTest::newRow("î (^ key)") << "Digit6" << Qt::KeyboardModifiers() << "KeyI"
- << "i" << Qt::Key_Icircumflex << Qt::KeyboardModifiers() << "î";
- QTest::newRow("Î (^ key)") << "Digit6" << Qt::KeyboardModifiers() << "KeyI"
- << "I" << Qt::Key_Icircumflex
- << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Î";
-
- QTest::newRow("ñ") << "KeyN" << Qt::KeyboardModifiers() << "KeyN"
- << "n" << Qt::Key_Ntilde << Qt::KeyboardModifiers() << "ñ";
- QTest::newRow("Ñ") << "KeyN" << Qt::KeyboardModifiers() << "KeyN"
- << "N" << Qt::Key_Ntilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ñ";
-
- QTest::newRow("ò (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Ograve << Qt::KeyboardModifiers() << "ò";
- QTest::newRow("Ò (Backquote)")
- << "Backquote" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Ograve << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ò";
- QTest::newRow("ò") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Ograve << Qt::KeyboardModifiers() << "ò";
- QTest::newRow("Ò") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Ograve << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ò";
- QTest::newRow("ó") << "KeyE" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Oacute << Qt::KeyboardModifiers() << "ó";
- QTest::newRow("Ó") << "KeyE" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Oacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ó";
- QTest::newRow("ó (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Oacute << Qt::KeyboardModifiers() << "ó";
- QTest::newRow("Ó (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Oacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Ó";
- QTest::newRow("ö (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers() << "ö";
- QTest::newRow("Ö (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ö";
- QTest::newRow("ö (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO"
- << "o" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers() << "ö";
- QTest::newRow("Ö (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO"
- << "O" << Qt::Key_Odiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ö";
- QTest::newRow("ô") << "KeyI" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers() << "ô";
- QTest::newRow("Ô") << "KeyI" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Ô";
- QTest::newRow("ô (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO"
- << "o" << Qt::Key_Ocircumflex << Qt::KeyboardModifiers() << "ô";
- QTest::newRow("Ô (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyO"
- << "O" << Qt::Key_Ocircumflex
- << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ô";
- QTest::newRow("õ") << "KeyN" << Qt::KeyboardModifiers() << "KeyO"
- << "o" << Qt::Key_Otilde << Qt::KeyboardModifiers() << "õ";
- QTest::newRow("Õ") << "KeyN" << Qt::KeyboardModifiers() << "KeyO"
- << "O" << Qt::Key_Otilde << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Õ";
-
- QTest::newRow("ù (Backquote)") << "Backquote" << Qt::KeyboardModifiers() << "KeyU"
- << "u" << Qt::Key_Ugrave << Qt::KeyboardModifiers() << "ù";
- QTest::newRow("Ù (Backquote)")
- << "Backquote" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Ugrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ù";
- QTest::newRow("ù") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyU"
- << "u" << Qt::Key_Ugrave << Qt::KeyboardModifiers() << "ù";
- QTest::newRow("Ù") << "IntlBackslash" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Ugrave << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ù";
- QTest::newRow("ú") << "KeyE" << Qt::KeyboardModifiers() << "KeyU"
- << "u" << Qt::Key_Uacute << Qt::KeyboardModifiers() << "ú";
- QTest::newRow("Ú") << "KeyE" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Uacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ú";
- QTest::newRow("ú (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyU"
- << "u" << Qt::Key_Uacute << Qt::KeyboardModifiers() << "ú";
- QTest::newRow("Ú (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Uacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Ú";
- QTest::newRow("ü (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyU"
- << "u" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers() << "ü";
- QTest::newRow("Ü (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ü";
- QTest::newRow("ü (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU"
- << "u" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers() << "ü";
- QTest::newRow("Ü (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU"
- << "U" << Qt::Key_Udiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ü";
- QTest::newRow("û") << "KeyI" << Qt::KeyboardModifiers() << "KeyU"
- << "û" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers() << "û";
- QTest::newRow("Û") << "KeyI" << Qt::KeyboardModifiers() << "KeyU"
- << "U" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Û";
- QTest::newRow("û (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU"
- << "û" << Qt::Key_Ucircumflex << Qt::KeyboardModifiers() << "û";
- QTest::newRow("Û (^ key)") << "Digit6" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyU"
- << "U" << Qt::Key_Ucircumflex
- << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Û";
-
- QTest::newRow("ý") << "KeyE" << Qt::KeyboardModifiers() << "KeyY"
- << "y" << Qt::Key_Yacute << Qt::KeyboardModifiers() << "ý";
- QTest::newRow("Ý") << "KeyE" << Qt::KeyboardModifiers() << "KeyY"
- << "Y" << Qt::Key_Yacute << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ý";
- QTest::newRow("ý (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyY"
- << "y" << Qt::Key_Yacute << Qt::KeyboardModifiers() << "ý";
- QTest::newRow("Ý (Quote)") << "Quote" << Qt::KeyboardModifiers() << "KeyY"
- << "Y" << Qt::Key_Yacute << Qt::KeyboardModifiers(Qt::ShiftModifier)
- << "Ý";
- QTest::newRow("ÿ (Mac Umlaut)") << "KeyU" << Qt::KeyboardModifiers() << "KeyY"
- << "y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers() << "ÿ";
- QTest::newRow("Ÿ (Mac Umlaut)")
- << "KeyU" << Qt::KeyboardModifiers() << "KeyY"
- << "Y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ÿ";
- QTest::newRow("ÿ (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyY"
- << "y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers() << "ÿ";
- QTest::newRow("Ÿ (Shift+Quote)")
- << "Quote" << Qt::KeyboardModifiers(Qt::ShiftModifier) << "KeyY"
- << "Y" << Qt::Key_ydiaeresis << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Ÿ";
-}
-
-void tst_QWasmKeyTranslator::modifyByDeadKey()
-{
- QFETCH(QString, deadKeyCode);
- QFETCH(Qt::KeyboardModifiers, deadKeyModifiers);
- QFETCH(QString, targetKeyCode);
- QFETCH(QString, targetKey);
- QFETCH(Qt::Key, targetQtKey);
- QFETCH(Qt::KeyboardModifiers, modifiers);
- QFETCH(QString, expectedModifiedKey);
-
- QWasmDeadKeySupport deadKeySupport;
-
- KeyEvent event(EventType::KeyDown, makeDeadKeyJsEvent(deadKeyCode, deadKeyModifiers), &deadKeySupport);
- QCOMPARE(event.deadKey, true);
-
- KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent(targetKeyCode, targetKey, modifiers), &deadKeySupport);
- QCOMPARE(eDown.deadKey, false);
- QCOMPARE(eDown.text, expectedModifiedKey);
- QCOMPARE(eDown.key, targetQtKey);
-
- KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent(targetKeyCode, targetKey, modifiers), &deadKeySupport);
- QCOMPARE(eUp.deadKey, false);
- QCOMPARE(eUp.text, expectedModifiedKey);
- QCOMPARE(eUp.key, targetQtKey);
-}
-
-void tst_QWasmKeyTranslator::deadKeyModifiesOnlyOneKeyPressAndUp()
-{
- QWasmDeadKeySupport deadKeySupport;
- KeyEvent event(EventType::KeyDown, makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eDown.text, "ü");
- QCOMPARE(eDown.key, Qt::Key_Udiaeresis);
-
- KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eUp.text, "ü");
- QCOMPARE(eUp.key, Qt::Key_Udiaeresis);
-
- KeyEvent eDown2(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eDown2.text, "u");
- QCOMPARE(eDown2.key, Qt::Key_U);
-
- KeyEvent eUp2(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eUp2.text, "u");
- QCOMPARE(eUp2.key, Qt::Key_U);
-}
-
-void tst_QWasmKeyTranslator::deadKeyIgnoresKeyUpPrecedingKeyDown()
-{
- QWasmDeadKeySupport deadKeySupport;
-
- KeyEvent deadKeyDownEvent(EventType::KeyDown,
- makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent deadKeyUpEvent(EventType::KeyUp, makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent otherKeyUpEvent(EventType::KeyUp,
- makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eDown.text, "ü");
- QCOMPARE(eDown.key, Qt::Key_Udiaeresis);
-
- KeyEvent yetAnotherKeyUpEvent(
- EventType::KeyUp, makeKeyJsEvent("ControlLeft", "Control", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eUp.text, "ü");
- QCOMPARE(eUp.key, Qt::Key_Udiaeresis);
-}
-
-void tst_QWasmKeyTranslator::onlyKeysProducingTextAreModifiedByDeadKeys()
-{
- QWasmDeadKeySupport deadKeySupport;
-
- KeyEvent deadKeyDownEvent(EventType::KeyDown,
- makeDeadKeyJsEvent("KeyU", Qt::KeyboardModifiers()), &deadKeySupport);
-
- KeyEvent noTextKeyDown(EventType::KeyDown,
- makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(noTextKeyDown.text, "");
- QCOMPARE(noTextKeyDown.key, Qt::Key_Alt);
-
- KeyEvent noTextKeyUp(EventType::KeyUp,
- makeKeyJsEvent("AltLeft", "Alt", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(noTextKeyDown.text, "");
- QCOMPARE(noTextKeyDown.key, Qt::Key_Alt);
-
- KeyEvent eDown(EventType::KeyDown, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eDown.text, "ü");
- QCOMPARE(eDown.key, Qt::Key_Udiaeresis);
-
- KeyEvent eUp(EventType::KeyUp, makeKeyJsEvent("KeyU", "u", Qt::KeyboardModifiers()), &deadKeySupport);
- QCOMPARE(eUp.text, "ü");
- QCOMPARE(eUp.key, Qt::Key_Udiaeresis);
-}
-
-QTEST_MAIN(tst_QWasmKeyTranslator)
-#include "tst_qwasmkeytranslator.moc"
diff --git a/tests/manual/corelib/itemmodels/qrangemodel/main.cpp b/tests/manual/corelib/itemmodels/qrangemodel/main.cpp
index 6cb87a4182a..00b7eabcfaf 100644
--- a/tests/manual/corelib/itemmodels/qrangemodel/main.cpp
+++ b/tests/manual/corelib/itemmodels/qrangemodel/main.cpp
@@ -197,6 +197,7 @@ class ModelFactory : public QObject
QList<QString> strings = {u"one"_s, u"two"_s, u"three"_s};
std::array<int, 1000000> largeArray = {};
std::array<Object *, 10000> objects = {};
+ std::unique_ptr<QTimer> updater = nullptr;
void updateAllObjects()
{
@@ -215,6 +216,22 @@ public slots:
return new QRangeModel(&largeArray);
}
+#if 0 // vector with adapter
+ QRangeModel *makeVectorWithAdapter()
+ {
+ QRangeModelAdapter adapter(std::ref(numbers));
+ QRangeModel &model = *adapter.model();
+ qDebug() << "Data from index" << adapter.index(0).data();
+ qDebug() << "Data from adapter" << adapter.data(0);
+ qDebug() << "Data from operator[]" << adapter[0];
+
+ QTimer::singleShot(5000, &model, [&adapter]{
+ adapter[0] = {};
+ });
+
+ return adapter.model();
+ }
+#endif
QRangeModel *makeStrings()
{
@@ -239,6 +256,20 @@ public slots:
{ 4, "vier"},
{ 5, "fünf"},
};
+#if 0
+ QRangeModelAdapter adapter(std::ref(data));
+ QRangeModel &model = *adapter.model();
+ qDebug() << "Tuple from index" << adapter.index(0, 1).data();
+ qDebug() << "Tuple from adapter" << adapter.data(0, 1);
+
+ qDebug() << "Tuple from operator[]" << adapter[0];
+ qDebug() << "Tuple from operator[...]" << adapter[0, 1];
+ QTimer::singleShot(5000, &model, [&adapter]{
+ adapter[0] = { 0, "null" };
+ adapter.insertRow(0, {-1, "negative"});
+ adapter[2, 1] = "two";
+ });
+#endif
return new QRangeModel(data);
}
@@ -379,12 +410,13 @@ public slots:
QRangeModel *makeTree()
{
- static TreeRow root[] = {{"Germany", "Berlin"},
- {"France", "Paris"},
- {"Austria", "Vienna"}
- };
+ TreeRow root[] = {{"Germany", "Berlin"},
+ {"France", "Paris"},
+ {"Austria", "Vienna"}
+ };
- static Tree europe{std::make_move_iterator(std::begin(root)), std::make_move_iterator(std::end(root))};
+ Tree europe{std::make_move_iterator(std::begin(root)),
+ std::make_move_iterator(std::end(root))};
TreeRow &bavaria = europe[0].addChild("Bavaria", "Munich");
bavaria.addChild("Upper Bavaria", "München");
bavaria.addChild("Lower Bavaria", "Landshut");
@@ -415,14 +447,27 @@ public slots:
europe[2].addChild("Vorarlberg", "Bregenz");
europe[2].addChild("Burgenland", "Eisenstadt");
- return new QRangeModel(std::ref(europe));
+ QRangeModelAdapter adapter(std::move(europe));
+ const QList<int> path = {1, 0};
+ QRangeModel *model = adapter.model();
+ updater.reset(new QTimer);
+ connect(updater.get(), &QTimer::timeout, model, [adapter] mutable {
+ // adapter[0] = tree_row{"Deutschland", "Berlin"};
+ for (auto row : adapter) {
+ qDebug() << row[0] << row[1];
+ }
+ adapter[{0, 0, 0}, 1] = "Munich";
+ });
+ updater->start(1000);
+
+ return model;
}
QRangeModel *makeAutoConnectedObjects()
{
QRangeModel *model = new QRangeModel(std::ref(objects));
- QTimer *updater = new QTimer(model);
- connect(updater, &QTimer::timeout, this, &ModelFactory::updateAllObjects);
+ updater.reset(new QTimer(model));
+ connect(updater.get(), &QTimer::timeout, this, &ModelFactory::updateAllObjects);
for (int i = 0; i < objects.size(); ++i)
objects[i] = new Object(i, model);
@@ -434,8 +479,8 @@ public slots:
QRangeModel *makeAutoConnectedConstObjects()
{
QRangeModel *model = new QRangeModel(&std::as_const(objects));
- QTimer *updater = new QTimer(model);
- connect(updater, &QTimer::timeout, this, &ModelFactory::updateAllObjects);
+ updater.reset(new QTimer(model));
+ connect(updater.get(), &QTimer::timeout, this, &ModelFactory::updateAllObjects);
for (int i = 0; i < objects.size(); ++i)
objects[i] = new Object(i, model);
@@ -587,12 +632,14 @@ public:
private:
void modelChanged(int index)
{
- QAbstractItemModel *oldModel = model;
+ auto oldModel = model;
+ QRangeModel *newModel = nullptr;
const QMetaObject &mo = ModelFactory::staticMetaObject;
const QMetaMethod method = mo.method(index + mo.methodOffset());
- if (method.invoke(&factory, qReturnArg(model))) {
- treeview->setModel(model);
+ if (method.invoke(&factory, qReturnArg(newModel))) {
+ model = newModel;
+ treeview->setModel(newModel);
#ifdef QUICK_UI
if (!quickWidget->rootObject())
statusBar()->showMessage(tr("Failed to load QML"));
@@ -601,15 +648,6 @@ private:
QQmlContext *rootContext = quickWidget->rootContext();
QQmlContext *UIContext = quickWidget->engine()->contextForObject(quickWidget->rootObject());
- qDebug() << "context objects" << rootContext->contextObject() << UIContext->contextObject();
- qDebug() << "context URLs" << rootContext->baseUrl() << UIContext->baseUrl();
-
- qDebug() << "name" << quickWidget->rootContext()->nameForObject(quickWidget->rootObject());
- qDebug() << "root object" << quickWidget->rootContext()->objectForName("root");
- qDebug() << "list object" << quickWidget->rootContext()->objectForName("root");
-
- qDebug() << "root object in context" << quickWidget->engine()->contextForObject(quickWidget->rootObject())->objectForName("root");
- qDebug() << "list object in context" << quickWidget->engine()->contextForObject(quickWidget->rootObject())->objectForName("list");
#endif
for (auto *action : connectionOptions->actions()) {
action->setChecked(action->data().value<QRangeModel::AutoConnectPolicy>()
@@ -688,7 +726,7 @@ private:
}
ModelFactory factory;
- QRangeModel *model;
+ QPointer<QRangeModel> model; // might be owned by an adapter
QTreeView *treeview;
#ifdef QUICK_UI
QQuickWidget *quickWidget;