diff options
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); ** ){ ** // do something with pVal ** } -** if( rc!=SQLITE_OK ){ +** if( rc!=SQLITE_DONE ){ ** // an error has occurred ** } ** </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); ** ){ ** // do something with pVal ** } -** if( rc!=SQLITE_OK ){ +** if( rc!=SQLITE_DONE ){ ** // an error has occurred ** } ** </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 ¶m 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 ¶ms); + 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 ¤t) const { @@ -3397,11 +3405,12 @@ void QIconModeViewBase::updateContentsSize() */ void QListView::currentChanged(const QModelIndex ¤t, 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 ¤t, 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 ¤t, 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 Binary files differindex 7516e4ee4f8..7516e4ee4f8 100644 --- a/src/widgets/styles/images/fusion_normalizedockup_10.png +++ b/src/widgets/styles/images/fusion_normalizedockup-10.png diff --git a/src/widgets/styles/images/fusion_normalizedockup_20.png b/src/widgets/styles/images/fusion_normalizedockup-20.png Binary files differindex 2bc9421d5ac..2bc9421d5ac 100644 --- a/src/widgets/styles/images/fusion_normalizedockup_20.png +++ b/src/widgets/styles/images/fusion_normalizedockup-20.png diff --git a/src/widgets/styles/images/fusion_normalizedockup_48.png b/src/widgets/styles/images/fusion_normalizedockup-48.png Binary files differindex 6c497abdded..6c497abdded 100644 --- a/src/widgets/styles/images/fusion_normalizedockup_48.png +++ b/src/widgets/styles/images/fusion_normalizedockup-48.png diff --git a/src/widgets/styles/images/fusion_normalizedockup_64.png b/src/widgets/styles/images/fusion_normalizedockup-64.png Binary files differindex 5ec620e5a04..5ec620e5a04 100644 --- a/src/widgets/styles/images/fusion_normalizedockup_64.png +++ b/src/widgets/styles/images/fusion_normalizedockup-64.png 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; |
