diff options
Diffstat (limited to 'src')
160 files changed, 6952 insertions, 17166 deletions
diff --git a/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch b/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch new file mode 100644 index 00000000000..2dd58d4b818 --- /dev/null +++ b/src/3rdparty/double-conversion/0001-fix-decimal_point-initialization-to-suppress-warning.patch @@ -0,0 +1,40 @@ +From c75f7f48c8a3d9c6aaaeb13a48fb3c051b46ccab Mon Sep 17 00:00:00 2001 +From: Ivan Solovev <[email protected]> +Date: Mon, 3 Nov 2025 12:33:26 +0100 +Subject: [PATCH] fix decimal_point initialization to suppress warnings in GCC + 14 + +This patch was submitted upstream as [0], but there was no new release +yet. + +[0]: https://github.com/google/double-conversion/commit/4aecc844c566d84a939fc35f4e62d58bd693f18d + +Change-Id: I97cc3103ff758f4c65b23d2f4c0fd82932e36df7 +--- + .../double-conversion/double-conversion/double-to-string.cc | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc +index 215eaa96d47..9ea3d18d5f7 100644 +--- a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc ++++ b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc +@@ -180,7 +180,7 @@ bool DoubleToStringConverter::ToShortestIeeeNumber( + return HandleSpecialValues(value, result_builder); + } + +- int decimal_point; ++ int decimal_point = 0; + bool sign; + const int kDecimalRepCapacity = kBase10MaximalLength + 1; + char decimal_rep[kDecimalRepCapacity]; +@@ -405,6 +405,7 @@ void DoubleToStringConverter::DoubleToAscii(double v, + if (mode == PRECISION && requested_digits == 0) { + vector[0] = '\0'; + *length = 0; ++ *point = 0; + return; + } + +-- +2.44.0 + diff --git a/src/3rdparty/double-conversion/REUSE.toml b/src/3rdparty/double-conversion/REUSE.toml index a7ebffce4f0..14e6e9b6706 100644 --- a/src/3rdparty/double-conversion/REUSE.toml +++ b/src/3rdparty/double-conversion/REUSE.toml @@ -1,7 +1,7 @@ version = 1 [[annotations]] -path = ["double-conversion/*"] +path = ["double-conversion/*", "0001-fix-decimal_point-initialization-to-suppress-warning.patch"] precedence = "closest" SPDX-FileCopyrightText = "Copyright 2006-2012, the V8 project authors" SPDX-License-Identifier = "BSD-3-Clause" diff --git a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc index 215eaa96d47..9ea3d18d5f7 100644 --- a/src/3rdparty/double-conversion/double-conversion/double-to-string.cc +++ b/src/3rdparty/double-conversion/double-conversion/double-to-string.cc @@ -180,7 +180,7 @@ bool DoubleToStringConverter::ToShortestIeeeNumber( return HandleSpecialValues(value, result_builder); } - int decimal_point; + int decimal_point = 0; bool sign; const int kDecimalRepCapacity = kBase10MaximalLength + 1; char decimal_rep[kDecimalRepCapacity]; @@ -405,6 +405,7 @@ void DoubleToStringConverter::DoubleToAscii(double v, if (mode == PRECISION && requested_digits == 0) { vector[0] = '\0'; *length = 0; + *point = 0; return; } diff --git a/src/3rdparty/pcre2/qt_attribution.json b/src/3rdparty/pcre2/qt_attribution.json index ef29ad7d951..0468bca1b3e 100644 --- a/src/3rdparty/pcre2/qt_attribution.json +++ b/src/3rdparty/pcre2/qt_attribution.json @@ -30,7 +30,7 @@ "Homepage": "http://www.pcre.org/", "Version": "10.47", "DownloadLocation": "https://github.com/PCRE2Project/pcre2/releases/download/pcre2-10.47/pcre2-10.47.tar.bz2", - "PURL": "pkg:github/PCRE2Project/pcre2@$<VERSION>", + "PURL": "pkg:github/PCRE2Project/pcre2@pcre2-$<VERSION>", "CPE": "cpe:2.3:a:pcre:pcre2:$<VERSION>:*:*:*:*:*:*:*", "License": "BSD 2-clause \"Simplified\" License", "LicenseId": "BSD-2-Clause", diff --git a/src/3rdparty/sqlite/qt_attribution.json b/src/3rdparty/sqlite/qt_attribution.json index e8809d2b23d..2f8bbc30a94 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.50.4", + "Version": "3.51.0", "PURL": "pkg:github/sqlite/sqlite@version-$<VERSION>", "CPE": "cpe:2.3:a:sqlite:sqlite:$<VERSION>:*:*:*:*:*:*:*", - "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3500400.zip", + "DownloadLocation": "https://www.sqlite.org/2025/sqlite-amalgamation-3510000.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 26a7a43d865..03d65b62820 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.50.4. By combining all the individual C code files into this +** version 3.51.0. 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 -** 4d8adfb30e03f9cf27f800a2c1ba3c48fb4c with changes in files: +** fb2c931ae597f8d00a37574ff67aeed3eced with changes in files: ** ** */ @@ -170,7 +170,9 @@ #define HAVE_UTIME 1 #else /* This is not VxWorks. */ -#define OS_VXWORKS 0 +#ifndef OS_VXWORKS +# define OS_VXWORKS 0 +#endif #define HAVE_FCHOWN 1 #define HAVE_READLINK 1 #define HAVE_LSTAT 1 @@ -465,9 +467,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.50.4" -#define SQLITE_VERSION_NUMBER 3050004 -#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3" +#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" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -487,9 +492,9 @@ extern "C" { ** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); ** </pre></blockquote>)^ ** -** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] -** macro. ^The sqlite3_libversion() function returns a pointer to the -** to the sqlite3_version[] string constant. The sqlite3_libversion() +** ^The sqlite3_version[] string constant contains the text of the +** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a +** pointer to the sqlite3_version[] string constant. The sqlite3_libversion() ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to @@ -689,7 +694,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, -** semicolon-separate SQL statements passed into its 2nd argument, +** semicolon-separated SQL statements passed into its 2nd argument, ** in the context of the [database connection] passed in as its 1st ** argument. ^If the callback function of the 3rd argument to ** sqlite3_exec() is not NULL, then it is invoked for each result row @@ -722,7 +727,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** result row is NULL then the corresponding string pointer for the ** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the ** sqlite3_exec() callback is an array of pointers to strings where each -** entry represents the name of corresponding result column as obtained +** entry represents the name of a corresponding result column as obtained ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer @@ -816,6 +821,9 @@ SQLITE_API int sqlite3_exec( #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) @@ -850,6 +858,8 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) @@ -908,7 +918,7 @@ SQLITE_API int sqlite3_exec( ** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into ** [sqlite3_open_v2()] does *not* cause the underlying database file ** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into -** [sqlite3_open_v2()] has historically be a no-op and might become an +** [sqlite3_open_v2()] has historically been a no-op and might become an ** error in future versions of SQLite. */ #define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ @@ -1002,7 +1012,7 @@ SQLITE_API int sqlite3_exec( ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods ** of an [sqlite3_io_methods] object. These values are ordered from -** lest restrictive to most restrictive. +** least restrictive to most restrictive. ** ** The argument to xLock() is always SHARED or higher. The argument to ** xUnlock is either SHARED or NONE. @@ -1243,7 +1253,7 @@ struct sqlite3_io_methods { ** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** ** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] -** No longer in use. +** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used. ** ** <li>[[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and @@ -1318,7 +1328,7 @@ struct sqlite3_io_methods { ** ** <li>[[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of -** all [VFSes] in the VFS stack. The names are of all VFS shims and the +** all [VFSes] in the VFS stack. The names of all VFS shims and the ** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. @@ -1332,7 +1342,7 @@ struct sqlite3_io_methods { ** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level ** [VFSes] currently in use. ^(The argument X in ** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be -** of type "[sqlite3_vfs] **". This opcodes will set *X +** of type "[sqlite3_vfs] **". This opcode will set *X ** to a pointer to the top-level VFS.)^ ** ^When there are multiple VFS shims in the stack, this opcode finds the ** upper-most shim only. @@ -1522,7 +1532,7 @@ struct sqlite3_io_methods { ** <li>[[SQLITE_FCNTL_EXTERNAL_READER]] ** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect ** whether or not there is a database client in another process with a wal-mode -** transaction open on the database or not. It is only available on unix.The +** transaction open on the database or not. It is only available on unix. The ** (void*) argument passed with this file-control should be a pointer to a ** value of type (int). The integer value is set to 1 if the database is a wal ** mode database and there exists at least one client in another process that @@ -1540,6 +1550,15 @@ struct sqlite3_io_methods { ** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control ** purges the contents of the in-memory page cache. If there is an open ** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** +** <li>[[SQLITE_FCNTL_FILESTAT]] +** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information +** about the [sqlite3_file] objects used access the database and journal files +** for the given schema. The fourth parameter to [sqlite3_file_control()] +** should be an initialized [sqlite3_str] pointer. JSON text describing +** various aspects of the sqlite3_file object is appended to the sqlite3_str. +** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time +** options are used to enable it. ** </ul> */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -1585,6 +1604,7 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_BLOCK_ON_CONNECT 44 +#define SQLITE_FCNTL_FILESTAT 45 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1947,7 +1967,7 @@ struct sqlite3_vfs { ** SQLite interfaces so that an application usually does not need to ** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] ** calls sqlite3_initialize() so the SQLite library will be automatically -** initialized when [sqlite3_open()] is called if it has not be initialized +** initialized when [sqlite3_open()] is called if it has not been initialized ** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] ** compile-time option, then the automatic calls to sqlite3_initialize() ** are omitted and the application must call sqlite3_initialize() directly @@ -2204,21 +2224,21 @@ struct sqlite3_mem_methods { ** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation -** routines with a wrapper that simulations memory allocation failure or +** routines with a wrapper that simulates memory allocation failure or ** tracks memory usage, for example. </dd> ** ** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt> -** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of ** type int, interpreted as a boolean, which if true provides a hint to ** SQLite that it should avoid large memory allocations if possible. ** SQLite will run faster if it is free to make large memory allocations, -** but some application might prefer to run slower in exchange for +** but some applications might prefer to run slower in exchange for ** guarantees about memory fragmentation that are possible if large ** allocations are avoided. This hint is normally off. ** </dd> ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> -** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: @@ -2263,7 +2283,7 @@ struct sqlite3_mem_methods { ** ^If pMem is NULL and N is non-zero, then each database connection ** does an initial bulk allocation for page cache memory ** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or -** of -1024*N bytes if N is negative, . ^If additional +** of -1024*N bytes if N is negative. ^If additional ** page cache memory is needed beyond what is provided by the initial ** allocation, then SQLite goes to [sqlite3_malloc()] separately for each ** additional cache line. </dd> @@ -2292,7 +2312,7 @@ struct sqlite3_mem_methods { ** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a ** pointer to an instance of the [sqlite3_mutex_methods] structure. ** The argument specifies alternative low-level mutex routines to be used -** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of ** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then @@ -2334,7 +2354,7 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> ** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which -** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off ** the current page cache implementation into that object.)^ </dd> ** ** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> @@ -2351,7 +2371,7 @@ struct sqlite3_mem_methods { ** the logger function is a copy of the first parameter to the corresponding ** [sqlite3_log()] call and is intended to be a [result code] or an ** [extended result code]. ^The third parameter passed to the logger is -** log message after formatting via [sqlite3_snprintf()]. +** a log message after formatting via [sqlite3_snprintf()]. ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger @@ -2542,7 +2562,7 @@ struct sqlite3_mem_methods { ** These constants are the available integer configuration options that ** can be passed as the second parameter to the [sqlite3_db_config()] interface. ** -** The [sqlite3_db_config()] interface is a var-args functions. It takes a +** The [sqlite3_db_config()] interface is a var-args function. It takes a ** variable number of parameters, though always at least two. The number of ** parameters passed into sqlite3_db_config() depends on which of these ** constants is given as the second parameter. This documentation page @@ -2654,17 +2674,20 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> -** <dd> ^This option is used to enable or disable the -** [fts3_tokenizer()] function which is part of the -** [FTS3] full-text search engine extension. -** There must be two additional arguments. -** The first argument is an integer which is 0 to disable fts3_tokenizer() or -** positive to enable fts3_tokenizer() or negative to leave the setting -** unchanged. -** The second parameter is a pointer to an integer into which -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled -** following this call. The second parameter may be a NULL pointer, in -** which case the new setting is not reported back. </dd> +** <dd> ^This option is used to enable or disable using the +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine +** extension - without using bound parameters as the parameters. Doing so +** is disabled by default. There must be two additional arguments. The first +** argument is an integer. If it is passed 0, then using fts3_tokenizer() +** without bound parameters is disabled. If it is passed a positive value, +** then calling fts3_tokenizer without bound parameters is enabled. If it +** is passed a negative value, this setting is not modified - this can be +** used to query for the current setting. The second parameter is a pointer +** to an integer into which is written 0 or 1 to indicate the current value +** of this setting (after it is modified, if applicable). The second +** parameter may be a NULL pointer, in which case the value of the setting +** is not reported back. Refer to [FTS3] documentation for further details. +** </dd> ** ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> @@ -2676,8 +2699,8 @@ struct sqlite3_mem_methods { ** When the first argument to this interface is 1, then only the C-API is ** enabled and the SQL function remains disabled. If the first argument to ** this interface is 0, then both the C-API and the SQL function are disabled. -** If the first argument is -1, then no changes are made to state of either the -** C-API or the SQL function. +** If the first argument is -1, then no changes are made to the state of either +** the C-API or the SQL function. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface ** is disabled or enabled following this call. The second parameter may @@ -2795,7 +2818,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] ** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt> ** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates -** the legacy behavior of the [ALTER TABLE RENAME] command such it +** the legacy behavior of the [ALTER TABLE RENAME] command such that it ** behaves as it did prior to [version 3.24.0] (2018-06-04). See the ** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for ** additional information. This feature can also be turned on and off @@ -2844,7 +2867,7 @@ struct sqlite3_mem_methods { ** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt> ** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates ** the legacy file format flag. When activated, this flag causes all newly -** created database file to have a schema format version number (the 4-byte +** created database files to have a schema format version number (the 4-byte ** integer found at offset 44 into the database header) of 1. This in turn ** means that the resulting database file will be readable and writable by ** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, @@ -2871,7 +2894,7 @@ struct sqlite3_mem_methods { ** the database handle both when the SQL statement is prepared and when it ** is stepped. The flag is set (collection of statistics is enabled) ** by default. <p>This option takes two arguments: an integer and a pointer to -** an integer.. The first argument is 1, 0, or -1 to enable, disable, or +** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after ** processing the first argument is written into the integer that the second @@ -2914,8 +2937,8 @@ struct sqlite3_mem_methods { ** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the ** ability of the [ATTACH DATABASE] SQL command to open a database for writing. ** This capability is enabled by default. Applications can disable or -** reenable this capability using the current DBCONFIG option. If the -** the this capability is disabled, the [ATTACH] command will still work, +** reenable this capability using the current DBCONFIG option. If +** this capability is disabled, the [ATTACH] command will still work, ** but the database will be opened read-only. If this option is disabled, ** then the ability to create a new database using [ATTACH] is also disabled, ** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE] @@ -2949,7 +2972,7 @@ struct sqlite3_mem_methods { ** ** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the ** overall call to [sqlite3_db_config()] has a total of four parameters. -** The first argument (the third parameter to sqlite3_db_config()) is a integer. +** The first argument (the third parameter to sqlite3_db_config()) is an integer. ** The second argument is a pointer to an integer. If the first argument is 1, ** then the option becomes enabled. If the first integer argument is 0, then the ** option is disabled. If the first argument is -1, then the option setting @@ -3239,7 +3262,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*); ** ^These routines return 0 if the statement is incomplete. ^If a ** memory allocation fails, then SQLITE_NOMEM is returned. ** -** ^These routines do not parse the SQL statements thus +** ^These routines do not parse the SQL statements and thus ** will not detect syntactically incorrect SQL. ** ** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior @@ -3356,7 +3379,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); ** indefinitely if possible. The results of passing any other negative value ** are undefined. ** -** Internally, each SQLite database handle store two timeout values - the +** Internally, each SQLite database handle stores two timeout values - the ** busy-timeout (used for rollback mode databases, or if the VFS does not ** support blocking locks) and the setlk-timeout (used for blocking locks ** on wal-mode databases). The sqlite3_busy_timeout() method sets both @@ -3386,7 +3409,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags); ** This is a legacy interface that is preserved for backwards compatibility. ** Use of this interface is not recommended. ** -** Definition: A <b>result table</b> is memory data structure created by the +** Definition: A <b>result table</b> is a memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. ** @@ -3529,7 +3552,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); ** ^Calling sqlite3_free() with a pointer previously returned ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so ** that it might be reused. ^The sqlite3_free() routine is -** a no-op if is called with a NULL pointer. Passing a NULL pointer +** a no-op if it is called with a NULL pointer. Passing a NULL pointer ** to sqlite3_free() is harmless. After being freed, memory ** should neither be read nor written. Even reading previously freed ** memory might result in a segmentation fault or other severe error. @@ -3547,13 +3570,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); ** sqlite3_free(X). ** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation ** of at least N bytes in size or NULL if insufficient memory is available. -** ^If M is the size of the prior allocation, then min(N,M) bytes -** of the prior allocation are copied into the beginning of buffer returned +** ^If M is the size of the prior allocation, then min(N,M) bytes of the +** prior allocation are copied into the beginning of the buffer returned ** by sqlite3_realloc(X,N) and the prior allocation is freed. ** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the ** prior allocation is not freed. ** -** ^The sqlite3_realloc64(X,N) interfaces works the same as +** ^The sqlite3_realloc64(X,N) interface works the same as ** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead ** of a 32-bit signed integer. ** @@ -3603,7 +3626,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*); ** was last reset. ^The values returned by [sqlite3_memory_used()] and ** [sqlite3_memory_highwater()] include any overhead ** added by SQLite in its implementation of [sqlite3_malloc()], -** but not overhead added by the any underlying system library +** but not overhead added by any underlying system library ** routines that [sqlite3_malloc()] may call. ** ** ^The memory high-water mark is reset to the current value of @@ -4055,7 +4078,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** there is no harm in trying.) ** ** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt> -** <dd>The database is opened [shared cache] enabled, overriding +** <dd>The database is opened with [shared cache] enabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** The [use of shared cache mode is discouraged] and hence shared cache @@ -4063,7 +4086,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** this option is a no-op. ** ** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt> -** <dd>The database is opened [shared cache] disabled, overriding +** <dd>The database is opened with [shared cache] disabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** @@ -4481,7 +4504,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); ** subsequent calls to other SQLite interface functions.)^ ** ** ^The sqlite3_errstr(E) interface returns the English-language text -** that describes the [result code] E, as UTF-8, or NULL if E is not an +** that describes the [result code] E, as UTF-8, or NULL if E is not a ** result code for which a text error message is available. ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. @@ -4489,7 +4512,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); ** ^If the most recent error references a specific token in the input ** SQL, the sqlite3_error_offset() interface returns the byte offset ** of the start of that token. ^The byte offset returned by -** sqlite3_error_offset() assumes that the input SQL is UTF8. +** sqlite3_error_offset() assumes that the input SQL is UTF-8. ** ^If the most recent error does not reference a specific token in the input ** SQL, then the sqlite3_error_offset() function returns -1. ** @@ -4515,6 +4538,34 @@ SQLITE_API const char *sqlite3_errstr(int); SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* +** CAPI3REF: Set Error Codes And Message +** METHOD: sqlite3 +** +** Set the error code of the database handle passed as the first argument +** to errcode, and the error message to a copy of nul-terminated string +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to +** the default message associated with the supplied error code. Subsequent +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will +** return the values set by this routine in place of what was previously +** set by SQLite itself. +** +** This function returns SQLITE_OK if the error code and error message are +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if +** the database handle is NULL or invalid. +** +** The error code and message set by this routine remains in effect until +** they are changed, either by another call to this routine or until they are +** changed to by SQLite itself to reflect the result of some subsquent +** API call. +** +** This function is intended for use by SQLite extensions or wrappers. The +** idea is that an extension or wrapper can use this routine to set error +** messages and error codes and thus behave more like a core SQLite +** feature from the point of view of an application. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); + +/* ** CAPI3REF: Prepared Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** @@ -4588,8 +4639,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** These constants define various performance limits ** that can be lowered at run-time using [sqlite3_limit()]. -** The synopsis of the meanings of the various limits is shown below. -** Additional information is available at [limits | Limits in SQLite]. +** A concise description of these limits follows, and additional information +** is available at [limits | Limits in SQLite]. ** ** <dl> ** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt> @@ -4654,7 +4705,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Prepare Flags ** -** These constants define various flags that can be passed into +** These constants define various flags that can be passed into the ** "prepFlags" parameter of the [sqlite3_prepare_v3()] and ** [sqlite3_prepare16_v3()] interfaces. ** @@ -4741,7 +4792,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** there is a small performance advantage to passing an nByte parameter that ** is the number of bytes in the input string <i>including</i> ** the nul-terminator. -** Note that nByte measure the length of the input in bytes, not +** Note that nByte measures the length of the input in bytes, not ** characters, even for the UTF-16 interfaces. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte @@ -4875,7 +4926,7 @@ SQLITE_API int sqlite3_prepare16_v3( ** ** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory ** is available to hold the result, or if the result would exceed the -** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** maximum string length determined by the [SQLITE_LIMIT_LENGTH]. ** ** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of ** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time @@ -5063,7 +5114,7 @@ typedef struct sqlite3_value sqlite3_value; ** ** The context in which an SQL function executes is stored in an ** sqlite3_context object. ^A pointer to an sqlite3_context object -** is always first parameter to [application-defined SQL functions]. +** is always the first parameter to [application-defined SQL functions]. ** The application-defined SQL function implementation will pass this ** pointer through into calls to [sqlite3_result_int | sqlite3_result()], ** [sqlite3_aggregate_context()], [sqlite3_user_data()], @@ -5187,9 +5238,11 @@ typedef struct sqlite3_context sqlite3_context; ** associated with the pointer P of type T. ^D is either a NULL pointer or ** a pointer to a destructor function for P. ^SQLite will invoke the ** destructor D with a single argument of P when it is finished using -** P. The T parameter should be a static string, preferably a string -** literal. The sqlite3_bind_pointer() routine is part of the -** [pointer passing interface] added for SQLite 3.20.0. +** P, even if the call to sqlite3_bind_pointer() fails. Due to a +** historical design quirk, results are undefined if D is +** SQLITE_TRANSIENT. The T parameter should be a static string, +** preferably a string literal. The sqlite3_bind_pointer() routine is +** part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which @@ -5800,7 +5853,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. ** ^If the most recent evaluation of the statement encountered no errors -** or if the statement is never been evaluated, then sqlite3_finalize() returns +** or if the statement has never been evaluated, then sqlite3_finalize() returns ** SQLITE_OK. ^If the most recent evaluation of statement S failed, then ** sqlite3_finalize(S) returns the appropriate [error code] or ** [extended error code]. @@ -6032,7 +6085,7 @@ SQLITE_API int sqlite3_create_window_function( /* ** CAPI3REF: Text Encodings ** -** These constant define integer codes that represent the various +** These constants define integer codes that represent the various ** text encodings supported by SQLite. */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ @@ -6124,7 +6177,7 @@ SQLITE_API int sqlite3_create_window_function( ** result. ** Every function that invokes [sqlite3_result_subtype()] should have this ** property. If it does not, then the call to [sqlite3_result_subtype()] -** might become a no-op if the function is used as term in an +** might become a no-op if the function is used as a term in an ** [expression index]. On the other hand, SQL functions that never invoke ** [sqlite3_result_subtype()] should avoid setting this property, as the ** purpose of this property is to disable certain optimizations that are @@ -6251,7 +6304,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation ** that the xUpdate method call was invoked to implement and if -** and the prior [xColumn] method call that was invoked to extracted +** the prior [xColumn] method call that was invoked to extract ** the value for that column returned without setting a result (probably ** because it queried [sqlite3_vtab_nochange()] and found that the column ** was unchanging). ^Within an [xUpdate] method, any value for which @@ -6524,6 +6577,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** or a NULL pointer if there were no prior calls to ** sqlite3_set_clientdata() with the same values of D and N. ** Names are compared using strcmp() and are thus case sensitive. +** It returns 0 on success and SQLITE_NOMEM on allocation failure. ** ** If P and X are both non-NULL, then the destructor X is invoked with ** argument P on the first of the following occurrences: @@ -9200,9 +9254,18 @@ SQLITE_API int sqlite3_status64( ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead +** of pointers to 32-bit integers, which allows larger status values +** to be returned. If a status value exceeds 2,147,483,647 then +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() +** will return the full value. +** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); /* ** CAPI3REF: Status Parameters for database connections @@ -9299,6 +9362,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** If an IO or other error occurs while writing a page to disk, the effect ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +** <p> +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. +** Resetting one will reduce the other.)^ ** </dd> ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> @@ -9314,6 +9381,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** <dd>This parameter returns zero for the current value if and only if ** all foreign key constraints (deferred or immediate) have been ** resolved.)^ ^The highwater mark is always 0. +** +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> +** <dd>^(This parameter returns the number of bytes written to temporary +** files on disk that could have been kept in memory had sufficient memory +** been available. This value includes writes to intermediate tables that +** are part of complex queries, external sorts that spill to disk, and +** writes to TEMP tables.)^ +** ^The highwater mark is always 0. +** <p> +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. +** Resetting one will reduce the other.)^ ** </dd> ** </dl> */ @@ -9330,7 +9409,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r #define SQLITE_DBSTATUS_DEFERRED_FKS 10 #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 #define SQLITE_DBSTATUS_CACHE_SPILL 12 -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ /* @@ -10095,7 +10175,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** is the number of pages currently in the write-ahead log file, ** including those that were just committed. ** -** The callback function should normally return [SQLITE_OK]. ^If an error +** ^The callback function should normally return [SQLITE_OK]. ^If an error ** code is returned, that error will propagate back up through the ** SQLite code base to cause the statement that provoked the callback ** to report an error, though the commit will have still occurred. If the @@ -10103,13 +10183,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^The return value is -** a copy of the third parameter from the previous call, if any, or 0. -** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will -** overwrite any prior [sqlite3_wal_hook()] settings. +** ^A single database handle may have at most a single write-ahead log +** callback registered at one time. ^Calling [sqlite3_wal_hook()] +** replaces the default behavior or previously registered write-ahead +** log callback. +** +** ^The return value is a copy of the third parameter from the +** previous call, if any, or 0. +** +** ^The [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and +** will overwrite any prior [sqlite3_wal_hook()] settings. +** +** ^If a write-ahead log callback is set using this function then +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] +** should be invoked periodically to keep the write-ahead log file +** from growing without bound. +** +** ^Passing a NULL pointer for the callback disables automatic +** checkpointing entirely. To re-enable the default behavior, call +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. */ SQLITE_API void *sqlite3_wal_hook( sqlite3*, @@ -10126,7 +10219,7 @@ SQLITE_API void *sqlite3_wal_hook( ** to automatically [checkpoint] ** after committing a transaction if there are N or ** more frames in the [write-ahead log] file. ^Passing zero or -** a negative value as the nFrame parameter disables automatic +** a negative value as the N parameter disables automatic ** checkpoints entirely. ** ** ^The callback registered by this function replaces any existing callback @@ -10142,9 +10235,10 @@ SQLITE_API void *sqlite3_wal_hook( ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] -** pages. The use of this interface -** is only necessary if the default setting is found to be suboptimal -** for a particular application. +** pages. +** +** ^The use of this interface is only necessary if the default setting +** is found to be suboptimal for a particular application. */ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); @@ -10209,6 +10303,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the ** addition that it also truncates the log file to zero bytes just prior ** to a successful return. +** +** <dt>SQLITE_CHECKPOINT_NOOP<dd> +** ^This mode always checkpoints zero frames. The only reason to invoke +** a NOOP checkpoint is to access the values returned by +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. ** </dl> ** ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in @@ -10279,6 +10378,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the ** meaning of each of these checkpoint modes. */ +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ @@ -11106,7 +11206,7 @@ typedef struct sqlite3_snapshot { ** The [sqlite3_snapshot_get()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( +SQLITE_API int sqlite3_snapshot_get( sqlite3 *db, const char *zSchema, sqlite3_snapshot **ppSnapshot @@ -11155,7 +11255,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** The [sqlite3_snapshot_open()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( +SQLITE_API int sqlite3_snapshot_open( sqlite3 *db, const char *zSchema, sqlite3_snapshot *pSnapshot @@ -11172,7 +11272,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( ** The [sqlite3_snapshot_free()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); /* ** CAPI3REF: Compare the ages of two snapshot handles. @@ -11199,7 +11299,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( +SQLITE_API int sqlite3_snapshot_cmp( sqlite3_snapshot *p1, sqlite3_snapshot *p2 ); @@ -11227,7 +11327,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Serialize a database @@ -11301,12 +11401,13 @@ SQLITE_API unsigned char *sqlite3_serialize( ** ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then -** reopen S as an in-memory database based on the serialization contained -** in P. The serialized database P is N bytes in size. M is the size of -** the buffer P, which might be larger than N. If M is larger than N, and -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is -** permitted to add content to the in-memory database as long as the total -** size does not exceed M bytes. +** reopen S as an in-memory database based on the serialization +** contained in P. If S is a NULL pointer, the main database is +** used. The serialized database P is N bytes in size. M is the size +** of the buffer P, which might be larger than N. If M is larger than +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then +** SQLite is permitted to add content to the in-memory database as +** long as the total size does not exceed M bytes. ** ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will ** invoke sqlite3_free() on the serialization buffer when the database @@ -11374,6 +11475,54 @@ SQLITE_API int sqlite3_deserialize( #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ /* +** CAPI3REF: Bind array values to the CARRAY table-valued function +** +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to +** one of the first argument of the [carray() table-valued function]. The +** S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. P is a pointer to the +** array to be bound, and N is the number of eements in the array. The +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to +** indicate the datatype of the array being bound. The X argument is not a +** NULL pointer, then SQLite will invoke the function X on the P parameter +** after it has finished using P, even if the call to +** sqlite3_carray_bind() fails. The special-case finalizer +** SQLITE_TRANSIENT has no effect here. +*/ +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*) /* Destructor for aData */ +); + +/* +** CAPI3REF: Datatypes for the CARRAY table-valued function +** +** The fifth argument to the [sqlite3_carray_bind()] interface musts be +** one of the following constants, to specify the datatype of the array +** that is being bound into the [carray table-valued function]. +*/ +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ + +/* +** Versions of the above #defines that omit the initial SQLITE_, for +** legacy compatibility. +*/ +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define CARRAY_DOUBLE 2 /* Data is doubles */ +#define CARRAY_TEXT 3 /* Data is char* */ +#define CARRAY_BLOB 4 /* Data is struct iovec */ + +/* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ @@ -12632,14 +12781,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** update the "main" database attached to handle db with the changes found in ** the changeset passed via the second and third arguments. ** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. Additionally, starting with version 3.51.0, +** an error code and error message that may be accessed using the +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database +** handle. +** ** The fourth argument (xFilter) passed to these functions is the "filter -** callback". If it is not NULL, then for each table affected by at least one -** change in the changeset, the filter callback is invoked with -** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument as the first. If the "filter callback" -** returns zero, then no attempt is made to apply any changes to the table. -** Otherwise, if the return value is non-zero or the xFilter argument to -** is NULL, all changes related to the table are attempted. +** callback". This may be passed NULL, in which case all changes in the +** changeset are applied to the database. For sqlite3changeset_apply() and +** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once +** for each table affected by at least one change in the changeset. In this +** case the table name is passed as the second argument, and a copy of +** the context pointer passed as the sixth argument to apply() or apply_v2() +** as the first. If the "filter callback" returns zero, then no attempt is +** made to apply any changes to the table. Otherwise, if the return value is +** non-zero, all changes related to the table are attempted. +** +** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once +** per change. The second argument in this case is an sqlite3_changeset_iter +** that may be queried using the usual APIs for the details of the current +** change. If the "filter callback" returns zero in this case, then no attempt +** is made to apply the current change. If it returns non-zero, the change +** is applied. ** ** For each table that is not excluded by the filter callback, this function ** tests that the target database contains a compatible table. A table is @@ -12660,11 +12827,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** one such warning is issued for each table in the changeset. ** ** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for -** each type of change is below. +** to modify the table contents according to each UPDATE, INSERT or DELETE +** change that is not excluded by a filter callback. If a change cannot be +** applied cleanly, the conflict handler function passed as the fifth argument +** to sqlite3changeset_apply() may be invoked. A description of exactly when +** the conflict handler is invoked for each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results ** of passing anything other than a valid function pointer as the xConflict @@ -12760,12 +12927,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** This can be used to further customize the application's conflict ** resolution strategy. ** -** All changes made by these functions are enclosed in a savepoint transaction. -** If any other error (aside from a constraint failure when attempting to -** write to the target database) occurs, then the savepoint transaction is -** rolled back, restoring the target database to its original state, and an -** SQLite error code returned. -** ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() ** may set (*ppRebase) to point to a "rebase" that may be used with the @@ -12815,6 +12976,23 @@ SQLITE_API int sqlite3changeset_apply_v2( void **ppRebase, int *pnRebase, /* OUT: Rebase data */ int flags /* SESSION_CHANGESETAPPLY_* flags */ ); +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 @@ -13234,6 +13412,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ); +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); SQLITE_API int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, @@ -14310,7 +14505,7 @@ struct fts5_api { ** Maximum number of pages in one database file. ** ** This is really just the default value for the max_page_count pragma. -** This value can be lowered (or raised) at run-time using that the +** This value can be lowered (or raised) at run-time using the ** max_page_count macro. */ #ifndef SQLITE_MAX_PAGE_COUNT @@ -15178,7 +15373,7 @@ SQLITE_PRIVATE void sqlite3HashClear(Hash*); ** ourselves. */ #ifndef offsetof -#define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD)) +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif /* @@ -15566,6 +15761,8 @@ SQLITE_PRIVATE u32 sqlite3TreeTrace; ** 0x00020000 Transform DISTINCT into GROUP BY ** 0x00040000 SELECT tree dump after all code has been generated ** 0x00080000 NOT NULL strength reduction +** 0x00100000 Pointers are all shown as zero +** 0x00200000 EXISTS-to-JOIN optimization */ /* @@ -15610,6 +15807,7 @@ SQLITE_PRIVATE u32 sqlite3WhereTrace; ** 0x00020000 Show WHERE terms returned from whereScanNext() ** 0x00040000 Solver overview messages ** 0x00080000 Star-query heuristic +** 0x00100000 Pointers are all shown as zero */ @@ -15682,7 +15880,7 @@ struct BusyHandler { ** pointer will work here as long as it is distinct from SQLITE_STATIC ** and SQLITE_TRANSIENT. */ -#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3OomClear) +#define SQLITE_DYNAMIC ((sqlite3_destructor_type)sqlite3RowSetClear) /* ** When SQLITE_OMIT_WSD is defined, it means that the target platform does @@ -15903,8 +16101,8 @@ typedef int VList; ** must provide its own VFS implementation together with sqlite3_os_init() ** and sqlite3_os_end() routines. */ -#if !defined(SQLITE_OS_KV) && !defined(SQLITE_OS_OTHER) && \ - !defined(SQLITE_OS_UNIX) && !defined(SQLITE_OS_WIN) +#if SQLITE_OS_KV+1<=1 && SQLITE_OS_OTHER+1<=1 && \ + SQLITE_OS_WIN+1<=1 && SQLITE_OS_UNIX+1<=1 # if defined(_WIN32) || defined(WIN32) || defined(__CYGWIN__) || \ defined(__MINGW32__) || defined(__BORLANDC__) # define SQLITE_OS_WIN 1 @@ -16750,6 +16948,7 @@ struct BtreePayload { SQLITE_PRIVATE int sqlite3BtreeInsert(BtCursor*, const BtreePayload *pPayload, int flags, int seekResult); SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor*, int *pRes); +SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes); SQLITE_PRIVATE int sqlite3BtreeLast(BtCursor*, int *pRes); SQLITE_PRIVATE int sqlite3BtreeNext(BtCursor*, int flags); SQLITE_PRIVATE int sqlite3BtreeEof(BtCursor*); @@ -17083,20 +17282,20 @@ typedef struct VdbeOpList VdbeOpList; #define OP_SorterSort 34 /* jump */ #define OP_Sort 35 /* jump */ #define OP_Rewind 36 /* jump0 */ -#define OP_SorterNext 37 /* jump */ -#define OP_Prev 38 /* jump */ -#define OP_Next 39 /* jump */ -#define OP_IdxLE 40 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxGT 41 /* jump, synopsis: key=r[P3@P4] */ -#define OP_IdxLT 42 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IfEmpty 37 /* jump, synopsis: if( empty(P1) ) goto P2 */ +#define OP_SorterNext 38 /* jump */ +#define OP_Prev 39 /* jump */ +#define OP_Next 40 /* jump */ +#define OP_IdxLE 41 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGT 42 /* jump, synopsis: key=r[P3@P4] */ #define OP_Or 43 /* same as TK_OR, synopsis: r[P3]=(r[P1] || r[P2]) */ #define OP_And 44 /* same as TK_AND, synopsis: r[P3]=(r[P1] && r[P2]) */ -#define OP_IdxGE 45 /* jump, synopsis: key=r[P3@P4] */ -#define OP_RowSetRead 46 /* jump, synopsis: r[P3]=rowset(P1) */ -#define OP_RowSetTest 47 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ -#define OP_Program 48 /* jump0 */ -#define OP_FkIfZero 49 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ -#define OP_IfPos 50 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IdxLT 45 /* jump, synopsis: key=r[P3@P4] */ +#define OP_IdxGE 46 /* jump, synopsis: key=r[P3@P4] */ +#define OP_RowSetRead 47 /* jump, synopsis: r[P3]=rowset(P1) */ +#define OP_RowSetTest 48 /* jump, synopsis: if r[P3] in rowset(P1) goto P2 */ +#define OP_Program 49 /* jump0 */ +#define OP_FkIfZero 50 /* jump, synopsis: if fkctr[P1]==0 goto P2 */ #define OP_IsNull 51 /* jump, same as TK_ISNULL, synopsis: if r[P1]==NULL goto P2 */ #define OP_NotNull 52 /* jump, same as TK_NOTNULL, synopsis: if r[P1]!=NULL goto P2 */ #define OP_Ne 53 /* jump, same as TK_NE, synopsis: IF r[P3]!=r[P1] */ @@ -17106,49 +17305,49 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Lt 57 /* jump, same as TK_LT, synopsis: IF r[P3]<r[P1] */ #define OP_Ge 58 /* jump, same as TK_GE, synopsis: IF r[P3]>=r[P1] */ #define OP_ElseEq 59 /* jump, same as TK_ESCAPE */ -#define OP_IfNotZero 60 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ -#define OP_DecrJumpZero 61 /* jump, synopsis: if (--r[P1])==0 goto P2 */ -#define OP_IncrVacuum 62 /* jump */ -#define OP_VNext 63 /* jump */ -#define OP_Filter 64 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */ -#define OP_PureFunc 65 /* synopsis: r[P3]=func(r[P2@NP]) */ -#define OP_Function 66 /* synopsis: r[P3]=func(r[P2@NP]) */ -#define OP_Return 67 -#define OP_EndCoroutine 68 -#define OP_HaltIfNull 69 /* synopsis: if r[P3]=null halt */ -#define OP_Halt 70 -#define OP_Integer 71 /* synopsis: r[P2]=P1 */ -#define OP_Int64 72 /* synopsis: r[P2]=P4 */ -#define OP_String 73 /* synopsis: r[P2]='P4' (len=P1) */ -#define OP_BeginSubrtn 74 /* synopsis: r[P2]=NULL */ -#define OP_Null 75 /* synopsis: r[P2..P3]=NULL */ -#define OP_SoftNull 76 /* synopsis: r[P1]=NULL */ -#define OP_Blob 77 /* synopsis: r[P2]=P4 (len=P1) */ -#define OP_Variable 78 /* synopsis: r[P2]=parameter(P1) */ -#define OP_Move 79 /* synopsis: r[P2@P3]=r[P1@P3] */ -#define OP_Copy 80 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ -#define OP_SCopy 81 /* synopsis: r[P2]=r[P1] */ -#define OP_IntCopy 82 /* synopsis: r[P2]=r[P1] */ -#define OP_FkCheck 83 -#define OP_ResultRow 84 /* synopsis: output=r[P1@P2] */ -#define OP_CollSeq 85 -#define OP_AddImm 86 /* synopsis: r[P1]=r[P1]+P2 */ -#define OP_RealAffinity 87 -#define OP_Cast 88 /* synopsis: affinity(r[P1]) */ -#define OP_Permutation 89 -#define OP_Compare 90 /* synopsis: r[P1@P3] <-> r[P2@P3] */ -#define OP_IsTrue 91 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ -#define OP_ZeroOrNull 92 /* synopsis: r[P2] = 0 OR NULL */ -#define OP_Offset 93 /* synopsis: r[P3] = sqlite_offset(P1) */ -#define OP_Column 94 /* synopsis: r[P3]=PX cursor P1 column P2 */ -#define OP_TypeCheck 95 /* synopsis: typecheck(r[P1@P2]) */ -#define OP_Affinity 96 /* synopsis: affinity(r[P1@P2]) */ -#define OP_MakeRecord 97 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ -#define OP_Count 98 /* synopsis: r[P2]=count() */ -#define OP_ReadCookie 99 -#define OP_SetCookie 100 -#define OP_ReopenIdx 101 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenRead 102 /* synopsis: root=P2 iDb=P3 */ +#define OP_IfPos 60 /* jump, synopsis: if r[P1]>0 then r[P1]-=P3, goto P2 */ +#define OP_IfNotZero 61 /* jump, synopsis: if r[P1]!=0 then r[P1]--, goto P2 */ +#define OP_DecrJumpZero 62 /* jump, synopsis: if (--r[P1])==0 goto P2 */ +#define OP_IncrVacuum 63 /* jump */ +#define OP_VNext 64 /* jump */ +#define OP_Filter 65 /* jump, synopsis: if key(P3@P4) not in filter(P1) goto P2 */ +#define OP_PureFunc 66 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Function 67 /* synopsis: r[P3]=func(r[P2@NP]) */ +#define OP_Return 68 +#define OP_EndCoroutine 69 +#define OP_HaltIfNull 70 /* synopsis: if r[P3]=null halt */ +#define OP_Halt 71 +#define OP_Integer 72 /* synopsis: r[P2]=P1 */ +#define OP_Int64 73 /* synopsis: r[P2]=P4 */ +#define OP_String 74 /* synopsis: r[P2]='P4' (len=P1) */ +#define OP_BeginSubrtn 75 /* synopsis: r[P2]=NULL */ +#define OP_Null 76 /* synopsis: r[P2..P3]=NULL */ +#define OP_SoftNull 77 /* synopsis: r[P1]=NULL */ +#define OP_Blob 78 /* synopsis: r[P2]=P4 (len=P1) */ +#define OP_Variable 79 /* synopsis: r[P2]=parameter(P1) */ +#define OP_Move 80 /* synopsis: r[P2@P3]=r[P1@P3] */ +#define OP_Copy 81 /* synopsis: r[P2@P3+1]=r[P1@P3+1] */ +#define OP_SCopy 82 /* synopsis: r[P2]=r[P1] */ +#define OP_IntCopy 83 /* synopsis: r[P2]=r[P1] */ +#define OP_FkCheck 84 +#define OP_ResultRow 85 /* synopsis: output=r[P1@P2] */ +#define OP_CollSeq 86 +#define OP_AddImm 87 /* synopsis: r[P1]=r[P1]+P2 */ +#define OP_RealAffinity 88 +#define OP_Cast 89 /* synopsis: affinity(r[P1]) */ +#define OP_Permutation 90 +#define OP_Compare 91 /* synopsis: r[P1@P3] <-> r[P2@P3] */ +#define OP_IsTrue 92 /* synopsis: r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4 */ +#define OP_ZeroOrNull 93 /* synopsis: r[P2] = 0 OR NULL */ +#define OP_Offset 94 /* synopsis: r[P3] = sqlite_offset(P1) */ +#define OP_Column 95 /* synopsis: r[P3]=PX cursor P1 column P2 */ +#define OP_TypeCheck 96 /* synopsis: typecheck(r[P1@P2]) */ +#define OP_Affinity 97 /* synopsis: affinity(r[P1@P2]) */ +#define OP_MakeRecord 98 /* synopsis: r[P3]=mkrec(r[P1@P2]) */ +#define OP_Count 99 /* synopsis: r[P2]=count() */ +#define OP_ReadCookie 100 +#define OP_SetCookie 101 +#define OP_ReopenIdx 102 /* synopsis: root=P2 iDb=P3 */ #define OP_BitAnd 103 /* same as TK_BITAND, synopsis: r[P3]=r[P1]&r[P2] */ #define OP_BitOr 104 /* same as TK_BITOR, synopsis: r[P3]=r[P1]|r[P2] */ #define OP_ShiftLeft 105 /* same as TK_LSHIFT, synopsis: r[P3]=r[P2]<<r[P1] */ @@ -17159,83 +17358,84 @@ typedef struct VdbeOpList VdbeOpList; #define OP_Divide 110 /* same as TK_SLASH, synopsis: r[P3]=r[P2]/r[P1] */ #define OP_Remainder 111 /* same as TK_REM, synopsis: r[P3]=r[P2]%r[P1] */ #define OP_Concat 112 /* same as TK_CONCAT, synopsis: r[P3]=r[P2]+r[P1] */ -#define OP_OpenWrite 113 /* synopsis: root=P2 iDb=P3 */ -#define OP_OpenDup 114 +#define OP_OpenRead 113 /* synopsis: root=P2 iDb=P3 */ +#define OP_OpenWrite 114 /* synopsis: root=P2 iDb=P3 */ #define OP_BitNot 115 /* same as TK_BITNOT, synopsis: r[P2]= ~r[P1] */ -#define OP_OpenAutoindex 116 /* synopsis: nColumn=P2 */ -#define OP_OpenEphemeral 117 /* synopsis: nColumn=P2 */ +#define OP_OpenDup 116 +#define OP_OpenAutoindex 117 /* synopsis: nColumn=P2 */ #define OP_String8 118 /* same as TK_STRING, synopsis: r[P2]='P4' */ -#define OP_SorterOpen 119 -#define OP_SequenceTest 120 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ -#define OP_OpenPseudo 121 /* synopsis: P3 columns in r[P2] */ -#define OP_Close 122 -#define OP_ColumnsUsed 123 -#define OP_SeekScan 124 /* synopsis: Scan-ahead up to P1 rows */ -#define OP_SeekHit 125 /* synopsis: set P2<=seekHit<=P3 */ -#define OP_Sequence 126 /* synopsis: r[P2]=cursor[P1].ctr++ */ -#define OP_NewRowid 127 /* synopsis: r[P2]=rowid */ -#define OP_Insert 128 /* synopsis: intkey=r[P3] data=r[P2] */ -#define OP_RowCell 129 -#define OP_Delete 130 -#define OP_ResetCount 131 -#define OP_SorterCompare 132 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ -#define OP_SorterData 133 /* synopsis: r[P2]=data */ -#define OP_RowData 134 /* synopsis: r[P2]=data */ -#define OP_Rowid 135 /* synopsis: r[P2]=PX rowid of P1 */ -#define OP_NullRow 136 -#define OP_SeekEnd 137 -#define OP_IdxInsert 138 /* synopsis: key=r[P2] */ -#define OP_SorterInsert 139 /* synopsis: key=r[P2] */ -#define OP_IdxDelete 140 /* synopsis: key=r[P2@P3] */ -#define OP_DeferredSeek 141 /* synopsis: Move P3 to P1.rowid if needed */ -#define OP_IdxRowid 142 /* synopsis: r[P2]=rowid */ -#define OP_FinishSeek 143 -#define OP_Destroy 144 -#define OP_Clear 145 -#define OP_ResetSorter 146 -#define OP_CreateBtree 147 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ -#define OP_SqlExec 148 -#define OP_ParseSchema 149 -#define OP_LoadAnalysis 150 -#define OP_DropTable 151 -#define OP_DropIndex 152 -#define OP_DropTrigger 153 +#define OP_OpenEphemeral 119 /* synopsis: nColumn=P2 */ +#define OP_SorterOpen 120 +#define OP_SequenceTest 121 /* synopsis: if( cursor[P1].ctr++ ) pc = P2 */ +#define OP_OpenPseudo 122 /* synopsis: P3 columns in r[P2] */ +#define OP_Close 123 +#define OP_ColumnsUsed 124 +#define OP_SeekScan 125 /* synopsis: Scan-ahead up to P1 rows */ +#define OP_SeekHit 126 /* synopsis: set P2<=seekHit<=P3 */ +#define OP_Sequence 127 /* synopsis: r[P2]=cursor[P1].ctr++ */ +#define OP_NewRowid 128 /* synopsis: r[P2]=rowid */ +#define OP_Insert 129 /* synopsis: intkey=r[P3] data=r[P2] */ +#define OP_RowCell 130 +#define OP_Delete 131 +#define OP_ResetCount 132 +#define OP_SorterCompare 133 /* synopsis: if key(P1)!=trim(r[P3],P4) goto P2 */ +#define OP_SorterData 134 /* synopsis: r[P2]=data */ +#define OP_RowData 135 /* synopsis: r[P2]=data */ +#define OP_Rowid 136 /* synopsis: r[P2]=PX rowid of P1 */ +#define OP_NullRow 137 +#define OP_SeekEnd 138 +#define OP_IdxInsert 139 /* synopsis: key=r[P2] */ +#define OP_SorterInsert 140 /* synopsis: key=r[P2] */ +#define OP_IdxDelete 141 /* synopsis: key=r[P2@P3] */ +#define OP_DeferredSeek 142 /* synopsis: Move P3 to P1.rowid if needed */ +#define OP_IdxRowid 143 /* synopsis: r[P2]=rowid */ +#define OP_FinishSeek 144 +#define OP_Destroy 145 +#define OP_Clear 146 +#define OP_ResetSorter 147 +#define OP_CreateBtree 148 /* synopsis: r[P2]=root iDb=P1 flags=P3 */ +#define OP_SqlExec 149 +#define OP_ParseSchema 150 +#define OP_LoadAnalysis 151 +#define OP_DropTable 152 +#define OP_DropIndex 153 #define OP_Real 154 /* same as TK_FLOAT, synopsis: r[P2]=P4 */ -#define OP_IntegrityCk 155 -#define OP_RowSetAdd 156 /* synopsis: rowset(P1)=r[P2] */ -#define OP_Param 157 -#define OP_FkCounter 158 /* synopsis: fkctr[P1]+=P2 */ -#define OP_MemMax 159 /* synopsis: r[P1]=max(r[P1],r[P2]) */ -#define OP_OffsetLimit 160 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ -#define OP_AggInverse 161 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ -#define OP_AggStep 162 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggStep1 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */ -#define OP_AggValue 164 /* synopsis: r[P3]=value N=P2 */ -#define OP_AggFinal 165 /* synopsis: accum=r[P1] N=P2 */ -#define OP_Expire 166 -#define OP_CursorLock 167 -#define OP_CursorUnlock 168 -#define OP_TableLock 169 /* synopsis: iDb=P1 root=P2 write=P3 */ -#define OP_VBegin 170 -#define OP_VCreate 171 -#define OP_VDestroy 172 -#define OP_VOpen 173 -#define OP_VCheck 174 -#define OP_VInitIn 175 /* synopsis: r[P2]=ValueList(P1,P3) */ -#define OP_VColumn 176 /* synopsis: r[P3]=vcolumn(P2) */ -#define OP_VRename 177 -#define OP_Pagecount 178 -#define OP_MaxPgcnt 179 -#define OP_ClrSubtype 180 /* synopsis: r[P1].subtype = 0 */ -#define OP_GetSubtype 181 /* synopsis: r[P2] = r[P1].subtype */ -#define OP_SetSubtype 182 /* synopsis: r[P2].subtype = r[P1] */ -#define OP_FilterAdd 183 /* synopsis: filter(P1) += key(P3@P4) */ -#define OP_Trace 184 -#define OP_CursorHint 185 -#define OP_ReleaseReg 186 /* synopsis: release r[P1@P2] mask P3 */ -#define OP_Noop 187 -#define OP_Explain 188 -#define OP_Abortable 189 +#define OP_DropTrigger 155 +#define OP_IntegrityCk 156 +#define OP_RowSetAdd 157 /* synopsis: rowset(P1)=r[P2] */ +#define OP_Param 158 +#define OP_FkCounter 159 /* synopsis: fkctr[P1]+=P2 */ +#define OP_MemMax 160 /* synopsis: r[P1]=max(r[P1],r[P2]) */ +#define OP_OffsetLimit 161 /* synopsis: if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1) */ +#define OP_AggInverse 162 /* synopsis: accum=r[P3] inverse(r[P2@P5]) */ +#define OP_AggStep 163 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggStep1 164 /* synopsis: accum=r[P3] step(r[P2@P5]) */ +#define OP_AggValue 165 /* synopsis: r[P3]=value N=P2 */ +#define OP_AggFinal 166 /* synopsis: accum=r[P1] N=P2 */ +#define OP_Expire 167 +#define OP_CursorLock 168 +#define OP_CursorUnlock 169 +#define OP_TableLock 170 /* synopsis: iDb=P1 root=P2 write=P3 */ +#define OP_VBegin 171 +#define OP_VCreate 172 +#define OP_VDestroy 173 +#define OP_VOpen 174 +#define OP_VCheck 175 +#define OP_VInitIn 176 /* synopsis: r[P2]=ValueList(P1,P3) */ +#define OP_VColumn 177 /* synopsis: r[P3]=vcolumn(P2) */ +#define OP_VRename 178 +#define OP_Pagecount 179 +#define OP_MaxPgcnt 180 +#define OP_ClrSubtype 181 /* synopsis: r[P1].subtype = 0 */ +#define OP_GetSubtype 182 /* synopsis: r[P2] = r[P1].subtype */ +#define OP_SetSubtype 183 /* synopsis: r[P2].subtype = r[P1] */ +#define OP_FilterAdd 184 /* synopsis: filter(P1) += key(P3@P4) */ +#define OP_Trace 185 +#define OP_CursorHint 186 +#define OP_ReleaseReg 187 /* synopsis: release r[P1@P2] mask P3 */ +#define OP_Noop 188 +#define OP_Explain 189 +#define OP_Abortable 190 /* Properties such as "out2" or "jump" that are specified in ** comments following the "case" for each opcode in the vdbe.c @@ -17254,26 +17454,26 @@ typedef struct VdbeOpList VdbeOpList; /* 8 */ 0x81, 0x01, 0x01, 0x81, 0x83, 0x83, 0x01, 0x01,\ /* 16 */ 0x03, 0x03, 0x01, 0x12, 0x01, 0xc9, 0xc9, 0xc9,\ /* 24 */ 0xc9, 0x01, 0x49, 0x49, 0x49, 0x49, 0xc9, 0x49,\ -/* 32 */ 0xc1, 0x01, 0x41, 0x41, 0xc1, 0x01, 0x41, 0x41,\ -/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x23, 0x0b,\ -/* 48 */ 0x81, 0x01, 0x03, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\ -/* 56 */ 0x0b, 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x01, 0x41,\ -/* 64 */ 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00, 0x10,\ -/* 72 */ 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10, 0x00,\ -/* 80 */ 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02, 0x02,\ -/* 88 */ 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x40, 0x00,\ -/* 96 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x40, 0x40, 0x26,\ +/* 32 */ 0xc1, 0x01, 0x41, 0x41, 0xc1, 0x01, 0x01, 0x41,\ +/* 40 */ 0x41, 0x41, 0x41, 0x26, 0x26, 0x41, 0x41, 0x23,\ +/* 48 */ 0x0b, 0x81, 0x01, 0x03, 0x03, 0x0b, 0x0b, 0x0b,\ +/* 56 */ 0x0b, 0x0b, 0x0b, 0x01, 0x03, 0x03, 0x03, 0x01,\ +/* 64 */ 0x41, 0x01, 0x00, 0x00, 0x02, 0x02, 0x08, 0x00,\ +/* 72 */ 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 0x10, 0x10,\ +/* 80 */ 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x00, 0x02,\ +/* 88 */ 0x02, 0x02, 0x00, 0x00, 0x12, 0x1e, 0x20, 0x40,\ +/* 96 */ 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x40, 0x26,\ /* 104 */ 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,\ -/* 112 */ 0x26, 0x00, 0x40, 0x12, 0x40, 0x40, 0x10, 0x00,\ -/* 120 */ 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10, 0x10,\ -/* 128 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x50,\ -/* 136 */ 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50, 0x40,\ -/* 144 */ 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,\ -/* 152 */ 0x00, 0x00, 0x10, 0x00, 0x06, 0x10, 0x00, 0x04,\ -/* 160 */ 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ -/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10, 0x50,\ -/* 176 */ 0x40, 0x00, 0x10, 0x10, 0x02, 0x12, 0x12, 0x00,\ -/* 184 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,} +/* 112 */ 0x26, 0x40, 0x00, 0x12, 0x40, 0x40, 0x10, 0x40,\ +/* 120 */ 0x00, 0x00, 0x00, 0x40, 0x00, 0x40, 0x40, 0x10,\ +/* 128 */ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,\ +/* 136 */ 0x50, 0x00, 0x40, 0x04, 0x04, 0x00, 0x40, 0x50,\ +/* 144 */ 0x40, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,\ +/* 152 */ 0x00, 0x00, 0x10, 0x00, 0x00, 0x06, 0x10, 0x00,\ +/* 160 */ 0x04, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\ +/* 168 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x10,\ +/* 176 */ 0x50, 0x40, 0x00, 0x10, 0x10, 0x02, 0x12, 0x12,\ +/* 184 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,} /* The resolve3P2Values() routine is able to run faster if it knows ** the value of the largest JUMP opcode. The smaller the maximum @@ -17281,7 +17481,7 @@ typedef struct VdbeOpList VdbeOpList; ** generated this include file strives to group all JUMP opcodes ** together near the beginning of the list. */ -#define SQLITE_MX_JUMP_OPCODE 64 /* Maximum JUMP opcode */ +#define SQLITE_MX_JUMP_OPCODE 65 /* Maximum JUMP opcode */ /************** End of opcodes.h *********************************************/ /************** Continuing where we left off in vdbe.h ***********************/ @@ -17404,8 +17604,11 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql(Vdbe*, const char*); #endif SQLITE_PRIVATE int sqlite3MemCompare(const Mem*, const Mem*, const CollSeq*); SQLITE_PRIVATE int sqlite3BlobCompare(const Mem*, const Mem*); +#ifdef SQLITE_ENABLE_PERCENTILE +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context*); +#endif -SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(KeyInfo*,int,const void*,UnpackedRecord*); +SQLITE_PRIVATE void sqlite3VdbeRecordUnpack(int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompare(int,const void*,UnpackedRecord*); SQLITE_PRIVATE int sqlite3VdbeRecordCompareWithSkip(int, const void *, UnpackedRecord *, int); SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord(KeyInfo*); @@ -17418,7 +17621,9 @@ SQLITE_PRIVATE int sqlite3VdbeHasSubProgram(Vdbe*); SQLITE_PRIVATE void sqlite3MemSetArrayInt64(sqlite3_value *aMem, int iIdx, i64 val); +#ifndef SQLITE_OMIT_DATETIME_FUNCS SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context*); +#endif #ifdef SQLITE_ENABLE_BYTECODE_VTAB SQLITE_PRIVATE int sqlite3VdbeBytecodeVtabInit(sqlite3*); #endif @@ -18074,7 +18279,7 @@ struct sqlite3 { u8 iDb; /* Which db file is being initialized */ u8 busy; /* TRUE if currently initializing */ unsigned orphanTrigger : 1; /* Last statement is orphaned TEMP trigger */ - unsigned imposterTable : 1; /* Building an imposter table */ + unsigned imposterTable : 2; /* Building an imposter table */ unsigned reopenMemdb : 1; /* ATTACH is really a reopen using MemDB */ const char **azInit; /* "type", "name", and "tbl_name" columns */ } init; @@ -18157,6 +18362,7 @@ struct sqlite3 { i64 nDeferredImmCons; /* Net deferred immediate constraints */ int *pnBytesFreed; /* If not NULL, increment this in DbFree() */ DbClientData *pDbData; /* sqlite3_set_clientdata() content */ + u64 nSpill; /* TEMP content spilled to disk */ #ifdef SQLITE_ENABLE_UNLOCK_NOTIFY /* The following variables are all protected by the STATIC_MAIN ** mutex, not by sqlite3.mutex. They are used by code in notify.c. @@ -18300,6 +18506,7 @@ struct sqlite3 { #define SQLITE_OnePass 0x08000000 /* Single-pass DELETE and UPDATE */ #define SQLITE_OrderBySubq 0x10000000 /* ORDER BY in subquery helps outer */ #define SQLITE_StarQuery 0x20000000 /* Heurists for star queries */ +#define SQLITE_ExistsToJoin 0x40000000 /* The EXISTS-to-JOIN optimization */ #define SQLITE_AllOpts 0xffffffff /* All optimizations */ /* @@ -18538,7 +18745,7 @@ struct FuncDestructor { #define STR_FUNCTION(zName, nArg, pArg, bNC, xFunc) \ {nArg, SQLITE_FUNC_BUILTIN|\ SQLITE_FUNC_SLOCHNG|SQLITE_UTF8|(bNC*SQLITE_FUNC_NEEDCOLL), \ - pArg, 0, xFunc, 0, 0, 0, #zName, } + pArg, 0, xFunc, 0, 0, 0, #zName, {0} } #define LIKEFUNC(zName, nArg, arg, flags) \ {nArg, SQLITE_FUNC_BUILTIN|SQLITE_FUNC_CONSTANT|SQLITE_UTF8|flags, \ (void *)arg, 0, likeFunc, 0, 0, 0, #zName, {0} } @@ -18866,6 +19073,7 @@ struct Table { #define TF_Ephemeral 0x00004000 /* An ephemeral table */ #define TF_Eponymous 0x00008000 /* An eponymous virtual table */ #define TF_Strict 0x00010000 /* STRICT mode */ +#define TF_Imposter 0x00020000 /* An imposter table */ /* ** Allowed values for Table.eTabType @@ -19021,9 +19229,15 @@ struct FKey { ** argument to sqlite3VdbeKeyCompare and is used to control the ** comparison of the two index keys. ** -** Note that aSortOrder[] and aColl[] have nField+1 slots. There -** are nField slots for the columns of an index then one extra slot -** for the rowid at the end. +** The aSortOrder[] and aColl[] arrays have nAllField slots each. There +** are nKeyField slots for the columns of an index then extra slots +** for the rowid or key at the end. The aSortOrder array is located after +** the aColl[] array. +** +** If SQLITE_ENABLE_PREUPDATE_HOOK is defined, then aSortFlags might be NULL +** to indicate that this object is for use by a preupdate hook. When aSortFlags +** is NULL, then nAllField is uninitialized and no space is allocated for +** aColl[], so those fields may not be used. */ struct KeyInfo { u32 nRef; /* Number of references to this KeyInfo object */ @@ -19035,9 +19249,18 @@ struct KeyInfo { CollSeq *aColl[FLEXARRAY]; /* Collating sequence for each term of the key */ }; -/* The size (in bytes) of a KeyInfo object with up to N fields */ +/* The size (in bytes) of a KeyInfo object with up to N fields. This includes +** the main body of the KeyInfo object and the aColl[] array of N elements, +** but does not count the memory used to hold aSortFlags[]. */ #define SZ_KEYINFO(N) (offsetof(KeyInfo,aColl) + (N)*sizeof(CollSeq*)) +/* The size of a bare KeyInfo with no aColl[] entries */ +#if FLEXARRAY+1 > 1 +# define SZ_KEYINFO_0 offsetof(KeyInfo,aColl) +#else +# define SZ_KEYINFO_0 sizeof(KeyInfo) +#endif + /* ** Allowed bit values for entries in the KeyInfo.aSortFlags[] array. */ @@ -19056,9 +19279,8 @@ struct KeyInfo { ** ** An instance of this object serves as a "key" for doing a search on ** an index b+tree. The goal of the search is to find the entry that -** is closed to the key described by this object. This object might hold -** just a prefix of the key. The number of fields is given by -** pKeyInfo->nField. +** is closest to the key described by this object. This object might hold +** just a prefix of the key. The number of fields is given by nField. ** ** The r1 and r2 fields are the values to return if this key is less than ** or greater than a key in the btree, respectively. These are normally @@ -19068,7 +19290,7 @@ struct KeyInfo { ** The key comparison functions actually return default_rc when they find ** an equals comparison. default_rc can be -1, 0, or +1. If there are ** multiple entries in the b-tree with the same key (when only looking -** at the first pKeyInfo->nFields,) then default_rc can be set to -1 to +** at the first nField elements) then default_rc can be set to -1 to ** cause the search to find the last match, or +1 to cause the search to ** find the first match. ** @@ -19080,8 +19302,8 @@ struct KeyInfo { ** b-tree. */ struct UnpackedRecord { - KeyInfo *pKeyInfo; /* Collation and sort-order information */ - Mem *aMem; /* Values */ + KeyInfo *pKeyInfo; /* Comparison info for the index that is unpacked */ + Mem *aMem; /* Values for columns of the index */ union { char *z; /* Cache of aMem[0].z for vdbeRecordCompareString() */ i64 i; /* Cache of aMem[0].u.i for vdbeRecordCompareInt() */ @@ -19730,6 +19952,7 @@ struct SrcItem { unsigned rowidUsed :1; /* The ROWID of this table is referenced */ unsigned fixedSchema :1; /* Uses u4.pSchema, not u4.zDatabase */ unsigned hadSchema :1; /* Had u4.zDatabase before u4.pSchema */ + unsigned fromExists :1; /* Comes from WHERE EXISTS(...) */ } fg; int iCursor; /* The VDBE cursor number used to access this table */ Bitmask colUsed; /* Bit N set if column N used. Details above for N>62 */ @@ -20017,6 +20240,7 @@ struct Select { #define SF_OrderByReqd 0x8000000 /* The ORDER BY clause may not be omitted */ #define SF_UpdateFrom 0x10000000 /* Query originates with UPDATE FROM */ #define SF_Correlated 0x20000000 /* True if references the outer context */ +#define SF_OnToWhere 0x40000000 /* One or more ON clauses moved to WHERE */ /* True if SrcItem X is a subquery that has SF_NestedFrom */ #define IsNestedFrom(X) \ @@ -20260,6 +20484,7 @@ struct Parse { u8 disableLookaside; /* Number of times lookaside has been disabled */ u8 prepFlags; /* SQLITE_PREPARE_* flags */ u8 withinRJSubrtn; /* Nesting level for RIGHT JOIN body subroutines */ + u8 bHasExists; /* Has a correlated "EXISTS (SELECT ....)" expression */ u8 mSubrtnSig; /* mini Bloom filter on available SubrtnSig.selId */ u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */ u8 bReturning; /* Coding a RETURNING trigger */ @@ -20769,6 +20994,7 @@ struct Walker { SrcItem *pSrcItem; /* A single FROM clause item */ DbFixer *pFix; /* See sqlite3FixSelect() */ Mem *aMem; /* See sqlite3BtreeCursorHint() */ + struct CheckOnCtx *pCheckOnCtx; /* See selectCheckOnClauses() */ } u; }; @@ -21256,6 +21482,7 @@ SQLITE_PRIVATE void sqlite3ShowTriggerList(const Trigger*); SQLITE_PRIVATE void sqlite3ShowWindow(const Window*); SQLITE_PRIVATE void sqlite3ShowWinFunc(const Window*); #endif +SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec*); #endif SQLITE_PRIVATE void sqlite3SetString(char **, sqlite3*, const char*); @@ -21572,13 +21799,17 @@ SQLITE_PRIVATE void sqlite3RegisterDateTimeFunctions(void); SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void); SQLITE_PRIVATE void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3*); #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) -SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3*); +SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3*,const char*); #endif SQLITE_PRIVATE int sqlite3SafetyCheckOk(sqlite3*); SQLITE_PRIVATE int sqlite3SafetyCheckSickOrOk(sqlite3*); SQLITE_PRIVATE void sqlite3ChangeCookie(Parse*, int); SQLITE_PRIVATE With *sqlite3WithDup(sqlite3 *db, With *p); +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3*); +#endif + #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER) SQLITE_PRIVATE void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int); #endif @@ -21799,7 +22030,7 @@ SQLITE_PRIVATE void sqlite3Reindex(Parse*, Token*, Token*); SQLITE_PRIVATE void sqlite3AlterFunctions(void); SQLITE_PRIVATE void sqlite3AlterRenameTable(Parse*, SrcList*, Token*); SQLITE_PRIVATE void sqlite3AlterRenameColumn(Parse*, SrcList*, Token*, Token*); -SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *, int *); +SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *, int *); SQLITE_PRIVATE void sqlite3NestedParse(Parse*, const char*, ...); SQLITE_PRIVATE void sqlite3ExpirePreparedStatements(sqlite3*, int); SQLITE_PRIVATE void sqlite3CodeRhsOfIN(Parse*, Expr*, int); @@ -22565,6 +22796,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_ENABLE_BYTECODE_VTAB "ENABLE_BYTECODE_VTAB", #endif +#ifdef SQLITE_ENABLE_CARRAY + "ENABLE_CARRAY", +#endif #ifdef SQLITE_ENABLE_CEROD "ENABLE_CEROD=" CTIMEOPT_VAL(SQLITE_ENABLE_CEROD), #endif @@ -22655,6 +22889,9 @@ static const char * const sqlite3azCompileOpt[] = { #ifdef SQLITE_ENABLE_OVERSIZE_CELL_CHECK "ENABLE_OVERSIZE_CELL_CHECK", #endif +#ifdef SQLITE_ENABLE_PERCENTILE + "ENABLE_PERCENTILE", +#endif #ifdef SQLITE_ENABLE_PREUPDATE_HOOK "ENABLE_PREUPDATE_HOOK", #endif @@ -23869,7 +24106,7 @@ struct sqlite3_value { ** MEM_Int, MEM_Real, and MEM_IntReal. ** ** * MEM_Blob|MEM_Zero A blob in Mem.z of length Mem.n plus -** MEM.u.i extra 0x00 bytes at the end. +** Mem.u.nZero extra 0x00 bytes at the end. ** ** * MEM_Int Integer stored in Mem.u.i. ** @@ -24138,7 +24375,9 @@ struct PreUpdate { Table *pTab; /* Schema object being updated */ Index *pPk; /* PK index if pTab is WITHOUT ROWID */ sqlite3_value **apDflt; /* Array of default values, if required */ - u8 keyinfoSpace[SZ_KEYINFO(0)]; /* Space to hold pKeyinfo[0] content */ + struct { + u8 keyinfoSpace[SZ_KEYINFO_0]; /* Space to hold pKeyinfo[0] content */ + } uKey; }; /* @@ -24302,9 +24541,11 @@ SQLITE_PRIVATE int sqlite3VdbeCheckMemInvariants(Mem*); #endif #ifndef SQLITE_OMIT_FOREIGN_KEY -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *, int); +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe*); +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe*); #else -# define sqlite3VdbeCheckFk(p,i) 0 +# define sqlite3VdbeCheckFkImmediate(p) 0 +# define sqlite3VdbeCheckFkDeferred(p) 0 #endif #ifdef SQLITE_DEBUG @@ -24513,23 +24754,25 @@ SQLITE_PRIVATE int sqlite3LookasideUsed(sqlite3 *db, int *pHighwater){ /* ** Query status information for a single database connection */ -SQLITE_API int sqlite3_db_status( - sqlite3 *db, /* The database connection whose status is desired */ - int op, /* Status verb */ - int *pCurrent, /* Write current value here */ - int *pHighwater, /* Write high-water mark here */ - int resetFlag /* Reset high-water mark if true */ +SQLITE_API int sqlite3_db_status64( + sqlite3 *db, /* The database connection whose status is desired */ + int op, /* Status verb */ + sqlite3_int64 *pCurrent, /* Write current value here */ + sqlite3_int64 *pHighwtr, /* Write high-water mark here */ + int resetFlag /* Reset high-water mark if true */ ){ int rc = SQLITE_OK; /* Return code */ #ifdef SQLITE_ENABLE_API_ARMOR - if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwater==0 ){ + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ return SQLITE_MISUSE_BKPT; } #endif sqlite3_mutex_enter(db->mutex); switch( op ){ case SQLITE_DBSTATUS_LOOKASIDE_USED: { - *pCurrent = sqlite3LookasideUsed(db, pHighwater); + int H = 0; + *pCurrent = sqlite3LookasideUsed(db, &H); + *pHighwtr = H; if( resetFlag ){ LookasideSlot *p = db->lookaside.pFree; if( p ){ @@ -24560,7 +24803,7 @@ SQLITE_API int sqlite3_db_status( assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)>=0 ); assert( (op-SQLITE_DBSTATUS_LOOKASIDE_HIT)<3 ); *pCurrent = 0; - *pHighwater = (int)db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; + *pHighwtr = db->lookaside.anStat[op-SQLITE_DBSTATUS_LOOKASIDE_HIT]; if( resetFlag ){ db->lookaside.anStat[op - SQLITE_DBSTATUS_LOOKASIDE_HIT] = 0; } @@ -24574,7 +24817,7 @@ SQLITE_API int sqlite3_db_status( */ case SQLITE_DBSTATUS_CACHE_USED_SHARED: case SQLITE_DBSTATUS_CACHE_USED: { - int totalUsed = 0; + sqlite3_int64 totalUsed = 0; int i; sqlite3BtreeEnterAll(db); for(i=0; i<db->nDb; i++){ @@ -24590,18 +24833,18 @@ SQLITE_API int sqlite3_db_status( } sqlite3BtreeLeaveAll(db); *pCurrent = totalUsed; - *pHighwater = 0; + *pHighwtr = 0; break; } /* ** *pCurrent gets an accurate estimate of the amount of memory used ** to store the schema for all databases (main, temp, and any ATTACHed - ** databases. *pHighwater is set to zero. + ** databases. *pHighwtr is set to zero. */ case SQLITE_DBSTATUS_SCHEMA_USED: { - int i; /* Used to iterate through schemas */ - int nByte = 0; /* Used to accumulate return value */ + int i; /* Used to iterate through schemas */ + int nByte = 0; /* Used to accumulate return value */ sqlite3BtreeEnterAll(db); db->pnBytesFreed = &nByte; @@ -24635,7 +24878,7 @@ SQLITE_API int sqlite3_db_status( db->lookaside.pEnd = db->lookaside.pTrueEnd; sqlite3BtreeLeaveAll(db); - *pHighwater = 0; + *pHighwtr = 0; *pCurrent = nByte; break; } @@ -24643,7 +24886,7 @@ SQLITE_API int sqlite3_db_status( /* ** *pCurrent gets an accurate estimate of the amount of memory used ** to store all prepared statements. - ** *pHighwater is set to zero. + ** *pHighwtr is set to zero. */ case SQLITE_DBSTATUS_STMT_USED: { struct Vdbe *pVdbe; /* Used to iterate through VMs */ @@ -24658,7 +24901,7 @@ SQLITE_API int sqlite3_db_status( db->lookaside.pEnd = db->lookaside.pTrueEnd; db->pnBytesFreed = 0; - *pHighwater = 0; /* IMP: R-64479-57858 */ + *pHighwtr = 0; /* IMP: R-64479-57858 */ *pCurrent = nByte; break; @@ -24666,7 +24909,7 @@ SQLITE_API int sqlite3_db_status( /* ** Set *pCurrent to the total cache hits or misses encountered by all - ** pagers the database handle is connected to. *pHighwater is always set + ** pagers the database handle is connected to. *pHighwtr is always set ** to zero. */ case SQLITE_DBSTATUS_CACHE_SPILL: @@ -24686,19 +24929,39 @@ SQLITE_API int sqlite3_db_status( sqlite3PagerCacheStat(pPager, op, resetFlag, &nRet); } } - *pHighwater = 0; /* IMP: R-42420-56072 */ + *pHighwtr = 0; /* IMP: R-42420-56072 */ /* IMP: R-54100-20147 */ /* IMP: R-29431-39229 */ - *pCurrent = (int)nRet & 0x7fffffff; + *pCurrent = nRet; + break; + } + + /* Set *pCurrent to the number of bytes that the db database connection + ** has spilled to the filesystem in temporary files that could have been + ** stored in memory, had sufficient memory been available. + ** The *pHighwater is always set to zero. + */ + case SQLITE_DBSTATUS_TEMPBUF_SPILL: { + u64 nRet = 0; + if( db->aDb[1].pBt ){ + Pager *pPager = sqlite3BtreePager(db->aDb[1].pBt); + sqlite3PagerCacheStat(pPager, SQLITE_DBSTATUS_CACHE_WRITE, + resetFlag, &nRet); + nRet *= sqlite3BtreeGetPageSize(db->aDb[1].pBt); + } + nRet += db->nSpill; + if( resetFlag ) db->nSpill = 0; + *pHighwtr = 0; + *pCurrent = nRet; break; } /* Set *pCurrent to non-zero if there are unresolved deferred foreign ** key constraints. Set *pCurrent to zero if all foreign key constraints - ** have been satisfied. The *pHighwater is always set to zero. + ** have been satisfied. The *pHighwtr is always set to zero. */ case SQLITE_DBSTATUS_DEFERRED_FKS: { - *pHighwater = 0; /* IMP: R-11967-56545 */ + *pHighwtr = 0; /* IMP: R-11967-56545 */ *pCurrent = db->nDeferredImmCons>0 || db->nDeferredCons>0; break; } @@ -24711,6 +24974,31 @@ SQLITE_API int sqlite3_db_status( return rc; } +/* +** 32-bit variant of sqlite3_db_status64() +*/ +SQLITE_API int sqlite3_db_status( + sqlite3 *db, /* The database connection whose status is desired */ + int op, /* Status verb */ + int *pCurrent, /* Write current value here */ + int *pHighwtr, /* Write high-water mark here */ + int resetFlag /* Reset high-water mark if true */ +){ + sqlite3_int64 C = 0, H = 0; + int rc; +#ifdef SQLITE_ENABLE_API_ARMOR + if( !sqlite3SafetyCheckOk(db) || pCurrent==0|| pHighwtr==0 ){ + return SQLITE_MISUSE_BKPT; + } +#endif + rc = sqlite3_db_status64(db, op, &C, &H, resetFlag); + if( rc==0 ){ + *pCurrent = C & 0x7fffffff; + *pHighwtr = H & 0x7fffffff; + } + return rc; +} + /************** End of status.c **********************************************/ /************** Begin file date.c ********************************************/ /* @@ -24903,6 +25191,10 @@ static int parseTimezone(const char *zDate, DateTime *p){ } zDate += 5; p->tz = sgn*(nMn + nHr*60); + if( p->tz==0 ){ /* Forum post 2025-09-17T10:12:14z */ + p->isLocal = 0; + p->isUtc = 1; + } zulu_time: while( sqlite3Isspace(*zDate) ){ zDate++; } return *zDate!=0; @@ -26098,8 +26390,8 @@ static int daysAfterSunday(DateTime *pDate){ ** %l hour 1-12 (leading zero converted to space) ** %m month 01-12 ** %M minute 00-59 -** %p "am" or "pm" -** %P "AM" or "PM" +** %p "AM" or "PM" +** %P "am" or "pm" ** %R time as HH:MM ** %s seconds since 1970-01-01 ** %S seconds 00-59 @@ -31706,6 +31998,7 @@ typedef struct et_info { /* Information about each format field */ etByte type; /* Conversion paradigm */ etByte charset; /* Offset into aDigits[] of the digits string */ etByte prefix; /* Offset into aPrefix[] of the prefix string */ + char iNxt; /* Next with same hash, or 0 for end of chain */ } et_info; /* @@ -31714,44 +32007,61 @@ typedef struct et_info { /* Information about each format field */ #define FLAG_SIGNED 1 /* True if the value to convert is signed */ #define FLAG_STRING 4 /* Allow infinite precision */ - /* -** The following table is searched linearly, so it is good to put the -** most frequently used conversion types first. +** The table is searched by hash. In the case of %C where C is the character +** and that character has ASCII value j, then the hash is j%23. +** +** The order of the entries in fmtinfo[] and the hash chain was entered +** manually, but based on the output of the following TCL script: */ +#if 0 /***** Beginning of script ******/ +foreach c {d s g z q Q w c o u x X f e E G i n % p T S r} { + scan $c %c x + set n($c) $x +} +set mx [llength [array names n]] +puts "count: $mx" + +set mx 27 +puts "*********** mx=$mx ************" +for {set r 0} {$r<$mx} {incr r} { + puts -nonewline [format %2d: $r] + foreach c [array names n] { + if {($n($c))%$mx==$r} {puts -nonewline " $c"} + } + puts "" +} +#endif /***** End of script ********/ + static const char aDigits[] = "0123456789ABCDEF0123456789abcdef"; static const char aPrefix[] = "-x0\000X0"; -static const et_info fmtinfo[] = { - { 'd', 10, 1, etDECIMAL, 0, 0 }, - { 's', 0, 4, etSTRING, 0, 0 }, - { 'g', 0, 1, etGENERIC, 30, 0 }, - { 'z', 0, 4, etDYNSTRING, 0, 0 }, - { 'q', 0, 4, etESCAPE_q, 0, 0 }, - { 'Q', 0, 4, etESCAPE_Q, 0, 0 }, - { 'w', 0, 4, etESCAPE_w, 0, 0 }, - { 'c', 0, 0, etCHARX, 0, 0 }, - { 'o', 8, 0, etRADIX, 0, 2 }, - { 'u', 10, 0, etDECIMAL, 0, 0 }, - { 'x', 16, 0, etRADIX, 16, 1 }, - { 'X', 16, 0, etRADIX, 0, 4 }, -#ifndef SQLITE_OMIT_FLOATING_POINT - { 'f', 0, 1, etFLOAT, 0, 0 }, - { 'e', 0, 1, etEXP, 30, 0 }, - { 'E', 0, 1, etEXP, 14, 0 }, - { 'G', 0, 1, etGENERIC, 14, 0 }, -#endif - { 'i', 10, 1, etDECIMAL, 0, 0 }, - { 'n', 0, 0, etSIZE, 0, 0 }, - { '%', 0, 0, etPERCENT, 0, 0 }, - { 'p', 16, 0, etPOINTER, 0, 1 }, - - /* All the rest are undocumented and are for internal use only */ - { 'T', 0, 0, etTOKEN, 0, 0 }, - { 'S', 0, 0, etSRCITEM, 0, 0 }, - { 'r', 10, 1, etORDINAL, 0, 0 }, +static const et_info fmtinfo[23] = { + /* 0 */ { 's', 0, 4, etSTRING, 0, 0, 1 }, + /* 1 */ { 'E', 0, 1, etEXP, 14, 0, 0 }, /* Hash: 0 */ + /* 2 */ { 'u', 10, 0, etDECIMAL, 0, 0, 3 }, + /* 3 */ { 'G', 0, 1, etGENERIC, 14, 0, 0 }, /* Hash: 2 */ + /* 4 */ { 'w', 0, 4, etESCAPE_w, 0, 0, 0 }, + /* 5 */ { 'x', 16, 0, etRADIX, 16, 1, 0 }, + /* 6 */ { 'c', 0, 0, etCHARX, 0, 0, 0 }, /* Hash: 7 */ + /* 7 */ { 'z', 0, 4, etDYNSTRING, 0, 0, 6 }, + /* 8 */ { 'd', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 9 */ { 'e', 0, 1, etEXP, 30, 0, 0 }, + /* 10 */ { 'f', 0, 1, etFLOAT, 0, 0, 0 }, + /* 11 */ { 'g', 0, 1, etGENERIC, 30, 0, 0 }, + /* 12 */ { 'Q', 0, 4, etESCAPE_Q, 0, 0, 0 }, + /* 13 */ { 'i', 10, 1, etDECIMAL, 0, 0, 0 }, + /* 14 */ { '%', 0, 0, etPERCENT, 0, 0, 16 }, + /* 15 */ { 'T', 0, 0, etTOKEN, 0, 0, 0 }, + /* 16 */ { 'S', 0, 0, etSRCITEM, 0, 0, 0 }, /* Hash: 14 */ + /* 17 */ { 'X', 16, 0, etRADIX, 0, 4, 0 }, /* Hash: 19 */ + /* 18 */ { 'n', 0, 0, etSIZE, 0, 0, 0 }, + /* 19 */ { 'o', 8, 0, etRADIX, 0, 2, 17 }, + /* 20 */ { 'p', 16, 0, etPOINTER, 0, 1, 0 }, + /* 21 */ { 'q', 0, 4, etESCAPE_q, 0, 0, 0 }, + /* 22 */ { 'r', 10, 1, etORDINAL, 0, 0, 0 } }; -/* Notes: +/* Additional Notes: ** ** %S Takes a pointer to SrcItem. Shows name or database.name ** %!S Like %S but prefer the zName over the zAlias @@ -31878,7 +32188,10 @@ SQLITE_API void sqlite3_str_vappendf( #if HAVE_STRCHRNUL fmt = strchrnul(fmt, '%'); #else - do{ fmt++; }while( *fmt && *fmt != '%' ); + fmt = strchr(fmt, '%'); + if( fmt==0 ){ + fmt = bufpt + strlen(bufpt); + } #endif sqlite3_str_append(pAccum, bufpt, (int)(fmt - bufpt)); if( *fmt==0 ) break; @@ -31992,6 +32305,9 @@ SQLITE_API void sqlite3_str_vappendf( }while( !done && (c=(*++fmt))!=0 ); /* Fetch the info entry for the field */ +#ifdef SQLITE_EBCDIC + /* The hash table only works for ASCII. For EBCDIC, we need to do + ** a linear search of the table */ infop = &fmtinfo[0]; xtype = etINVALID; for(idx=0; idx<ArraySize(fmtinfo); idx++){ @@ -32001,6 +32317,20 @@ SQLITE_API void sqlite3_str_vappendf( break; } } +#else + /* Fast hash-table lookup */ + assert( ArraySize(fmtinfo)==23 ); + idx = ((unsigned)c) % 23; + if( fmtinfo[idx].fmttype==c + || fmtinfo[idx = fmtinfo[idx].iNxt].fmttype==c + ){ + infop = &fmtinfo[idx]; + xtype = infop->type; + }else{ + infop = &fmtinfo[0]; + xtype = etINVALID; + } +#endif /* ** At this point, variables are initialized as follows: @@ -32068,6 +32398,14 @@ SQLITE_API void sqlite3_str_vappendf( } prefix = 0; } + +#if WHERETRACE_ENABLED + if( xtype==etPOINTER && sqlite3WhereTrace & 0x100000 ) longvalue = 0; +#endif +#if TREETRACE_ENABLED + if( xtype==etPOINTER && sqlite3TreeTrace & 0x100000 ) longvalue = 0; +#endif + if( longvalue==0 ) flag_alternateform = 0; if( flag_zeropad && precision<width-(prefix!=0) ){ precision = width-(prefix!=0); @@ -32180,7 +32518,21 @@ SQLITE_API void sqlite3_str_vappendf( } } if( s.sign=='-' ){ - prefix = '-'; + if( flag_alternateform + && !flag_prefix + && xtype==etFLOAT + && s.iDP<=iRound + ){ + /* Suppress the minus sign if all of the following are true: + ** * The value displayed is zero + ** * The '#' flag is used + ** * The '+' flag is not used, and + ** * The format is %f + */ + prefix = 0; + }else{ + prefix = '-'; + } }else{ prefix = flag_prefix; } @@ -33391,9 +33743,13 @@ SQLITE_PRIVATE void sqlite3TreeViewSrcList(TreeView *pView, const SrcList *pSrc) n = 0; if( pItem->fg.isSubquery ) n++; if( pItem->fg.isTabFunc ) n++; - if( pItem->fg.isUsing ) n++; + if( pItem->fg.isUsing || pItem->u3.pOn!=0 ) n++; if( pItem->fg.isUsing ){ sqlite3TreeViewIdList(pView, pItem->u3.pUsing, (--n)>0, "USING"); + }else if( pItem->u3.pOn!=0 ){ + sqlite3TreeViewItem(pView, "ON", (--n)>0); + sqlite3TreeViewExpr(pView, pItem->u3.pOn, 0); + sqlite3TreeViewPop(&pView); } if( pItem->fg.isSubquery ){ assert( n==1 ); @@ -37699,20 +38055,20 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 34 */ "SorterSort" OpHelp(""), /* 35 */ "Sort" OpHelp(""), /* 36 */ "Rewind" OpHelp(""), - /* 37 */ "SorterNext" OpHelp(""), - /* 38 */ "Prev" OpHelp(""), - /* 39 */ "Next" OpHelp(""), - /* 40 */ "IdxLE" OpHelp("key=r[P3@P4]"), - /* 41 */ "IdxGT" OpHelp("key=r[P3@P4]"), - /* 42 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 37 */ "IfEmpty" OpHelp("if( empty(P1) ) goto P2"), + /* 38 */ "SorterNext" OpHelp(""), + /* 39 */ "Prev" OpHelp(""), + /* 40 */ "Next" OpHelp(""), + /* 41 */ "IdxLE" OpHelp("key=r[P3@P4]"), + /* 42 */ "IdxGT" OpHelp("key=r[P3@P4]"), /* 43 */ "Or" OpHelp("r[P3]=(r[P1] || r[P2])"), /* 44 */ "And" OpHelp("r[P3]=(r[P1] && r[P2])"), - /* 45 */ "IdxGE" OpHelp("key=r[P3@P4]"), - /* 46 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), - /* 47 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), - /* 48 */ "Program" OpHelp(""), - /* 49 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), - /* 50 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 45 */ "IdxLT" OpHelp("key=r[P3@P4]"), + /* 46 */ "IdxGE" OpHelp("key=r[P3@P4]"), + /* 47 */ "RowSetRead" OpHelp("r[P3]=rowset(P1)"), + /* 48 */ "RowSetTest" OpHelp("if r[P3] in rowset(P1) goto P2"), + /* 49 */ "Program" OpHelp(""), + /* 50 */ "FkIfZero" OpHelp("if fkctr[P1]==0 goto P2"), /* 51 */ "IsNull" OpHelp("if r[P1]==NULL goto P2"), /* 52 */ "NotNull" OpHelp("if r[P1]!=NULL goto P2"), /* 53 */ "Ne" OpHelp("IF r[P3]!=r[P1]"), @@ -37722,49 +38078,49 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 57 */ "Lt" OpHelp("IF r[P3]<r[P1]"), /* 58 */ "Ge" OpHelp("IF r[P3]>=r[P1]"), /* 59 */ "ElseEq" OpHelp(""), - /* 60 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), - /* 61 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), - /* 62 */ "IncrVacuum" OpHelp(""), - /* 63 */ "VNext" OpHelp(""), - /* 64 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"), - /* 65 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), - /* 66 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), - /* 67 */ "Return" OpHelp(""), - /* 68 */ "EndCoroutine" OpHelp(""), - /* 69 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), - /* 70 */ "Halt" OpHelp(""), - /* 71 */ "Integer" OpHelp("r[P2]=P1"), - /* 72 */ "Int64" OpHelp("r[P2]=P4"), - /* 73 */ "String" OpHelp("r[P2]='P4' (len=P1)"), - /* 74 */ "BeginSubrtn" OpHelp("r[P2]=NULL"), - /* 75 */ "Null" OpHelp("r[P2..P3]=NULL"), - /* 76 */ "SoftNull" OpHelp("r[P1]=NULL"), - /* 77 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), - /* 78 */ "Variable" OpHelp("r[P2]=parameter(P1)"), - /* 79 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), - /* 80 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), - /* 81 */ "SCopy" OpHelp("r[P2]=r[P1]"), - /* 82 */ "IntCopy" OpHelp("r[P2]=r[P1]"), - /* 83 */ "FkCheck" OpHelp(""), - /* 84 */ "ResultRow" OpHelp("output=r[P1@P2]"), - /* 85 */ "CollSeq" OpHelp(""), - /* 86 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), - /* 87 */ "RealAffinity" OpHelp(""), - /* 88 */ "Cast" OpHelp("affinity(r[P1])"), - /* 89 */ "Permutation" OpHelp(""), - /* 90 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), - /* 91 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), - /* 92 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"), - /* 93 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), - /* 94 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"), - /* 95 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"), - /* 96 */ "Affinity" OpHelp("affinity(r[P1@P2])"), - /* 97 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), - /* 98 */ "Count" OpHelp("r[P2]=count()"), - /* 99 */ "ReadCookie" OpHelp(""), - /* 100 */ "SetCookie" OpHelp(""), - /* 101 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), - /* 102 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 60 */ "IfPos" OpHelp("if r[P1]>0 then r[P1]-=P3, goto P2"), + /* 61 */ "IfNotZero" OpHelp("if r[P1]!=0 then r[P1]--, goto P2"), + /* 62 */ "DecrJumpZero" OpHelp("if (--r[P1])==0 goto P2"), + /* 63 */ "IncrVacuum" OpHelp(""), + /* 64 */ "VNext" OpHelp(""), + /* 65 */ "Filter" OpHelp("if key(P3@P4) not in filter(P1) goto P2"), + /* 66 */ "PureFunc" OpHelp("r[P3]=func(r[P2@NP])"), + /* 67 */ "Function" OpHelp("r[P3]=func(r[P2@NP])"), + /* 68 */ "Return" OpHelp(""), + /* 69 */ "EndCoroutine" OpHelp(""), + /* 70 */ "HaltIfNull" OpHelp("if r[P3]=null halt"), + /* 71 */ "Halt" OpHelp(""), + /* 72 */ "Integer" OpHelp("r[P2]=P1"), + /* 73 */ "Int64" OpHelp("r[P2]=P4"), + /* 74 */ "String" OpHelp("r[P2]='P4' (len=P1)"), + /* 75 */ "BeginSubrtn" OpHelp("r[P2]=NULL"), + /* 76 */ "Null" OpHelp("r[P2..P3]=NULL"), + /* 77 */ "SoftNull" OpHelp("r[P1]=NULL"), + /* 78 */ "Blob" OpHelp("r[P2]=P4 (len=P1)"), + /* 79 */ "Variable" OpHelp("r[P2]=parameter(P1)"), + /* 80 */ "Move" OpHelp("r[P2@P3]=r[P1@P3]"), + /* 81 */ "Copy" OpHelp("r[P2@P3+1]=r[P1@P3+1]"), + /* 82 */ "SCopy" OpHelp("r[P2]=r[P1]"), + /* 83 */ "IntCopy" OpHelp("r[P2]=r[P1]"), + /* 84 */ "FkCheck" OpHelp(""), + /* 85 */ "ResultRow" OpHelp("output=r[P1@P2]"), + /* 86 */ "CollSeq" OpHelp(""), + /* 87 */ "AddImm" OpHelp("r[P1]=r[P1]+P2"), + /* 88 */ "RealAffinity" OpHelp(""), + /* 89 */ "Cast" OpHelp("affinity(r[P1])"), + /* 90 */ "Permutation" OpHelp(""), + /* 91 */ "Compare" OpHelp("r[P1@P3] <-> r[P2@P3]"), + /* 92 */ "IsTrue" OpHelp("r[P2] = coalesce(r[P1]==TRUE,P3) ^ P4"), + /* 93 */ "ZeroOrNull" OpHelp("r[P2] = 0 OR NULL"), + /* 94 */ "Offset" OpHelp("r[P3] = sqlite_offset(P1)"), + /* 95 */ "Column" OpHelp("r[P3]=PX cursor P1 column P2"), + /* 96 */ "TypeCheck" OpHelp("typecheck(r[P1@P2])"), + /* 97 */ "Affinity" OpHelp("affinity(r[P1@P2])"), + /* 98 */ "MakeRecord" OpHelp("r[P3]=mkrec(r[P1@P2])"), + /* 99 */ "Count" OpHelp("r[P2]=count()"), + /* 100 */ "ReadCookie" OpHelp(""), + /* 101 */ "SetCookie" OpHelp(""), + /* 102 */ "ReopenIdx" OpHelp("root=P2 iDb=P3"), /* 103 */ "BitAnd" OpHelp("r[P3]=r[P1]&r[P2]"), /* 104 */ "BitOr" OpHelp("r[P3]=r[P1]|r[P2]"), /* 105 */ "ShiftLeft" OpHelp("r[P3]=r[P2]<<r[P1]"), @@ -37775,83 +38131,84 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){ /* 110 */ "Divide" OpHelp("r[P3]=r[P2]/r[P1]"), /* 111 */ "Remainder" OpHelp("r[P3]=r[P2]%r[P1]"), /* 112 */ "Concat" OpHelp("r[P3]=r[P2]+r[P1]"), - /* 113 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), - /* 114 */ "OpenDup" OpHelp(""), + /* 113 */ "OpenRead" OpHelp("root=P2 iDb=P3"), + /* 114 */ "OpenWrite" OpHelp("root=P2 iDb=P3"), /* 115 */ "BitNot" OpHelp("r[P2]= ~r[P1]"), - /* 116 */ "OpenAutoindex" OpHelp("nColumn=P2"), - /* 117 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 116 */ "OpenDup" OpHelp(""), + /* 117 */ "OpenAutoindex" OpHelp("nColumn=P2"), /* 118 */ "String8" OpHelp("r[P2]='P4'"), - /* 119 */ "SorterOpen" OpHelp(""), - /* 120 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), - /* 121 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), - /* 122 */ "Close" OpHelp(""), - /* 123 */ "ColumnsUsed" OpHelp(""), - /* 124 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"), - /* 125 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"), - /* 126 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), - /* 127 */ "NewRowid" OpHelp("r[P2]=rowid"), - /* 128 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), - /* 129 */ "RowCell" OpHelp(""), - /* 130 */ "Delete" OpHelp(""), - /* 131 */ "ResetCount" OpHelp(""), - /* 132 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), - /* 133 */ "SorterData" OpHelp("r[P2]=data"), - /* 134 */ "RowData" OpHelp("r[P2]=data"), - /* 135 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"), - /* 136 */ "NullRow" OpHelp(""), - /* 137 */ "SeekEnd" OpHelp(""), - /* 138 */ "IdxInsert" OpHelp("key=r[P2]"), - /* 139 */ "SorterInsert" OpHelp("key=r[P2]"), - /* 140 */ "IdxDelete" OpHelp("key=r[P2@P3]"), - /* 141 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), - /* 142 */ "IdxRowid" OpHelp("r[P2]=rowid"), - /* 143 */ "FinishSeek" OpHelp(""), - /* 144 */ "Destroy" OpHelp(""), - /* 145 */ "Clear" OpHelp(""), - /* 146 */ "ResetSorter" OpHelp(""), - /* 147 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), - /* 148 */ "SqlExec" OpHelp(""), - /* 149 */ "ParseSchema" OpHelp(""), - /* 150 */ "LoadAnalysis" OpHelp(""), - /* 151 */ "DropTable" OpHelp(""), - /* 152 */ "DropIndex" OpHelp(""), - /* 153 */ "DropTrigger" OpHelp(""), + /* 119 */ "OpenEphemeral" OpHelp("nColumn=P2"), + /* 120 */ "SorterOpen" OpHelp(""), + /* 121 */ "SequenceTest" OpHelp("if( cursor[P1].ctr++ ) pc = P2"), + /* 122 */ "OpenPseudo" OpHelp("P3 columns in r[P2]"), + /* 123 */ "Close" OpHelp(""), + /* 124 */ "ColumnsUsed" OpHelp(""), + /* 125 */ "SeekScan" OpHelp("Scan-ahead up to P1 rows"), + /* 126 */ "SeekHit" OpHelp("set P2<=seekHit<=P3"), + /* 127 */ "Sequence" OpHelp("r[P2]=cursor[P1].ctr++"), + /* 128 */ "NewRowid" OpHelp("r[P2]=rowid"), + /* 129 */ "Insert" OpHelp("intkey=r[P3] data=r[P2]"), + /* 130 */ "RowCell" OpHelp(""), + /* 131 */ "Delete" OpHelp(""), + /* 132 */ "ResetCount" OpHelp(""), + /* 133 */ "SorterCompare" OpHelp("if key(P1)!=trim(r[P3],P4) goto P2"), + /* 134 */ "SorterData" OpHelp("r[P2]=data"), + /* 135 */ "RowData" OpHelp("r[P2]=data"), + /* 136 */ "Rowid" OpHelp("r[P2]=PX rowid of P1"), + /* 137 */ "NullRow" OpHelp(""), + /* 138 */ "SeekEnd" OpHelp(""), + /* 139 */ "IdxInsert" OpHelp("key=r[P2]"), + /* 140 */ "SorterInsert" OpHelp("key=r[P2]"), + /* 141 */ "IdxDelete" OpHelp("key=r[P2@P3]"), + /* 142 */ "DeferredSeek" OpHelp("Move P3 to P1.rowid if needed"), + /* 143 */ "IdxRowid" OpHelp("r[P2]=rowid"), + /* 144 */ "FinishSeek" OpHelp(""), + /* 145 */ "Destroy" OpHelp(""), + /* 146 */ "Clear" OpHelp(""), + /* 147 */ "ResetSorter" OpHelp(""), + /* 148 */ "CreateBtree" OpHelp("r[P2]=root iDb=P1 flags=P3"), + /* 149 */ "SqlExec" OpHelp(""), + /* 150 */ "ParseSchema" OpHelp(""), + /* 151 */ "LoadAnalysis" OpHelp(""), + /* 152 */ "DropTable" OpHelp(""), + /* 153 */ "DropIndex" OpHelp(""), /* 154 */ "Real" OpHelp("r[P2]=P4"), - /* 155 */ "IntegrityCk" OpHelp(""), - /* 156 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), - /* 157 */ "Param" OpHelp(""), - /* 158 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), - /* 159 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), - /* 160 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), - /* 161 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), - /* 162 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 163 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), - /* 164 */ "AggValue" OpHelp("r[P3]=value N=P2"), - /* 165 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), - /* 166 */ "Expire" OpHelp(""), - /* 167 */ "CursorLock" OpHelp(""), - /* 168 */ "CursorUnlock" OpHelp(""), - /* 169 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), - /* 170 */ "VBegin" OpHelp(""), - /* 171 */ "VCreate" OpHelp(""), - /* 172 */ "VDestroy" OpHelp(""), - /* 173 */ "VOpen" OpHelp(""), - /* 174 */ "VCheck" OpHelp(""), - /* 175 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"), - /* 176 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), - /* 177 */ "VRename" OpHelp(""), - /* 178 */ "Pagecount" OpHelp(""), - /* 179 */ "MaxPgcnt" OpHelp(""), - /* 180 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"), - /* 181 */ "GetSubtype" OpHelp("r[P2] = r[P1].subtype"), - /* 182 */ "SetSubtype" OpHelp("r[P2].subtype = r[P1]"), - /* 183 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"), - /* 184 */ "Trace" OpHelp(""), - /* 185 */ "CursorHint" OpHelp(""), - /* 186 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), - /* 187 */ "Noop" OpHelp(""), - /* 188 */ "Explain" OpHelp(""), - /* 189 */ "Abortable" OpHelp(""), + /* 155 */ "DropTrigger" OpHelp(""), + /* 156 */ "IntegrityCk" OpHelp(""), + /* 157 */ "RowSetAdd" OpHelp("rowset(P1)=r[P2]"), + /* 158 */ "Param" OpHelp(""), + /* 159 */ "FkCounter" OpHelp("fkctr[P1]+=P2"), + /* 160 */ "MemMax" OpHelp("r[P1]=max(r[P1],r[P2])"), + /* 161 */ "OffsetLimit" OpHelp("if r[P1]>0 then r[P2]=r[P1]+max(0,r[P3]) else r[P2]=(-1)"), + /* 162 */ "AggInverse" OpHelp("accum=r[P3] inverse(r[P2@P5])"), + /* 163 */ "AggStep" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 164 */ "AggStep1" OpHelp("accum=r[P3] step(r[P2@P5])"), + /* 165 */ "AggValue" OpHelp("r[P3]=value N=P2"), + /* 166 */ "AggFinal" OpHelp("accum=r[P1] N=P2"), + /* 167 */ "Expire" OpHelp(""), + /* 168 */ "CursorLock" OpHelp(""), + /* 169 */ "CursorUnlock" OpHelp(""), + /* 170 */ "TableLock" OpHelp("iDb=P1 root=P2 write=P3"), + /* 171 */ "VBegin" OpHelp(""), + /* 172 */ "VCreate" OpHelp(""), + /* 173 */ "VDestroy" OpHelp(""), + /* 174 */ "VOpen" OpHelp(""), + /* 175 */ "VCheck" OpHelp(""), + /* 176 */ "VInitIn" OpHelp("r[P2]=ValueList(P1,P3)"), + /* 177 */ "VColumn" OpHelp("r[P3]=vcolumn(P2)"), + /* 178 */ "VRename" OpHelp(""), + /* 179 */ "Pagecount" OpHelp(""), + /* 180 */ "MaxPgcnt" OpHelp(""), + /* 181 */ "ClrSubtype" OpHelp("r[P1].subtype = 0"), + /* 182 */ "GetSubtype" OpHelp("r[P2] = r[P1].subtype"), + /* 183 */ "SetSubtype" OpHelp("r[P2].subtype = r[P1]"), + /* 184 */ "FilterAdd" OpHelp("filter(P1) += key(P3@P4)"), + /* 185 */ "Trace" OpHelp(""), + /* 186 */ "CursorHint" OpHelp(""), + /* 187 */ "ReleaseReg" OpHelp("release r[P1@P2] mask P3"), + /* 188 */ "Noop" OpHelp(""), + /* 189 */ "Explain" OpHelp(""), + /* 190 */ "Abortable" OpHelp(""), }; return azName[i]; } @@ -38035,7 +38392,7 @@ static int kvstorageRead(const char*, const char *zKey, char *zBuf, int nBuf); #define KVSTORAGE_KEY_SZ 32 /* Expand the key name with an appropriate prefix and put the result -** zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least +** in zKeyOut[]. The zKeyOut[] buffer is assumed to hold at least ** KVSTORAGE_KEY_SZ bytes. */ static void kvstorageMakeKey( @@ -38094,10 +38451,12 @@ static int kvstorageDelete(const char *zClass, const char *zKey){ ** ** Return the total number of bytes in the data, without truncation, and ** not counting the final zero terminator. Return -1 if the key does -** not exist. +** not exist or its key cannot be read. ** -** If nBuf<=0 then this routine simply returns the size of the data without -** actually reading it. +** If nBuf<=0 then this routine simply returns the size of the data +** without actually reading it. Similarly, if nBuf==1 then it +** zero-terminates zBuf at zBuf[0] and returns the size of the data +** without reading it. */ static int kvstorageRead( const char *zClass, @@ -38146,11 +38505,9 @@ static int kvstorageRead( ** kvvfs i/o methods with JavaScript implementations in WASM builds. ** Maintenance reminder: if this struct changes in any way, the JSON ** rendering of its structure must be updated in -** sqlite3_wasm_enum_json(). There are no binary compatibility -** concerns, so it does not need an iVersion member. This file is -** necessarily always compiled together with sqlite3_wasm_enum_json(), -** and JS code dynamically creates the mapping of members based on -** that JSON description. +** sqlite3-wasm.c:sqlite3__wasm_enum_json(). There are no binary +** compatibility concerns, so it does not need an iVersion +** member. */ typedef struct sqlite3_kvvfs_methods sqlite3_kvvfs_methods; struct sqlite3_kvvfs_methods { @@ -38167,8 +38524,8 @@ struct sqlite3_kvvfs_methods { ** the compiler can hopefully optimize this level of indirection out. ** That said, kvvfs is intended primarily for use in WASM builds. ** -** Note that this is not explicitly flagged as static because the -** amalgamation build will tag it with SQLITE_PRIVATE. +** This is not explicitly flagged as static because the amalgamation +** build will tag it with SQLITE_PRIVATE. */ #ifndef SQLITE_WASM const @@ -39341,10 +39698,11 @@ static struct unix_syscall { #if defined(HAVE_FCHMOD) { "fchmod", (sqlite3_syscall_ptr)fchmod, 0 }, +#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) #else { "fchmod", (sqlite3_syscall_ptr)0, 0 }, +#define osFchmod(FID,MODE) 0 #endif -#define osFchmod ((int(*)(int,mode_t))aSyscall[14].pCurrent) #if defined(HAVE_POSIX_FALLOCATE) && HAVE_POSIX_FALLOCATE { "fallocate", (sqlite3_syscall_ptr)posix_fallocate, 0 }, @@ -39438,6 +39796,119 @@ static struct unix_syscall { }; /* End of the overrideable system calls */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Extract Posix Advisory Locking information about file description fd +** from the /proc/PID/fdinfo/FD pseudo-file. Fill the string buffer a[16] +** with characters to indicate which SQLite-relevant locks are held. +** a[16] will be a 15-character zero-terminated string with the following +** schema: +** +** AAA/B.DDD.DDDDD +** +** Each of character A-D will be "w" or "r" or "-" to indicate either a +** write-lock, a read-lock, or no-lock, respectively. The "." and "/" +** characters are delimiters intended to make the string more easily +** readable by humans. Here are the meaning of the specific letters: +** +** AAA -> The main database locks. PENDING_BYTE, RESERVED_BYTE, +** and SHARED_FIRST, respectively. +** +** B -> The deadman switch lock. Offset 128 of the -shm file. +** +** CCC -> WAL locks: WRITE, CKPT, RECOVER +** +** DDDDD -> WAL read-locks 0 through 5 +** +** Note that elements before the "/" apply to the main database file and +** elements after the "/" apply to the -shm file in WAL mode. +** +** Here is another way of thinking about the meaning of the result string: +** +** AAA/B.CCC.DDDDD +** ||| | ||| \___/ +** PENDING--'|| | ||| `----- READ 0-5 +** RESERVED--'| | ||`---- RECOVER +** SHARED ----' | |`----- CKPT +** DMS ------' `------ WRITE +** +** Return SQLITE_OK on success and SQLITE_ERROR_UNABLE if the /proc +** pseudo-filesystem is unavailable. +*/ +static int unixPosixAdvisoryLocks( + int fd, /* The file descriptor to analyze */ + char a[16] /* Write a text description of PALs here */ +){ + int in; + ssize_t n; + char *p, *pNext, *x; + char z[2000]; + + /* 1 */ + /* 012 4 678 01234 */ + memcpy(a, "---/-.---.-----", 16); + sqlite3_snprintf(sizeof(z), z, "/proc/%d/fdinfo/%d", getpid(), fd); + in = osOpen(z, O_RDONLY, 0); + if( in<0 ){ + return SQLITE_ERROR_UNABLE; + } + n = osRead(in, z, sizeof(z)-1); + osClose(in); + if( n<=0 ) return SQLITE_ERROR_UNABLE; + z[n] = 0; + + /* We are looking for lines that begin with "lock:\t". Examples: + ** + ** lock: 1: POSIX ADVISORY READ 494716 08:02:5277597 1073741826 1073742335 + ** lock: 1: POSIX ADVISORY WRITE 494716 08:02:5282282 120 120 + ** lock: 2: POSIX ADVISORY READ 494716 08:02:5282282 123 123 + ** lock: 3: POSIX ADVISORY READ 494716 08:02:5282282 128 128 + */ + pNext = strstr(z, "lock:\t"); + while( pNext ){ + char cType = 0; + sqlite3_int64 iFirst, iLast; + p = pNext+6; + pNext = strstr(p, "lock:\t"); + if( pNext ) pNext[-1] = 0; + if( (x = strstr(p, " READ "))!=0 ){ + cType = 'r'; + x += 6; + }else if( (x = strstr(p, " WRITE "))!=0 ){ + cType = 'w'; + x += 7; + }else{ + continue; + } + x = strrchr(x, ' '); + if( x==0 ) continue; + iLast = strtoll(x+1, 0, 10); + *x = 0; + x = strrchr(p, ' '); + if( x==0 ) continue; + iFirst = strtoll(x+1, 0, 10); + if( iLast>=PENDING_BYTE ){ + if( iFirst<=PENDING_BYTE && iLast>=PENDING_BYTE ) a[0] = cType; + if( iFirst<=PENDING_BYTE+1 && iLast>=PENDING_BYTE+1 ) a[1] = cType; + if( iFirst<=PENDING_BYTE+2 && iLast>=PENDING_BYTE+510 ) a[2] = cType; + }else if( iLast<=128 ){ + if( iFirst<=128 && iLast>=128 ) a[4] = cType; + if( iFirst<=120 && iLast>=120 ) a[6] = cType; + if( iFirst<=121 && iLast>=121 ) a[7] = cType; + if( iFirst<=122 && iLast>=122 ) a[8] = cType; + if( iFirst<=123 && iLast>=123 ) a[10] = cType; + if( iFirst<=124 && iLast>=124 ) a[11] = cType; + if( iFirst<=125 && iLast>=125 ) a[12] = cType; + if( iFirst<=126 && iLast>=126 ) a[13] = cType; + if( iFirst<=127 && iLast>=127 ) a[14] = cType; + } + } + return SQLITE_OK; +} +#else +# define unixPosixAdvisoryLocks(A,B) SQLITE_ERROR_UNABLE +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + /* ** On some systems, calls to fchown() will trigger a message in a security ** log if they come from non-root processes. So avoid calling fchown() if @@ -39602,9 +40073,8 @@ static int robust_open(const char *z, int f, mode_t m){ /* ** Helper functions to obtain and relinquish the global mutex. The -** global mutex is used to protect the unixInodeInfo and -** vxworksFileId objects used by this file, all of which may be -** shared by multiple threads. +** global mutex is used to protect the unixInodeInfo objects used by +** this file, all of which may be shared by multiple threads. ** ** Function unixMutexHeld() is used to assert() that the global mutex ** is held when required. This function is only used as part of assert() @@ -39806,6 +40276,7 @@ struct vxworksFileId { ** variable: */ static struct vxworksFileId *vxworksFileList = 0; +static sqlite3_mutex *vxworksMutex = 0; /* ** Simplify a filename into its canonical form @@ -39871,14 +40342,14 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ ** If found, increment the reference count and return a pointer to ** the existing file ID. */ - unixEnterMutex(); + sqlite3_mutex_enter(vxworksMutex); for(pCandidate=vxworksFileList; pCandidate; pCandidate=pCandidate->pNext){ if( pCandidate->nName==n && memcmp(pCandidate->zCanonicalName, pNew->zCanonicalName, n)==0 ){ sqlite3_free(pNew); pCandidate->nRef++; - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); return pCandidate; } } @@ -39888,7 +40359,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ pNew->nName = n; pNew->pNext = vxworksFileList; vxworksFileList = pNew; - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); return pNew; } @@ -39897,7 +40368,7 @@ static struct vxworksFileId *vxworksFindFileId(const char *zAbsoluteName){ ** the object when the reference count reaches zero. */ static void vxworksReleaseFileId(struct vxworksFileId *pId){ - unixEnterMutex(); + sqlite3_mutex_enter(vxworksMutex); assert( pId->nRef>0 ); pId->nRef--; if( pId->nRef==0 ){ @@ -39907,7 +40378,7 @@ static void vxworksReleaseFileId(struct vxworksFileId *pId){ *pp = pId->pNext; sqlite3_free(pId); } - unixLeaveMutex(); + sqlite3_mutex_leave(vxworksMutex); } #endif /* OS_VXWORKS */ /*************** End of Unique File ID Utility Used By VxWorks **************** @@ -40295,6 +40766,10 @@ static int findInodeInfo( storeLastErrno(pFile, errno); return SQLITE_IOERR; } + if( fsync(fd) ){ + storeLastErrno(pFile, errno); + return SQLITE_IOERR_FSYNC; + } rc = osFstat(fd, &statbuf); if( rc!=0 ){ storeLastErrno(pFile, errno); @@ -40464,18 +40939,42 @@ static int osSetPosixAdvisoryLock( struct flock *pLock, /* The description of the lock */ unixFile *pFile /* Structure holding timeout value */ ){ - int tm = pFile->iBusyTimeout; - int rc = osFcntl(h,F_SETLK,pLock); - while( rc<0 && tm>0 ){ - /* On systems that support some kind of blocking file lock with a timeout, - ** make appropriate changes here to invoke that blocking file lock. On - ** generic posix, however, there is no such API. So we simply try the - ** lock once every millisecond until either the timeout expires, or until - ** the lock is obtained. */ - unixSleep(0,1000); + int rc = 0; + + if( pFile->iBusyTimeout==0 ){ + /* unixFile->iBusyTimeout is set to 0. In this case, attempt a + ** non-blocking lock. */ + rc = osFcntl(h,F_SETLK,pLock); + }else{ + /* unixFile->iBusyTimeout is set to greater than zero. In this case, + ** attempt a blocking-lock with a unixFile->iBusyTimeout ms timeout. + ** + ** On systems that support some kind of blocking file lock operation, + ** this block should be replaced by code to attempt a blocking lock + ** with a timeout of unixFile->iBusyTimeout ms. The code below is + ** placeholder code. If SQLITE_TEST is defined, the placeholder code + ** retries the lock once every 1ms until it succeeds or the timeout + ** is reached. Or, if SQLITE_TEST is not defined, the placeholder + ** code attempts a non-blocking lock and sets unixFile->iBusyTimeout + ** to 0. This causes the caller to return SQLITE_BUSY, instead of + ** SQLITE_BUSY_TIMEOUT to SQLite - as required by a VFS that does not + ** support blocking locks. + */ +#ifdef SQLITE_TEST + int tm = pFile->iBusyTimeout; + while( tm>0 ){ + rc = osFcntl(h,F_SETLK,pLock); + if( rc==0 ) break; + unixSleep(0,1000); + tm--; + } +#else rc = osFcntl(h,F_SETLK,pLock); - tm--; + pFile->iBusyTimeout = 0; +#endif + /* End of code to replace with real blocking-locks code. */ } + return rc; } #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ @@ -40533,6 +41032,13 @@ static int unixFileLock(unixFile *pFile, struct flock *pLock){ return rc; } +#if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) +/* Forward reference */ +static int unixIsSharingShmNode(unixFile*); +#else +#define unixIsSharingShmNode(pFile) (0) +#endif + /* ** Lock the file with the lock specified by parameter eFileLock - one ** of the following: @@ -40721,7 +41227,9 @@ static int unixLock(sqlite3_file *id, int eFileLock){ pInode->nLock++; pInode->nShared = 1; } - }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ + }else if( (eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1) + || unixIsSharingShmNode(pFile) + ){ /* We are trying for an exclusive lock but another thread in this ** same process is still holding a shared lock. */ rc = SQLITE_BUSY; @@ -42816,6 +43324,10 @@ static int unixGetTempname(int nBuf, char *zBuf); #if !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) static int unixFcntlExternalReader(unixFile*, int*); #endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + static void unixDescribeShm(sqlite3_str*,unixShm*); +#endif + /* ** Information and control of an open file handle. @@ -42958,6 +43470,66 @@ static int unixFileControl(sqlite3_file *id, int op, void *pArg){ return SQLITE_OK; #endif } + +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + case SQLITE_FCNTL_FILESTAT: { + sqlite3_str *pStr = (sqlite3_str*)pArg; + char aLck[16]; + unixInodeInfo *pInode; + static const char *azLock[] = { "SHARED", "RESERVED", + "PENDING", "EXCLUSIVE" }; + sqlite3_str_appendf(pStr, "{\"h\":%d", pFile->h); + sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName); + if( pFile->eFileLock ){ + sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"", + azLock[pFile->eFileLock-1]); + if( unixPosixAdvisoryLocks(pFile->h, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + } + unixEnterMutex(); + if( pFile->pShm ){ + sqlite3_str_appendall(pStr, ",\"shm\":"); + unixDescribeShm(pStr, pFile->pShm); + } +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->mmapSize ){ + sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize); + sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut); + } +#endif + if( (pInode = pFile->pInode)!=0 ){ + sqlite3_str_appendf(pStr, ",\"inode\":{\"nRef\":%d",pInode->nRef); + sqlite3_mutex_enter(pInode->pLockMutex); + sqlite3_str_appendf(pStr, ",\"nShared\":%d", pInode->nShared); + if( pInode->eFileLock ){ + sqlite3_str_appendf(pStr, ",\"eFileLock\":\"%s\"", + azLock[pInode->eFileLock-1]); + } + if( pInode->pUnused ){ + char cSep = '['; + UnixUnusedFd *pUFd = pFile->pInode->pUnused; + sqlite3_str_appendall(pStr, ",\"unusedFd\":"); + while( pUFd ){ + sqlite3_str_appendf(pStr, "%c{\"fd\":%d,\"flags\":%d", + cSep, pUFd->fd, pUFd->flags); + cSep = ','; + if( unixPosixAdvisoryLocks(pUFd->fd, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + sqlite3_str_append(pStr, "}", 1); + pUFd = pUFd->pNext; + } + sqlite3_str_append(pStr, "]", 1); + } + sqlite3_mutex_leave(pInode->pLockMutex); + sqlite3_str_append(pStr, "}", 1); + } + unixLeaveMutex(); + sqlite3_str_append(pStr, "}", 1); + return SQLITE_OK; + } +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ } return SQLITE_NOTFOUND; } @@ -43224,6 +43796,26 @@ struct unixShm { #define UNIX_SHM_BASE ((22+SQLITE_SHM_NLOCK)*4) /* first lock byte */ #define UNIX_SHM_DMS (UNIX_SHM_BASE+SQLITE_SHM_NLOCK) /* deadman switch */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Describe the pShm object using JSON. Used for diagnostics only. +*/ +static void unixDescribeShm(sqlite3_str *pStr, unixShm *pShm){ + unixShmNode *pNode = pShm->pShmNode; + char aLck[16]; + sqlite3_str_appendf(pStr, "{\"h\":%d", pNode->hShm); + assert( unixMutexHeld() ); + sqlite3_str_appendf(pStr, ",\"nRef\":%d", pNode->nRef); + sqlite3_str_appendf(pStr, ",\"id\":%d", pShm->id); + sqlite3_str_appendf(pStr, ",\"sharedMask\":%d", pShm->sharedMask); + sqlite3_str_appendf(pStr, ",\"exclMask\":%d", pShm->exclMask); + if( unixPosixAdvisoryLocks(pNode->hShm, aLck)==SQLITE_OK ){ + sqlite3_str_appendf(pStr, ",\"pal\":\"%s\"", aLck); + } + sqlite3_str_append(pStr, "}", 1); +} +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + /* ** Use F_GETLK to check whether or not there are any readers with open ** wal-mode transactions in other processes on database file pFile. If @@ -43257,6 +43849,49 @@ static int unixFcntlExternalReader(unixFile *pFile, int *piOut){ return rc; } +/* +** If pFile has a -shm file open and it is sharing that file with some +** other connection, either in the same process or in a separate process, +** then return true. Return false if either pFile does not have a -shm +** file open or if it is the only connection to that -shm file across the +** entire system. +** +** This routine is not required for correct operation. It can always return +** false and SQLite will continue to operate according to spec. However, +** when this routine does its job, it adds extra robustness in cases +** where database file locks have been erroneously deleted in a WAL-mode +** database by doing close(open(DATABASE_PATHNAME)) or similar. +** +** With false negatives, SQLite still operates to spec, though with less +** robustness. With false positives, the last database connection on a +** WAL-mode database will fail to unlink the -wal and -shm files, which +** is annoying but harmless. False positives will also prevent a database +** connection from running "PRAGMA journal_mode=DELETE" in order to take +** the database out of WAL mode, which is perhaps more serious, but is +** still not a disaster. +*/ +static int unixIsSharingShmNode(unixFile *pFile){ + int rc; + unixShmNode *pShmNode; + if( pFile->pShm==0 ) return 0; + if( pFile->ctrlFlags & UNIXFILE_EXCL ) return 0; + pShmNode = pFile->pShm->pShmNode; + rc = 1; + unixEnterMutex(); + if( ALWAYS(pShmNode->nRef==1) ){ + struct flock lock; + lock.l_whence = SEEK_SET; + lock.l_start = UNIX_SHM_DMS; + lock.l_len = 1; + lock.l_type = F_WRLCK; + osFcntl(pShmNode->hShm, F_GETLK, &lock); + if( lock.l_type==F_UNLCK ){ + rc = 0; + } + } + unixLeaveMutex(); + return rc; +} /* ** Apply posix advisory locks for all bytes from ofst through ofst+n-1. @@ -43302,7 +43937,8 @@ static int unixShmSystemLock( /* Locks are within range */ assert( n>=1 && n<=SQLITE_SHM_NLOCK ); - assert( ofst>=UNIX_SHM_BASE && ofst<=(UNIX_SHM_DMS+SQLITE_SHM_NLOCK) ); + assert( ofst>=UNIX_SHM_BASE && ofst<=UNIX_SHM_DMS ); + assert( ofst+n-1<=UNIX_SHM_DMS ); if( pShmNode->hShm>=0 ){ int res; @@ -43834,7 +44470,7 @@ static int assertLockingArrayOk(unixShmNode *pShmNode){ return (memcmp(pShmNode->aLock, aLock, sizeof(aLock))==0); #endif } -#endif +#endif /* !defined(SQLITE_WASI) && !defined(SQLITE_OMIT_WAL) */ /* ** Change the lock state for a shared-memory segment. @@ -44796,10 +45432,17 @@ static int fillInUnixFile( storeLastErrno(pNew, 0); #if OS_VXWORKS if( rc!=SQLITE_OK ){ - if( h>=0 ) robust_close(pNew, h, __LINE__); - h = -1; - osUnlink(zFilename); - pNew->ctrlFlags |= UNIXFILE_DELETE; + if( h>=0 ){ + robust_close(pNew, h, __LINE__); + h = -1; + } + if( pNew->ctrlFlags & UNIXFILE_DELETE ){ + osUnlink(zFilename); + } + if( pNew->pId ){ + vxworksReleaseFileId(pNew->pId); + pNew->pId = 0; + } } #endif if( rc!=SQLITE_OK ){ @@ -44843,6 +45486,9 @@ static const char *unixTempFileDir(void){ while(1){ if( zDir!=0 +#if OS_VXWORKS + && zDir[0]=='/' +#endif && osStat(zDir, &buf)==0 && S_ISDIR(buf.st_mode) && osAccess(zDir, 03)==0 @@ -45157,6 +45803,12 @@ static int unixOpen( || eType==SQLITE_OPEN_TRANSIENT_DB || eType==SQLITE_OPEN_WAL ); +#if OS_VXWORKS + /* The file-ID mechanism used in Vxworks requires that all pathnames + ** provided to unixOpen must be absolute pathnames. */ + if( zPath!=0 && zPath[0]!='/' ){ return SQLITE_CANTOPEN; } +#endif + /* Detect a pid change and reset the PRNG. There is a race condition ** here such that two or more threads all trying to open databases at ** the same instant might all reset the PRNG. But multiple resets @@ -45357,8 +46009,11 @@ static int unixOpen( } #endif - assert( zPath==0 || zPath[0]=='/' - || eType==SQLITE_OPEN_SUPER_JOURNAL || eType==SQLITE_OPEN_MAIN_JOURNAL + assert( zPath==0 + || zPath[0]=='/' + || eType==SQLITE_OPEN_SUPER_JOURNAL + || eType==SQLITE_OPEN_MAIN_JOURNAL + || eType==SQLITE_OPEN_TEMP_JOURNAL ); rc = fillInUnixFile(pVfs, fd, pFile, zPath, ctrlFlags); @@ -47087,6 +47742,9 @@ SQLITE_API int sqlite3_os_init(void){ sqlite3KvvfsInit(); #endif unixBigLock = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS1); +#if OS_VXWORKS + vxworksMutex = sqlite3MutexAlloc(SQLITE_MUTEX_STATIC_VFS2); +#endif #ifndef SQLITE_OMIT_WAL /* Validate lock assumptions */ @@ -47121,6 +47779,9 @@ SQLITE_API int sqlite3_os_init(void){ */ SQLITE_API int sqlite3_os_end(void){ unixBigLock = 0; +#if OS_VXWORKS + vxworksMutex = 0; +#endif return SQLITE_OK; } @@ -49793,6 +50454,7 @@ static BOOL winLockFile( #endif } +#ifndef SQLITE_OMIT_WAL /* ** Lock a region of nByte bytes starting at offset offset of file hFile. ** Take an EXCLUSIVE lock if parameter bExclusive is true, or a SHARED lock @@ -49875,6 +50537,7 @@ static int winHandleLockTimeout( } return rc; } +#endif /* #ifndef SQLITE_OMIT_WAL */ /* ** Unlock a file region. @@ -49909,6 +50572,7 @@ static BOOL winUnlockFile( #endif } +#ifndef SQLITE_OMIT_WAL /* ** Remove an nByte lock starting at offset iOff from HANDLE h. */ @@ -49916,6 +50580,7 @@ static int winHandleUnlock(HANDLE h, int iOff, int nByte){ BOOL ret = winUnlockFile(&h, iOff, 0, nByte, 0); return (ret ? SQLITE_OK : SQLITE_IOERR_UNLOCK); } +#endif /***************************************************************************** ** The next group of routines implement the I/O methods specified @@ -50253,6 +50918,7 @@ static int winWrite( return SQLITE_OK; } +#ifndef SQLITE_OMIT_WAL /* ** Truncate the file opened by handle h to nByte bytes in size. */ @@ -50306,6 +50972,7 @@ static void winHandleClose(HANDLE h){ osCloseHandle(h); } } +#endif /* #ifndef SQLITE_OMIT_WAL */ /* ** Truncate an open file to a specified size @@ -51083,6 +51750,28 @@ static int winFileControl(sqlite3_file *id, int op, void *pArg){ } #endif /* SQLITE_ENABLE_SETLK_TIMEOUT */ +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + case SQLITE_FCNTL_FILESTAT: { + sqlite3_str *pStr = (sqlite3_str*)pArg; + sqlite3_str_appendf(pStr, "{\"h\":%llu", (sqlite3_uint64)pFile->h); + sqlite3_str_appendf(pStr, ",\"vfs\":\"%s\"", pFile->pVfs->zName); + if( pFile->locktype ){ + static const char *azLock[] = { "SHARED", "RESERVED", + "PENDING", "EXCLUSIVE" }; + sqlite3_str_appendf(pStr, ",\"locktype\":\"%s\"", + azLock[pFile->locktype-1]); + } +#if SQLITE_MAX_MMAP_SIZE>0 + if( pFile->mmapSize ){ + sqlite3_str_appendf(pStr, ",\"mmapSize\":%lld", pFile->mmapSize); + sqlite3_str_appendf(pStr, ",\"nFetchOut\":%d", pFile->nFetchOut); + } +#endif + sqlite3_str_append(pStr, "}", 1); + return SQLITE_OK; + } +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + } OSTRACE(("FCNTL file=%p, rc=SQLITE_NOTFOUND\n", pFile->h)); return SQLITE_NOTFOUND; @@ -51120,6 +51809,103 @@ static int winDeviceCharacteristics(sqlite3_file *id){ */ static SYSTEM_INFO winSysInfo; +/* +** Convert a UTF-8 filename into whatever form the underlying +** operating system wants filenames in. Space to hold the result +** is obtained from malloc and must be freed by the calling +** function +** +** On Cygwin, 3 possible input forms are accepted: +** - If the filename starts with "<drive>:/" or "<drive>:\", +** it is converted to UTF-16 as-is. +** - If the filename contains '/', it is assumed to be a +** Cygwin absolute path, it is converted to a win32 +** absolute path in UTF-16. +** - Otherwise it must be a filename only, the win32 filename +** is returned in UTF-16. +** Note: If the function cygwin_conv_path() fails, only +** UTF-8 -> UTF-16 conversion will be done. This can only +** happen when the file path >32k, in which case winUtf8ToUnicode() +** will fail too. +*/ +static void *winConvertFromUtf8Filename(const char *zFilename){ + void *zConverted = 0; + if( osIsNT() ){ +#ifdef __CYGWIN__ + int nChar; + LPWSTR zWideFilename; + + if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename) + && winIsDirSep(zFilename[2])) ){ + i64 nByte; + int convertflag = CCP_POSIX_TO_WIN_W; + if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE; + nByte = (i64)osCygwin_conv_path(convertflag, + zFilename, 0, 0); + if( nByte>0 ){ + zConverted = sqlite3MallocZero(12+(u64)nByte); + if ( zConverted==0 ){ + return zConverted; + } + zWideFilename = zConverted; + /* Filenames should be prefixed, except when converted + * full path already starts with "\\?\". */ + if( osCygwin_conv_path(convertflag, zFilename, + zWideFilename+4, nByte)==0 ){ + if( (convertflag&CCP_RELATIVE) ){ + memmove(zWideFilename, zWideFilename+4, nByte); + }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){ + memcpy(zWideFilename, L"\\\\?\\", 8); + }else if( zWideFilename[6]!='?' ){ + memmove(zWideFilename+6, zWideFilename+4, nByte); + memcpy(zWideFilename, L"\\\\?\\UNC", 14); + }else{ + memmove(zWideFilename, zWideFilename+4, nByte); + } + return zConverted; + } + sqlite3_free(zConverted); + } + } + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); + if( nChar==0 ){ + return 0; + } + zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 ); + if( zWideFilename==0 ){ + return 0; + } + nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, + zWideFilename, nChar); + if( nChar==0 ){ + sqlite3_free(zWideFilename); + zWideFilename = 0; + }else if( nChar>MAX_PATH + && winIsDriveLetterAndColon(zFilename) + && winIsDirSep(zFilename[2]) ){ + memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR)); + zWideFilename[2] = '\\'; + memcpy(zWideFilename, L"\\\\?\\", 8); + }else if( nChar>MAX_PATH + && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1]) + && zFilename[2] != '?' ){ + memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR)); + memcpy(zWideFilename, L"\\\\?\\UNC", 14); + } + zConverted = zWideFilename; +#else + zConverted = winUtf8ToUnicode(zFilename); +#endif /* __CYGWIN__ */ + } +#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32) + else{ + zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); + } +#endif + /* caller will handle out of memory */ + return zConverted; +} + #ifndef SQLITE_OMIT_WAL /* @@ -51156,29 +51942,35 @@ static int winShmMutexHeld(void) { ** log-summary is opened only once per process. ** ** winShmMutexHeld() must be true when creating or destroying -** this object or while reading or writing the following fields: +** this object, or while editing the global linked list that starts +** at winShmNodeList. ** -** nRef -** pNext +** When reading or writing the linked list starting at winShmNode.pWinShmList, +** pShmNode->mutex must be held. ** -** The following fields are read-only after the object is created: +** The following fields are constant after the object is created: ** ** zFilename +** hSharedShm +** mutex +** bUseSharedLockHandle ** -** Either winShmNode.mutex must be held or winShmNode.nRef==0 and +** Either winShmNode.mutex must be held or winShmNode.pWinShmList==0 and ** winShmMutexHeld() is true when reading or writing any other field ** in this structure. ** -** File-handle hSharedShm is used to (a) take the DMS lock, (b) truncate -** the *-shm file if the DMS-locking protocol demands it, and (c) map -** regions of the *-shm file into memory using MapViewOfFile() or -** similar. Other locks are taken by individual clients using the -** winShm.hShm handles. +** File-handle hSharedShm is always used to (a) take the DMS lock, (b) +** truncate the *-shm file if the DMS-locking protocol demands it, and +** (c) map regions of the *-shm file into memory using MapViewOfFile() +** or similar. If bUseSharedLockHandle is true, then other locks are also +** taken on hSharedShm. Or, if bUseSharedLockHandle is false, then other +** locks are taken using each connection's winShm.hShm handles. */ struct winShmNode { sqlite3_mutex *mutex; /* Mutex to access this object */ char *zFilename; /* Name of the file */ HANDLE hSharedShm; /* File handle open on zFilename */ + int bUseSharedLockHandle; /* True to use hSharedShm for everything */ int isUnlocked; /* DMS lock has not yet been obtained */ int isReadonly; /* True if read-only */ @@ -51191,7 +51983,8 @@ struct winShmNode { } *aRegion; DWORD lastErrno; /* The Windows errno from the last I/O error */ - int nRef; /* Number of winShm objects pointing to this */ + winShm *pWinShmList; /* List of winShm objects with ptrs to this */ + winShmNode *pNext; /* Next in list of all winShmNode objects */ #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) u8 nextShmId; /* Next available winShm.id value */ @@ -51219,6 +52012,7 @@ struct winShm { #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) u8 id; /* Id of this connection with its winShmNode */ #endif + winShm *pWinShmNext; /* Next winShm object on same winShmNode */ }; /* @@ -51232,7 +52026,7 @@ static int winOpen(sqlite3_vfs*,const char*,sqlite3_file*,int,int*); static int winDelete(sqlite3_vfs *,const char*,int); /* -** Purge the winShmNodeList list of all entries with winShmNode.nRef==0. +** Purge the winShmNodeList list of all entries with winShmNode.pWinShmList==0. ** ** This is not a VFS shared-memory method; it is a utility function called ** by VFS shared-memory methods. @@ -51245,7 +52039,7 @@ static void winShmPurge(sqlite3_vfs *pVfs, int deleteFlag){ osGetCurrentProcessId(), deleteFlag)); pp = &winShmNodeList; while( (p = *pp)!=0 ){ - if( p->nRef==0 ){ + if( p->pWinShmList==0 ){ int i; if( p->mutex ){ sqlite3_mutex_free(p->mutex); } for(i=0; i<p->nRegion; i++){ @@ -51315,103 +52109,6 @@ static int winLockSharedMemory(winShmNode *pShmNode, DWORD nMs){ /* -** Convert a UTF-8 filename into whatever form the underlying -** operating system wants filenames in. Space to hold the result -** is obtained from malloc and must be freed by the calling -** function -** -** On Cygwin, 3 possible input forms are accepted: -** - If the filename starts with "<drive>:/" or "<drive>:\", -** it is converted to UTF-16 as-is. -** - If the filename contains '/', it is assumed to be a -** Cygwin absolute path, it is converted to a win32 -** absolute path in UTF-16. -** - Otherwise it must be a filename only, the win32 filename -** is returned in UTF-16. -** Note: If the function cygwin_conv_path() fails, only -** UTF-8 -> UTF-16 conversion will be done. This can only -** happen when the file path >32k, in which case winUtf8ToUnicode() -** will fail too. -*/ -static void *winConvertFromUtf8Filename(const char *zFilename){ - void *zConverted = 0; - if( osIsNT() ){ -#ifdef __CYGWIN__ - int nChar; - LPWSTR zWideFilename; - - if( osCygwin_conv_path && !(winIsDriveLetterAndColon(zFilename) - && winIsDirSep(zFilename[2])) ){ - i64 nByte; - int convertflag = CCP_POSIX_TO_WIN_W; - if( !strchr(zFilename, '/') ) convertflag |= CCP_RELATIVE; - nByte = (i64)osCygwin_conv_path(convertflag, - zFilename, 0, 0); - if( nByte>0 ){ - zConverted = sqlite3MallocZero(12+(u64)nByte); - if ( zConverted==0 ){ - return zConverted; - } - zWideFilename = zConverted; - /* Filenames should be prefixed, except when converted - * full path already starts with "\\?\". */ - if( osCygwin_conv_path(convertflag, zFilename, - zWideFilename+4, nByte)==0 ){ - if( (convertflag&CCP_RELATIVE) ){ - memmove(zWideFilename, zWideFilename+4, nByte); - }else if( memcmp(zWideFilename+4, L"\\\\", 4) ){ - memcpy(zWideFilename, L"\\\\?\\", 8); - }else if( zWideFilename[6]!='?' ){ - memmove(zWideFilename+6, zWideFilename+4, nByte); - memcpy(zWideFilename, L"\\\\?\\UNC", 14); - }else{ - memmove(zWideFilename, zWideFilename+4, nByte); - } - return zConverted; - } - sqlite3_free(zConverted); - } - } - nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, NULL, 0); - if( nChar==0 ){ - return 0; - } - zWideFilename = sqlite3MallocZero( nChar*sizeof(WCHAR)+12 ); - if( zWideFilename==0 ){ - return 0; - } - nChar = osMultiByteToWideChar(CP_UTF8, 0, zFilename, -1, - zWideFilename, nChar); - if( nChar==0 ){ - sqlite3_free(zWideFilename); - zWideFilename = 0; - }else if( nChar>MAX_PATH - && winIsDriveLetterAndColon(zFilename) - && winIsDirSep(zFilename[2]) ){ - memmove(zWideFilename+4, zWideFilename, nChar*sizeof(WCHAR)); - zWideFilename[2] = '\\'; - memcpy(zWideFilename, L"\\\\?\\", 8); - }else if( nChar>MAX_PATH - && winIsDirSep(zFilename[0]) && winIsDirSep(zFilename[1]) - && zFilename[2] != '?' ){ - memmove(zWideFilename+6, zWideFilename, nChar*sizeof(WCHAR)); - memcpy(zWideFilename, L"\\\\?\\UNC", 14); - } - zConverted = zWideFilename; -#else - zConverted = winUtf8ToUnicode(zFilename); -#endif /* __CYGWIN__ */ - } -#if defined(SQLITE_WIN32_HAS_ANSI) && defined(_WIN32) - else{ - zConverted = winUtf8ToMbcs(zFilename, osAreFileApisANSI()); - } -#endif - /* caller will handle out of memory */ - return zConverted; -} - -/* ** This function is used to open a handle on a *-shm file. ** ** If SQLITE_ENABLE_SETLK_TIMEOUT is defined at build time, then the file @@ -51506,6 +52203,60 @@ static int winHandleOpen( return rc; } +/* +** Close pDbFd's connection to shared-memory. Delete the underlying +** *-shm file if deleteFlag is true. +*/ +static int winCloseSharedMemory(winFile *pDbFd, int deleteFlag){ + winShm *p; /* The connection to be closed */ + winShm **pp; /* Iterator for pShmNode->pWinShmList */ + winShmNode *pShmNode; /* The underlying shared-memory file */ + + p = pDbFd->pShm; + if( p==0 ) return SQLITE_OK; + if( p->hShm!=INVALID_HANDLE_VALUE ){ + osCloseHandle(p->hShm); + } + + winShmEnterMutex(); + pShmNode = p->pShmNode; + + /* Remove this connection from the winShmNode.pWinShmList list */ + sqlite3_mutex_enter(pShmNode->mutex); + for(pp=&pShmNode->pWinShmList; *pp!=p; pp=&(*pp)->pWinShmNext){} + *pp = p->pWinShmNext; + sqlite3_mutex_leave(pShmNode->mutex); + + winShmPurge(pDbFd->pVfs, deleteFlag); + winShmLeaveMutex(); + + /* Free the connection p */ + sqlite3_free(p); + pDbFd->pShm = 0; + return SQLITE_OK; +} + +/* +** testfixture builds may set this global variable to true via a +** Tcl interface. This forces the VFS to use the locking normally +** only used for UNC paths for all files. +*/ +#ifdef SQLITE_TEST +SQLITE_API int sqlite3_win_test_unc_locking = 0; +#else +# define sqlite3_win_test_unc_locking 0 +#endif + +/* +** Return true if the string passed as the only argument is likely +** to be a UNC path. In other words, if it starts with "\\". +*/ +static int winIsUNCPath(const char *zFile){ + if( zFile[0]=='\\' && zFile[1]=='\\' ){ + return 1; + } + return sqlite3_win_test_unc_locking; +} /* ** Open the shared-memory area associated with database file pDbFd. @@ -51532,15 +52283,10 @@ static int winOpenSharedMemory(winFile *pDbFd){ pNew->zFilename = (char*)&pNew[1]; pNew->hSharedShm = INVALID_HANDLE_VALUE; pNew->isUnlocked = 1; + pNew->bUseSharedLockHandle = winIsUNCPath(pDbFd->zPath); sqlite3_snprintf(nName+15, pNew->zFilename, "%s-shm", pDbFd->zPath); sqlite3FileSuffix3(pDbFd->zPath, pNew->zFilename); - /* Open a file-handle on the *-shm file for this connection. This file-handle - ** is only used for locking. The mapping of the *-shm file is created using - ** the shared file handle in winShmNode.hSharedShm. */ - p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); - rc = winHandleOpen(pNew->zFilename, &p->bReadonly, &p->hShm); - /* Look to see if there is an existing winShmNode that can be used. ** If no matching winShmNode currently exists, then create a new one. */ winShmEnterMutex(); @@ -51561,7 +52307,7 @@ static int winOpenSharedMemory(winFile *pDbFd){ /* Open a file-handle to use for mappings, and for the DMS lock. */ if( rc==SQLITE_OK ){ HANDLE h = INVALID_HANDLE_VALUE; - pShmNode->isReadonly = p->bReadonly; + pShmNode->isReadonly = sqlite3_uri_boolean(pDbFd->zPath,"readonly_shm",0); rc = winHandleOpen(pNew->zFilename, &pShmNode->isReadonly, &h); pShmNode->hSharedShm = h; } @@ -51583,20 +52329,35 @@ static int winOpenSharedMemory(winFile *pDbFd){ /* If no error has occurred, link the winShm object to the winShmNode and ** the winShm to pDbFd. */ if( rc==SQLITE_OK ){ + sqlite3_mutex_enter(pShmNode->mutex); p->pShmNode = pShmNode; - pShmNode->nRef++; + p->pWinShmNext = pShmNode->pWinShmList; + pShmNode->pWinShmList = p; #if defined(SQLITE_DEBUG) || defined(SQLITE_HAVE_OS_TRACE) p->id = pShmNode->nextShmId++; #endif pDbFd->pShm = p; + sqlite3_mutex_leave(pShmNode->mutex); }else if( p ){ - winHandleClose(p->hShm); sqlite3_free(p); } assert( rc!=SQLITE_OK || pShmNode->isUnlocked==0 || pShmNode->nRegion==0 ); winShmLeaveMutex(); sqlite3_free(pNew); + + /* Open a file-handle on the *-shm file for this connection. This file-handle + ** is only used for locking. The mapping of the *-shm file is created using + ** the shared file handle in winShmNode.hSharedShm. */ + if( rc==SQLITE_OK && pShmNode->bUseSharedLockHandle==0 ){ + p->bReadonly = sqlite3_uri_boolean(pDbFd->zPath, "readonly_shm", 0); + rc = winHandleOpen(pShmNode->zFilename, &p->bReadonly, &p->hShm); + if( rc!=SQLITE_OK ){ + assert( p->hShm==INVALID_HANDLE_VALUE ); + winCloseSharedMemory(pDbFd, 0); + } + } + return rc; } @@ -51608,33 +52369,7 @@ static int winShmUnmap( sqlite3_file *fd, /* Database holding shared memory */ int deleteFlag /* Delete after closing if true */ ){ - winFile *pDbFd; /* Database holding shared-memory */ - winShm *p; /* The connection to be closed */ - winShmNode *pShmNode; /* The underlying shared-memory file */ - - pDbFd = (winFile*)fd; - p = pDbFd->pShm; - if( p==0 ) return SQLITE_OK; - if( p->hShm!=INVALID_HANDLE_VALUE ){ - osCloseHandle(p->hShm); - } - - pShmNode = p->pShmNode; - winShmEnterMutex(); - - /* If pShmNode->nRef has reached 0, then close the underlying - ** shared-memory file, too. */ - assert( pShmNode->nRef>0 ); - pShmNode->nRef--; - if( pShmNode->nRef==0 ){ - winShmPurge(pDbFd->pVfs, deleteFlag); - } - winShmLeaveMutex(); - - /* Free the connection p */ - sqlite3_free(p); - pDbFd->pShm = 0; - return SQLITE_OK; + return winCloseSharedMemory((winFile*)fd, deleteFlag); } /* @@ -51703,6 +52438,7 @@ static int winShmLock( || (flags==(SQLITE_SHM_SHARED|SQLITE_SHM_LOCK) && 0==(p->sharedMask & mask)) || (flags==(SQLITE_SHM_EXCLUSIVE|SQLITE_SHM_LOCK)) ){ + HANDLE h = p->hShm; if( flags & SQLITE_SHM_UNLOCK ){ /* Case (a) - unlock. */ @@ -51711,7 +52447,27 @@ static int winShmLock( assert( !(flags & SQLITE_SHM_EXCLUSIVE) || (p->exclMask & mask)==mask ); assert( !(flags & SQLITE_SHM_SHARED) || (p->sharedMask & mask)==mask ); - rc = winHandleUnlock(p->hShm, ofst+WIN_SHM_BASE, n); + assert( !(flags & SQLITE_SHM_SHARED) || n==1 ); + if( pShmNode->bUseSharedLockHandle ){ + h = pShmNode->hSharedShm; + if( flags & SQLITE_SHM_SHARED ){ + winShm *pShm; + sqlite3_mutex_enter(pShmNode->mutex); + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ + if( pShm!=p && (pShm->sharedMask & mask) ){ + /* Another connection within this process is also holding this + ** SHARED lock. So do not actually release the OS lock. */ + h = INVALID_HANDLE_VALUE; + break; + } + } + sqlite3_mutex_leave(pShmNode->mutex); + } + } + + if( h!=INVALID_HANDLE_VALUE ){ + rc = winHandleUnlock(h, ofst+WIN_SHM_BASE, n); + } /* If successful, also clear the bits in sharedMask/exclMask */ if( rc==SQLITE_OK ){ @@ -51721,7 +52477,32 @@ static int winShmLock( }else{ int bExcl = ((flags & SQLITE_SHM_EXCLUSIVE) ? 1 : 0); DWORD nMs = winFileBusyTimeout(pDbFd); - rc = winHandleLockTimeout(p->hShm, ofst+WIN_SHM_BASE, n, bExcl, nMs); + + if( pShmNode->bUseSharedLockHandle ){ + winShm *pShm; + h = pShmNode->hSharedShm; + sqlite3_mutex_enter(pShmNode->mutex); + for(pShm=pShmNode->pWinShmList; pShm; pShm=pShm->pWinShmNext){ + if( bExcl ){ + if( (pShm->sharedMask|pShm->exclMask) & mask ){ + rc = SQLITE_BUSY; + h = INVALID_HANDLE_VALUE; + } + }else{ + if( pShm->sharedMask & mask ){ + h = INVALID_HANDLE_VALUE; + }else if( pShm->exclMask & mask ){ + rc = SQLITE_BUSY; + h = INVALID_HANDLE_VALUE; + } + } + } + sqlite3_mutex_leave(pShmNode->mutex); + } + + if( h!=INVALID_HANDLE_VALUE ){ + rc = winHandleLockTimeout(h, ofst+WIN_SHM_BASE, n, bExcl, nMs); + } if( rc==SQLITE_OK ){ if( bExcl ){ p->exclMask = (p->exclMask | mask); @@ -54860,6 +55641,7 @@ struct Bitvec { } u; }; + /* ** Create a new bitmap object able to handle bits between 0 and iSize, ** inclusive. Return a pointer to the new object. Return NULL if @@ -55048,6 +55830,52 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ return p->iSize; } +#ifdef SQLITE_DEBUG +/* +** Show the content of a Bitvec option and its children. Indent +** everything by n spaces. Add x to each bitvec value. +** +** From a debugger such as gdb, one can type: +** +** call sqlite3ShowBitvec(p) +** +** For some Bitvec p and see a recursive view of the Bitvec's content. +*/ +static void showBitvec(Bitvec *p, int n, unsigned x){ + int i; + if( p==0 ){ + printf("NULL\n"); + return; + } + printf("Bitvec 0x%p iSize=%u", p, p->iSize); + if( p->iSize<=BITVEC_NBIT ){ + printf(" bitmap\n"); + printf("%*s bits:", n, ""); + for(i=1; i<=BITVEC_NBIT; i++){ + if( sqlite3BitvecTest(p,i) ) printf(" %u", x+(unsigned)i); + } + printf("\n"); + }else if( p->iDivisor==0 ){ + printf(" hash with %u entries\n", p->nSet); + printf("%*s bits:", n, ""); + for(i=0; i<BITVEC_NINT; i++){ + if( p->u.aHash[i] ) printf(" %u", x+(unsigned)p->u.aHash[i]); + } + printf("\n"); + }else{ + printf(" sub-bitvec with iDivisor=%u\n", p->iDivisor); + for(i=0; i<BITVEC_NPTR; i++){ + if( p->u.apSub[i]==0 ) continue; + printf("%*s apSub[%d]=", n, "", i); + showBitvec(p->u.apSub[i], n+4, i*p->iDivisor); + } + } +} +SQLITE_PRIVATE void sqlite3ShowBitvec(Bitvec *p){ + showBitvec(p, 0, 0); +} +#endif + #ifndef SQLITE_UNTESTABLE /* ** Let V[] be an array of unsigned characters sufficient to hold @@ -55059,6 +55887,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ #define CLEARBIT(V,I) V[I>>3] &= ~(BITVEC_TELEM)(1<<(I&7)) #define TESTBIT(V,I) (V[I>>3]&(1<<(I&7)))!=0 + /* ** This routine runs an extensive test of the Bitvec code. ** @@ -55067,7 +55896,7 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ ** by 0, 1, or 3 operands, depending on the opcode. Another ** opcode follows immediately after the last operand. ** -** There are 6 opcodes numbered from 0 through 5. 0 is the +** There are opcodes numbered starting with 0. 0 is the ** "halt" opcode and causes the test to end. ** ** 0 Halt and return the number of errors @@ -55076,18 +55905,25 @@ SQLITE_PRIVATE u32 sqlite3BitvecSize(Bitvec *p){ ** 3 N Set N randomly chosen bits ** 4 N Clear N randomly chosen bits ** 5 N S X Set N bits from S increment X in array only, not in bitvec +** 6 Invoice sqlite3ShowBitvec() on the Bitvec object so far +** 7 X Show compile-time parameters and the hash of X ** ** The opcodes 1 through 4 perform set and clear operations are performed ** on both a Bitvec object and on a linear array of bits obtained from malloc. ** Opcode 5 works on the linear array only, not on the Bitvec. ** Opcode 5 is used to deliberately induce a fault in order to -** confirm that error detection works. +** confirm that error detection works. Opcodes 6 and greater are +** state output opcodes. Opcodes 6 and greater are no-ops unless +** SQLite has been compiled with SQLITE_DEBUG. ** ** At the conclusion of the test the linear array is compared ** against the Bitvec object. If there are any differences, ** an error is returned. If they are the same, zero is returned. ** ** If a memory allocation error occurs, return -1. +** +** sz is the size of the Bitvec. Or if sz is negative, make the size +** 2*(unsigned)(-sz) and disabled the linear vector check. */ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ Bitvec *pBitvec = 0; @@ -55098,10 +55934,15 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ /* Allocate the Bitvec to be tested and a linear array of ** bits to act as the reference */ - pBitvec = sqlite3BitvecCreate( sz ); - pV = sqlite3MallocZero( (7+(i64)sz)/8 + 1 ); + if( sz<=0 ){ + pBitvec = sqlite3BitvecCreate( 2*(unsigned)(-sz) ); + pV = 0; + }else{ + pBitvec = sqlite3BitvecCreate( sz ); + pV = sqlite3MallocZero( (7+(i64)sz)/8 + 1 ); + } pTmpSpace = sqlite3_malloc64(BITVEC_SZ); - if( pBitvec==0 || pV==0 || pTmpSpace==0 ) goto bitvec_end; + if( pBitvec==0 || pTmpSpace==0 || (pV==0 && sz>0) ) goto bitvec_end; /* NULL pBitvec tests */ sqlite3BitvecSet(0, 1); @@ -55110,6 +55951,24 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ /* Run the program */ pc = i = 0; while( (op = aOp[pc])!=0 ){ + if( op>=6 ){ +#ifdef SQLITE_DEBUG + if( op==6 ){ + sqlite3ShowBitvec(pBitvec); + }else if( op==7 ){ + printf("BITVEC_SZ = %d (%d by sizeof)\n", + BITVEC_SZ, (int)sizeof(Bitvec)); + printf("BITVEC_USIZE = %d\n", (int)BITVEC_USIZE); + printf("BITVEC_NELEM = %d\n", (int)BITVEC_NELEM); + printf("BITVEC_NBIT = %d\n", (int)BITVEC_NBIT); + printf("BITVEC_NINT = %d\n", (int)BITVEC_NINT); + printf("BITVEC_MXHASH = %d\n", (int)BITVEC_MXHASH); + printf("BITVEC_NPTR = %d\n", (int)BITVEC_NPTR); + } +#endif + pc++; + continue; + } switch( op ){ case 1: case 2: @@ -55131,12 +55990,12 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ pc += nx; i = (i & 0x7fffffff)%sz; if( (op & 1)!=0 ){ - SETBIT(pV, (i+1)); + if( pV ) SETBIT(pV, (i+1)); if( op!=5 ){ if( sqlite3BitvecSet(pBitvec, i+1) ) goto bitvec_end; } }else{ - CLEARBIT(pV, (i+1)); + if( pV ) CLEARBIT(pV, (i+1)); sqlite3BitvecClear(pBitvec, i+1, pTmpSpace); } } @@ -55146,14 +56005,18 @@ SQLITE_PRIVATE int sqlite3BitvecBuiltinTest(int sz, int *aOp){ ** match (rc==0). Change rc to non-zero if a discrepancy ** is found. */ - rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1) - + sqlite3BitvecTest(pBitvec, 0) - + (sqlite3BitvecSize(pBitvec) - sz); - for(i=1; i<=sz; i++){ - if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){ - rc = i; - break; + if( pV ){ + rc = sqlite3BitvecTest(0,0) + sqlite3BitvecTest(pBitvec, sz+1) + + sqlite3BitvecTest(pBitvec, 0) + + (sqlite3BitvecSize(pBitvec) - sz); + for(i=1; i<=sz; i++){ + if( (TESTBIT(pV,i))!=sqlite3BitvecTest(pBitvec,i) ){ + rc = i; + break; + } } + }else{ + rc = 0; } /* Free allocated structure */ @@ -59914,7 +60777,7 @@ static void pager_unlock(Pager *pPager){ ** have sqlite3WalEndReadTransaction() drop the write-lock, as it once ** did, because this would break "BEGIN EXCLUSIVE" handling for ** SQLITE_ENABLE_SETLK_TIMEOUT builds. */ - sqlite3WalEndWriteTransaction(pPager->pWal); + (void)sqlite3WalEndWriteTransaction(pPager->pWal); } sqlite3WalEndReadTransaction(pPager->pWal); pPager->eState = PAGER_OPEN; @@ -61670,14 +62533,27 @@ SQLITE_PRIVATE void sqlite3PagerSetFlags( unsigned pgFlags /* Various flags */ ){ unsigned level = pgFlags & PAGER_SYNCHRONOUS_MASK; - if( pPager->tempFile ){ + if( pPager->tempFile || level==PAGER_SYNCHRONOUS_OFF ){ pPager->noSync = 1; pPager->fullSync = 0; pPager->extraSync = 0; }else{ - pPager->noSync = level==PAGER_SYNCHRONOUS_OFF ?1:0; + pPager->noSync = 0; pPager->fullSync = level>=PAGER_SYNCHRONOUS_FULL ?1:0; - pPager->extraSync = level==PAGER_SYNCHRONOUS_EXTRA ?1:0; + + /* Set Pager.extraSync if "PRAGMA synchronous=EXTRA" is requested, or + ** if the file-system supports F2FS style atomic writes. If this flag + ** is set, SQLite syncs the directory to disk immediately after deleting + ** a journal file in "PRAGMA journal_mode=DELETE" mode. */ + if( level==PAGER_SYNCHRONOUS_EXTRA +#ifdef SQLITE_ENABLE_BATCH_ATOMIC_WRITE + || (sqlite3OsDeviceCharacteristics(pPager->fd) & SQLITE_IOCAP_BATCH_ATOMIC) +#endif + ){ + pPager->extraSync = 1; + }else{ + pPager->extraSync = 0; + } } if( pPager->noSync ){ pPager->syncFlags = 0; @@ -65570,7 +66446,7 @@ SQLITE_PRIVATE int sqlite3PagerCheckpoint( } if( pPager->pWal ){ rc = sqlite3WalCheckpoint(pPager->pWal, db, eMode, - (eMode==SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), + (eMode<=SQLITE_CHECKPOINT_PASSIVE ? 0 : pPager->xBusyHandler), pPager->pBusyHandlerArg, pPager->walSyncFlags, pPager->pageSize, (u8 *)pPager->pTmpSpace, pnLog, pnCkpt @@ -66480,7 +67356,7 @@ struct WalIterator { /* Size (in bytes) of a WalIterator object suitable for N or fewer segments */ #define SZ_WALITERATOR(N) \ - (offsetof(WalIterator,aSegment)*(N)*sizeof(struct WalSegment)) + (offsetof(WalIterator,aSegment)+(N)*sizeof(struct WalSegment)) /* ** Define the parameters of the hash tables in the wal-index file. There @@ -69366,7 +70242,7 @@ SQLITE_PRIVATE void sqlite3WalEndReadTransaction(Wal *pWal){ assert( pWal->writeLock==0 || pWal->readLock<0 ); #endif if( pWal->readLock>=0 ){ - sqlite3WalEndWriteTransaction(pWal); + (void)sqlite3WalEndWriteTransaction(pWal); walUnlockShared(pWal, WAL_READ_LOCK(pWal->readLock)); pWal->readLock = -1; } @@ -70175,7 +71051,8 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( /* EVIDENCE-OF: R-62920-47450 The busy-handler callback is never invoked ** in the SQLITE_CHECKPOINT_PASSIVE mode. */ - assert( eMode!=SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); + assert( SQLITE_CHECKPOINT_NOOP<SQLITE_CHECKPOINT_PASSIVE ); + assert( eMode>SQLITE_CHECKPOINT_PASSIVE || xBusy==0 ); if( pWal->readOnly ) return SQLITE_READONLY; WALTRACE(("WAL%p: checkpoint begins\n", pWal)); @@ -70192,31 +71069,35 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( ** EVIDENCE-OF: R-53820-33897 Even if there is a busy-handler configured, ** it will not be invoked in this case. */ - rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); - testcase( rc==SQLITE_BUSY ); - testcase( rc!=SQLITE_OK && xBusy2!=0 ); - if( rc==SQLITE_OK ){ - pWal->ckptLock = 1; + if( eMode!=SQLITE_CHECKPOINT_NOOP ){ + rc = walLockExclusive(pWal, WAL_CKPT_LOCK, 1); + testcase( rc==SQLITE_BUSY ); + testcase( rc!=SQLITE_OK && xBusy2!=0 ); + if( rc==SQLITE_OK ){ + pWal->ckptLock = 1; - /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART and - ** TRUNCATE modes also obtain the exclusive "writer" lock on the database - ** file. - ** - ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained - ** immediately, and a busy-handler is configured, it is invoked and the - ** writer lock retried until either the busy-handler returns 0 or the - ** lock is successfully obtained. - */ - if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ - rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); - if( rc==SQLITE_OK ){ - pWal->writeLock = 1; - }else if( rc==SQLITE_BUSY ){ - eMode2 = SQLITE_CHECKPOINT_PASSIVE; - xBusy2 = 0; - rc = SQLITE_OK; + /* IMPLEMENTATION-OF: R-59782-36818 The SQLITE_CHECKPOINT_FULL, RESTART + ** and TRUNCATE modes also obtain the exclusive "writer" lock on the + ** database file. + ** + ** EVIDENCE-OF: R-60642-04082 If the writer lock cannot be obtained + ** immediately, and a busy-handler is configured, it is invoked and the + ** writer lock retried until either the busy-handler returns 0 or the + ** lock is successfully obtained. + */ + if( eMode!=SQLITE_CHECKPOINT_PASSIVE ){ + rc = walBusyLock(pWal, xBusy2, pBusyArg, WAL_WRITE_LOCK, 1); + if( rc==SQLITE_OK ){ + pWal->writeLock = 1; + }else if( rc==SQLITE_BUSY ){ + eMode2 = SQLITE_CHECKPOINT_PASSIVE; + xBusy2 = 0; + rc = SQLITE_OK; + } } } + }else{ + rc = SQLITE_OK; } @@ -70230,7 +71111,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( ** immediately and do a partial checkpoint if it cannot obtain it. */ walDisableBlocking(pWal); rc = walIndexReadHdr(pWal, &isChanged); - if( eMode2!=SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); + if( eMode2>SQLITE_CHECKPOINT_PASSIVE ) (void)walEnableBlocking(pWal); if( isChanged && pWal->pDbFd->pMethods->iVersion>=3 ){ sqlite3OsUnfetch(pWal->pDbFd, 0, 0); } @@ -70240,7 +71121,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( if( rc==SQLITE_OK ){ if( pWal->hdr.mxFrame && walPagesize(pWal)!=nBuf ){ rc = SQLITE_CORRUPT_BKPT; - }else{ + }else if( eMode2!=SQLITE_CHECKPOINT_NOOP ){ rc = walCheckpoint(pWal, db, eMode2, xBusy2, pBusyArg, sync_flags,zBuf); } @@ -70268,7 +71149,7 @@ SQLITE_PRIVATE int sqlite3WalCheckpoint( sqlite3WalDb(pWal, 0); /* Release the locks. */ - sqlite3WalEndWriteTransaction(pWal); + (void)sqlite3WalEndWriteTransaction(pWal); if( pWal->ckptLock ){ walUnlockExclusive(pWal, WAL_CKPT_LOCK, 1); pWal->ckptLock = 0; @@ -72425,7 +73306,7 @@ static int btreeMoveto( assert( nKey==(i64)(int)nKey ); pIdxKey = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pIdxKey==0 ) return SQLITE_NOMEM_BKPT; - sqlite3VdbeRecordUnpack(pKeyInfo, (int)nKey, pKey, pIdxKey); + sqlite3VdbeRecordUnpack((int)nKey, pKey, pIdxKey); if( pIdxKey->nField==0 || pIdxKey->nField>pKeyInfo->nAllField ){ rc = SQLITE_CORRUPT_BKPT; }else{ @@ -73482,10 +74363,10 @@ static int freeSpace(MemPage *pPage, int iStart, int iSize){ assert( pPage->pBt!=0 ); assert( sqlite3PagerIswriteable(pPage->pDbPage) ); assert( CORRUPT_DB || iStart>=pPage->hdrOffset+6+pPage->childPtrSize ); - assert( CORRUPT_DB || iEnd <= pPage->pBt->usableSize ); + assert( CORRUPT_DB || iEnd <= (int)pPage->pBt->usableSize ); assert( sqlite3_mutex_held(pPage->pBt->mutex) ); assert( iSize>=4 ); /* Minimum cell size is 4 */ - assert( CORRUPT_DB || iStart<=pPage->pBt->usableSize-4 ); + assert( CORRUPT_DB || iStart<=(int)pPage->pBt->usableSize-4 ); /* The list of freeblocks must be in ascending order. Find the ** spot on the list where iStart should be inserted. @@ -74409,6 +75290,7 @@ static int removeFromSharingList(BtShared *pBt){ sqlite3_mutex_leave(pMainMtx); return removed; #else + UNUSED_PARAMETER( pBt ); return 1; #endif } @@ -74626,6 +75508,10 @@ SQLITE_PRIVATE int sqlite3BtreeSetPageSize(Btree *p, int pageSize, int nReserve, sqlite3BtreeEnter(p); pBt->nReserveWanted = (u8)nReserve; x = pBt->pageSize - pBt->usableSize; + if( x==nReserve && (pageSize==0 || (u32)pageSize==pBt->pageSize) ){ + sqlite3BtreeLeave(p); + return SQLITE_OK; + } if( nReserve<x ) nReserve = x; if( pBt->btsFlags & BTS_PAGESIZE_FIXED ){ sqlite3BtreeLeave(p); @@ -77215,6 +78101,30 @@ SQLITE_PRIVATE int sqlite3BtreeFirst(BtCursor *pCur, int *pRes){ return rc; } +/* Set *pRes to 1 (true) if the BTree pointed to by cursor pCur contains zero +** rows of content. Set *pRes to 0 (false) if the table contains content. +** Return SQLITE_OK on success or some error code (ex: SQLITE_NOMEM) if +** something goes wrong. +*/ +SQLITE_PRIVATE int sqlite3BtreeIsEmpty(BtCursor *pCur, int *pRes){ + int rc; + + assert( cursorOwnsBtShared(pCur) ); + assert( sqlite3_mutex_held(pCur->pBtree->db->mutex) ); + if( pCur->eState==CURSOR_VALID ){ + *pRes = 0; + return SQLITE_OK; + } + rc = moveToRoot(pCur); + if( rc==SQLITE_EMPTY ){ + *pRes = 1; + rc = SQLITE_OK; + }else{ + *pRes = 0; + } + return rc; +} + #ifdef SQLITE_DEBUG /* The cursors is CURSOR_VALID and has BTCF_AtLast set. Verify that ** this flags are true for a consistent database. @@ -77434,8 +78344,8 @@ moveto_table_finish: } /* -** Compare the "idx"-th cell on the page the cursor pCur is currently -** pointing to to pIdxKey using xRecordCompare. Return negative or +** Compare the "idx"-th cell on the page pPage against the key +** pointing to by pIdxKey using xRecordCompare. Return negative or ** zero if the cell is less than or equal pIdxKey. Return positive ** if unknown. ** @@ -77450,12 +78360,11 @@ moveto_table_finish: ** a positive value as that will cause the optimization to be skipped. */ static int indexCellCompare( - BtCursor *pCur, + MemPage *pPage, int idx, UnpackedRecord *pIdxKey, RecordCompare xRecordCompare ){ - MemPage *pPage = pCur->pPage; int c; int nCell; /* Size of the pCell cell in bytes */ u8 *pCell = findCellPastPtr(pPage, idx); @@ -77564,14 +78473,14 @@ SQLITE_PRIVATE int sqlite3BtreeIndexMoveto( ){ int c; if( pCur->ix==pCur->pPage->nCell-1 - && (c = indexCellCompare(pCur, pCur->ix, pIdxKey, xRecordCompare))<=0 + && (c = indexCellCompare(pCur->pPage,pCur->ix,pIdxKey,xRecordCompare))<=0 && pIdxKey->errCode==SQLITE_OK ){ *pRes = c; return SQLITE_OK; /* Cursor already pointing at the correct spot */ } if( pCur->iPage>0 - && indexCellCompare(pCur, 0, pIdxKey, xRecordCompare)<=0 + && indexCellCompare(pCur->pPage, 0, pIdxKey, xRecordCompare)<=0 && pIdxKey->errCode==SQLITE_OK ){ pCur->curFlags &= ~(BTCF_ValidOvfl|BTCF_AtLast); @@ -77788,7 +78697,7 @@ SQLITE_PRIVATE i64 sqlite3BtreeRowCountEst(BtCursor *pCur){ n = pCur->pPage->nCell; for(i=0; i<pCur->iPage; i++){ - n *= pCur->apPage[i]->nCell; + n *= pCur->apPage[i]->nCell+1; } return n; } @@ -80245,7 +81154,12 @@ static int balance_nonroot( ** of the right-most new sibling page is set to the value that was ** originally in the same field of the right-most old sibling page. */ if( (pageFlags & PTF_LEAF)==0 && nOld!=nNew ){ - MemPage *pOld = (nNew>nOld ? apNew : apOld)[nOld-1]; + MemPage *pOld; + if( nNew>nOld ){ + pOld = apNew[nOld-1]; + }else{ + pOld = apOld[nOld-1]; + } memcpy(&apNew[nNew-1]->aData[8], &pOld->aData[8], 4); } @@ -82877,6 +83791,7 @@ SQLITE_PRIVATE void *sqlite3BtreeSchema(Btree *p, int nBytes, void(*xFree)(void */ SQLITE_PRIVATE int sqlite3BtreeSchemaLocked(Btree *p){ int rc; + UNUSED_PARAMETER(p); /* only used in DEBUG builds */ assert( sqlite3_mutex_held(p->db->mutex) ); sqlite3BtreeEnter(p); rc = querySharedCacheTableLock(p, SCHEMA_ROOT, READ_LOCK); @@ -85062,6 +85977,7 @@ SQLITE_PRIVATE int sqlite3VdbeMemSetStr( if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ return SQLITE_NOMEM_BKPT; } + assert( pMem->z!=0 ); memcpy(pMem->z, z, nAlloc); }else{ sqlite3VdbeMemRelease(pMem); @@ -88889,10 +89805,12 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ if( 0==sqlite3Strlen30(sqlite3BtreeGetFilename(db->aDb[0].pBt)) || nTrans<=1 ){ - for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ - Btree *pBt = db->aDb[i].pBt; - if( pBt ){ - rc = sqlite3BtreeCommitPhaseOne(pBt, 0); + if( needXcommit ){ + for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( sqlite3BtreeTxnState(pBt)>=SQLITE_TXN_WRITE ){ + rc = sqlite3BtreeCommitPhaseOne(pBt, 0); + } } } @@ -88903,7 +89821,9 @@ static int vdbeCommit(sqlite3 *db, Vdbe *p){ */ for(i=0; rc==SQLITE_OK && i<db->nDb; i++){ Btree *pBt = db->aDb[i].pBt; - if( pBt ){ + int txn = sqlite3BtreeTxnState(pBt); + if( txn!=SQLITE_TXN_NONE ){ + assert( needXcommit || txn==SQLITE_TXN_READ ); rc = sqlite3BtreeCommitPhaseTwo(pBt, 0); } } @@ -89158,28 +90078,31 @@ SQLITE_PRIVATE int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){ /* -** This function is called when a transaction opened by the database +** These functions are called when a transaction opened by the database ** handle associated with the VM passed as an argument is about to be -** committed. If there are outstanding deferred foreign key constraint -** violations, return SQLITE_ERROR. Otherwise, SQLITE_OK. +** committed. If there are outstanding foreign key constraint violations +** return an error code. Otherwise, SQLITE_OK. ** ** If there are outstanding FK violations and this function returns -** SQLITE_ERROR, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY -** and write an error message to it. Then return SQLITE_ERROR. +** non-zero, set the result of the VM to SQLITE_CONSTRAINT_FOREIGNKEY +** and write an error message to it. */ #ifndef SQLITE_OMIT_FOREIGN_KEY -SQLITE_PRIVATE int sqlite3VdbeCheckFk(Vdbe *p, int deferred){ +static SQLITE_NOINLINE int vdbeFkError(Vdbe *p){ + p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; + p->errorAction = OE_Abort; + sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); + if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; + return SQLITE_CONSTRAINT_FOREIGNKEY; +} +SQLITE_PRIVATE int sqlite3VdbeCheckFkImmediate(Vdbe *p){ + if( p->nFkConstraint==0 ) return SQLITE_OK; + return vdbeFkError(p); +} +SQLITE_PRIVATE int sqlite3VdbeCheckFkDeferred(Vdbe *p){ sqlite3 *db = p->db; - if( (deferred && (db->nDeferredCons+db->nDeferredImmCons)>0) - || (!deferred && p->nFkConstraint>0) - ){ - p->rc = SQLITE_CONSTRAINT_FOREIGNKEY; - p->errorAction = OE_Abort; - sqlite3VdbeError(p, "FOREIGN KEY constraint failed"); - if( (p->prepFlags & SQLITE_PREPARE_SAVESQL)==0 ) return SQLITE_ERROR; - return SQLITE_CONSTRAINT_FOREIGNKEY; - } - return SQLITE_OK; + if( (db->nDeferredCons+db->nDeferredImmCons)==0 ) return SQLITE_OK; + return vdbeFkError(p); } #endif @@ -89273,7 +90196,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ /* Check for immediate foreign key violations. */ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ - (void)sqlite3VdbeCheckFk(p, 0); + (void)sqlite3VdbeCheckFkImmediate(p); } /* If the auto-commit flag is set and this is the only active writer @@ -89287,7 +90210,7 @@ SQLITE_PRIVATE int sqlite3VdbeHalt(Vdbe *p){ && db->nVdbeWrite==(p->readOnly==0) ){ if( p->rc==SQLITE_OK || (p->errorAction==OE_Fail && !isSpecialError) ){ - rc = sqlite3VdbeCheckFk(p, 1); + rc = sqlite3VdbeCheckFkDeferred(p); if( rc!=SQLITE_OK ){ if( NEVER(p->readOnly) ){ sqlite3VdbeLeave(p); @@ -90097,30 +91020,22 @@ SQLITE_PRIVATE void sqlite3VdbeSerialGet( return; } /* -** This routine is used to allocate sufficient space for an UnpackedRecord -** structure large enough to be used with sqlite3VdbeRecordUnpack() if -** the first argument is a pointer to KeyInfo structure pKeyInfo. -** -** The space is either allocated using sqlite3DbMallocRaw() or from within -** the unaligned buffer passed via the second and third arguments (presumably -** stack space). If the former, then *ppFree is set to a pointer that should -** be eventually freed by the caller using sqlite3DbFree(). Or, if the -** allocation comes from the pSpace/szSpace buffer, *ppFree is set to NULL -** before returning. +** Allocate sufficient space for an UnpackedRecord structure large enough +** to hold a decoded index record for pKeyInfo. ** -** If an OOM error occurs, NULL is returned. +** The space is allocated using sqlite3DbMallocRaw(). If an OOM error +** occurs, NULL is returned. */ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( KeyInfo *pKeyInfo /* Description of the record */ ){ UnpackedRecord *p; /* Unpacked record to return */ - int nByte; /* Number of bytes required for *p */ + u64 nByte; /* Number of bytes required for *p */ assert( sizeof(UnpackedRecord) + sizeof(Mem)*65536 < 0x7fffffff ); nByte = ROUND8P(sizeof(UnpackedRecord)) + sizeof(Mem)*(pKeyInfo->nKeyField+1); p = (UnpackedRecord *)sqlite3DbMallocRaw(pKeyInfo->db, nByte); if( !p ) return 0; p->aMem = (Mem*)&((char*)p)[ROUND8P(sizeof(UnpackedRecord))]; - assert( pKeyInfo->aSortFlags!=0 ); p->pKeyInfo = pKeyInfo; p->nField = pKeyInfo->nKeyField + 1; return p; @@ -90132,7 +91047,6 @@ SQLITE_PRIVATE UnpackedRecord *sqlite3VdbeAllocUnpackedRecord( ** contents of the decoded record. */ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( - KeyInfo *pKeyInfo, /* Information about the record format */ int nKey, /* Size of the binary record */ const void *pKey, /* The binary record */ UnpackedRecord *p /* Populate this structure before returning. */ @@ -90143,6 +91057,7 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( u16 u; /* Unsigned loop counter */ u32 szHdr; Mem *pMem = p->aMem; + KeyInfo *pKeyInfo = p->pKeyInfo; p->default_rc = 0; assert( EIGHT_BYTE_ALIGNMENT(pMem) ); @@ -90160,16 +91075,18 @@ SQLITE_PRIVATE void sqlite3VdbeRecordUnpack( pMem->z = 0; sqlite3VdbeSerialGet(&aKey[d], serial_type, pMem); d += sqlite3VdbeSerialTypeLen(serial_type); - pMem++; if( (++u)>=p->nField ) break; + pMem++; } if( d>(u32)nKey && u ){ assert( CORRUPT_DB ); /* In a corrupt record entry, the last pMem might have been set up using ** uninitialized memory. Overwrite its value with NULL, to prevent ** warnings from MSAN. */ - sqlite3VdbeMemSetNull(pMem-1); + sqlite3VdbeMemSetNull(pMem-(u<p->nField)); } + testcase( u == pKeyInfo->nKeyField + 1 ); + testcase( u < pKeyInfo->nKeyField + 1 ); assert( u<=pKeyInfo->nKeyField + 1 ); p->nField = u; } @@ -90337,6 +91254,32 @@ static void vdbeAssertFieldCountWithinLimits( ** or positive value if *pMem1 is less than, equal to or greater than ** *pMem2, respectively. Similar in spirit to "rc = (*pMem1) - (*pMem2);". */ +static SQLITE_NOINLINE int vdbeCompareMemStringWithEncodingChange( + const Mem *pMem1, + const Mem *pMem2, + const CollSeq *pColl, + u8 *prcErr /* If an OOM occurs, set to SQLITE_NOMEM */ +){ + int rc; + const void *v1, *v2; + Mem c1; + Mem c2; + sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); + sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); + sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); + sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); + v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); + v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); + if( (v1==0 || v2==0) ){ + if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; + rc = 0; + }else{ + rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); + } + sqlite3VdbeMemReleaseMalloc(&c1); + sqlite3VdbeMemReleaseMalloc(&c2); + return rc; +} static int vdbeCompareMemString( const Mem *pMem1, const Mem *pMem2, @@ -90348,25 +91291,7 @@ static int vdbeCompareMemString( ** comparison function directly */ return pColl->xCmp(pColl->pUser,pMem1->n,pMem1->z,pMem2->n,pMem2->z); }else{ - int rc; - const void *v1, *v2; - Mem c1; - Mem c2; - sqlite3VdbeMemInit(&c1, pMem1->db, MEM_Null); - sqlite3VdbeMemInit(&c2, pMem1->db, MEM_Null); - sqlite3VdbeMemShallowCopy(&c1, pMem1, MEM_Ephem); - sqlite3VdbeMemShallowCopy(&c2, pMem2, MEM_Ephem); - v1 = sqlite3ValueText((sqlite3_value*)&c1, pColl->enc); - v2 = sqlite3ValueText((sqlite3_value*)&c2, pColl->enc); - if( (v1==0 || v2==0) ){ - if( prcErr ) *prcErr = SQLITE_NOMEM_BKPT; - rc = 0; - }else{ - rc = pColl->xCmp(pColl->pUser, c1.n, v1, c2.n, v2); - } - sqlite3VdbeMemReleaseMalloc(&c1); - sqlite3VdbeMemReleaseMalloc(&c2); - return rc; + return vdbeCompareMemStringWithEncodingChange(pMem1,pMem2,pColl,prcErr); } } @@ -91029,6 +91954,7 @@ SQLITE_PRIVATE RecordCompare sqlite3VdbeFindCompare(UnpackedRecord *p){ ** The easiest way to enforce this limit is to consider only records with ** 13 fields or less. If the first field is an integer, the maximum legal ** header size is (12*5 + 1 + 1) bytes. */ + assert( p->pKeyInfo->aSortFlags!=0 ); if( p->pKeyInfo->nAllField<=13 ){ int flags = p->aMem[0].flags; if( p->pKeyInfo->aSortFlags[0] ){ @@ -91278,6 +92204,7 @@ SQLITE_PRIVATE void sqlite3VdbeSetVarmask(Vdbe *v, int iVar){ } } +#ifndef SQLITE_OMIT_DATETIME_FUNCS /* ** Cause a function to throw an error if it was call from OP_PureFunc ** rather than OP_Function. @@ -91311,6 +92238,7 @@ SQLITE_PRIVATE int sqlite3NotPureFunc(sqlite3_context *pCtx){ } return 1; } +#endif /* SQLITE_OMIT_DATETIME_FUNCS */ #if defined(SQLITE_ENABLE_CURSOR_HINTS) && defined(SQLITE_DEBUG) /* @@ -91387,7 +92315,6 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( i64 iKey2; PreUpdate preupdate; const char *zTbl = pTab->zName; - static const u8 fakeSortOrder = 0; #ifdef SQLITE_DEBUG int nRealCol; if( pTab->tabFlags & TF_WithoutRowid ){ @@ -91422,11 +92349,11 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( preupdate.pCsr = pCsr; preupdate.op = op; preupdate.iNewReg = iReg; - preupdate.pKeyinfo = (KeyInfo*)&preupdate.keyinfoSpace; + preupdate.pKeyinfo = (KeyInfo*)&preupdate.uKey; preupdate.pKeyinfo->db = db; preupdate.pKeyinfo->enc = ENC(db); preupdate.pKeyinfo->nKeyField = pTab->nCol; - preupdate.pKeyinfo->aSortFlags = (u8*)&fakeSortOrder; + preupdate.pKeyinfo->aSortFlags = 0; /* Indicate .aColl, .nAllField uninit */ preupdate.iKey1 = iKey1; preupdate.iKey2 = iKey2; preupdate.pTab = pTab; @@ -91456,6 +92383,17 @@ SQLITE_PRIVATE void sqlite3VdbePreUpdateHook( } #endif /* SQLITE_ENABLE_PREUPDATE_HOOK */ +#ifdef SQLITE_ENABLE_PERCENTILE +/* +** Return the name of an SQL function associated with the sqlite3_context. +*/ +SQLITE_PRIVATE const char *sqlite3VdbeFuncName(const sqlite3_context *pCtx){ + assert( pCtx!=0 ); + assert( pCtx->pFunc!=0 ); + return pCtx->pFunc->zName; +} +#endif /* SQLITE_ENABLE_PERCENTILE */ + /************** End of vdbeaux.c *********************************************/ /************** Begin file vdbeapi.c *****************************************/ /* @@ -93153,8 +94091,12 @@ static int bindText( if( zData!=0 ){ pVar = &p->aVar[i-1]; rc = sqlite3VdbeMemSetStr(pVar, zData, nData, encoding, xDel); - if( rc==SQLITE_OK && encoding!=0 ){ - rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + if( rc==SQLITE_OK ){ + if( encoding==0 ){ + pVar->enc = ENC(p->db); + }else{ + rc = sqlite3VdbeChangeEncoding(pVar, ENC(p->db)); + } } if( rc ){ sqlite3Error(p->db, rc); @@ -93623,7 +94565,7 @@ static UnpackedRecord *vdbeUnpackRecord( pRet = sqlite3VdbeAllocUnpackedRecord(pKeyInfo); if( pRet ){ memset(pRet->aMem, 0, sizeof(Mem)*(pKeyInfo->nKeyField+1)); - sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, pRet); + sqlite3VdbeRecordUnpack(nKey, pKey, pRet); } return pRet; } @@ -93652,6 +94594,9 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppVa } if( p->pPk ){ iStore = sqlite3TableColumnToIndex(p->pPk, iIdx); + }else if( iIdx >= p->pTab->nCol ){ + rc = SQLITE_MISUSE_BKPT; + goto preupdate_old_out; }else{ iStore = sqlite3TableColumnToStorage(p->pTab, iIdx); } @@ -93807,6 +94752,8 @@ SQLITE_API int sqlite3_preupdate_new(sqlite3 *db, int iIdx, sqlite3_value **ppVa } if( p->pPk && p->op!=SQLITE_UPDATE ){ iStore = sqlite3TableColumnToIndex(p->pPk, iIdx); + }else if( iIdx >= p->pTab->nCol ){ + return SQLITE_MISUSE_BKPT; }else{ iStore = sqlite3TableColumnToStorage(p->pTab, iIdx); } @@ -94082,10 +95029,10 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt *pStmt){ ** a host parameter. If the text contains no host parameters, return ** the total number of bytes in the text. */ -static int findNextHostParameter(const char *zSql, int *pnToken){ +static i64 findNextHostParameter(const char *zSql, i64 *pnToken){ int tokenType; - int nTotal = 0; - int n; + i64 nTotal = 0; + i64 n; *pnToken = 0; while( zSql[0] ){ @@ -94132,8 +95079,8 @@ SQLITE_PRIVATE char *sqlite3VdbeExpandSql( sqlite3 *db; /* The database connection */ int idx = 0; /* Index of a host parameter */ int nextIndex = 1; /* Index of next ? host parameter */ - int n; /* Length of a token prefix */ - int nToken; /* Length of the parameter token */ + i64 n; /* Length of a token prefix */ + i64 nToken; /* Length of the parameter token */ int i; /* Loop counter */ Mem *pVar; /* Value of a host parameter */ StrAccum out; /* Accumulate the output here */ @@ -95057,7 +96004,7 @@ static u64 filterHash(const Mem *aMem, const Op *pOp){ static SQLITE_NOINLINE int vdbeColumnFromOverflow( VdbeCursor *pC, /* The BTree cursor from which we are reading */ int iCol, /* The column to read */ - int t, /* The serial-type code for the column value */ + u32 t, /* The serial-type code for the column value */ i64 iOffset, /* Offset to the start of the content value */ u32 cacheStatus, /* Current Vdbe.cacheCtr value */ u32 colCacheCtr, /* Current value of the column cache counter */ @@ -95132,6 +96079,36 @@ static SQLITE_NOINLINE int vdbeColumnFromOverflow( return rc; } +/* +** Send a "statement aborts" message to the error log. +*/ +static SQLITE_NOINLINE void sqlite3VdbeLogAbort( + Vdbe *p, /* The statement that is running at the time of failure */ + int rc, /* Error code */ + Op *pOp, /* Opcode that filed */ + Op *aOp /* All opcodes */ +){ + const char *zSql = p->zSql; /* Original SQL text */ + const char *zPrefix = ""; /* Prefix added to SQL text */ + int pc; /* Opcode address */ + char zXtra[100]; /* Buffer space to store zPrefix */ + + if( p->pFrame ){ + assert( aOp[0].opcode==OP_Init ); + if( aOp[0].p4.z!=0 ){ + assert( aOp[0].p4.z[0]=='-' + && aOp[0].p4.z[1]=='-' + && aOp[0].p4.z[2]==' ' ); + sqlite3_snprintf(sizeof(zXtra), zXtra,"/* %s */ ",aOp[0].p4.z+3); + zPrefix = zXtra; + }else{ + zPrefix = "/* unknown trigger */ "; + } + } + pc = (int)(pOp - aOp); + sqlite3_log(rc, "statement aborts at %d: %s; [%s%s]", + pc, p->zErrMsg, zPrefix, zSql); +} /* ** Return the symbolic name for the data type of a pMem @@ -95657,8 +96634,7 @@ case OP_Halt: { }else{ sqlite3VdbeError(p, "%s", pOp->p4.z); } - pcx = (int)(pOp - aOp); - sqlite3_log(pOp->p1, "abort at %d: %s; [%s]", pcx, p->zErrMsg, p->zSql); + sqlite3VdbeLogAbort(p, pOp->p1, pOp, aOp); } rc = sqlite3VdbeHalt(p); assert( rc==SQLITE_BUSY || rc==SQLITE_OK || rc==SQLITE_ERROR ); @@ -96037,7 +97013,7 @@ case OP_IntCopy: { /* out2 */ ** RETURNING clause. */ case OP_FkCheck: { - if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){ + if( (rc = sqlite3VdbeCheckFkImmediate(p))!=SQLITE_OK ){ goto abort_due_to_error; } break; @@ -96129,10 +97105,14 @@ case OP_Concat: { /* same as TK_CONCAT, in1, in2, out3 */ if( sqlite3VdbeMemExpandBlob(pIn2) ) goto no_mem; flags2 = pIn2->flags & ~MEM_Str; } - nByte = pIn1->n + pIn2->n; + nByte = pIn1->n; + nByte += pIn2->n; if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ goto too_big; } +#if SQLITE_MAX_LENGTH>2147483645 + if( nByte>2147483645 ){ goto too_big; } +#endif if( sqlite3VdbeMemGrow(pOut, (int)nByte+2, pOut==pIn2) ){ goto no_mem; } @@ -96816,6 +97796,7 @@ case OP_Compare: { pKeyInfo = pOp->p4.pKeyInfo; assert( n>0 ); assert( pKeyInfo!=0 ); + assert( pKeyInfo->aSortFlags!=0 ); p1 = pOp->p1; p2 = pOp->p2; #ifdef SQLITE_DEBUG @@ -97578,6 +98559,15 @@ op_column_corrupt: ** Take the affinities from the Table object in P4. If any value ** cannot be coerced into the correct type, then raise an error. ** +** If P3==0, then omit checking of VIRTUAL columns. +** +** If P3==1, then omit checking of all generated column, both VIRTUAL +** and STORED. +** +** If P3>=2, then only check column number P3-2 in the table (which will +** be a VIRTUAL column) against the value in reg[P1]. In this case, +** P2 will be 1. +** ** This opcode is similar to OP_Affinity except that this opcode ** forces the register type to the Table column type. This is used ** to implement "strict affinity". @@ -97591,8 +98581,8 @@ op_column_corrupt: ** ** <ul> ** <li> P2 should be the number of non-virtual columns in the -** table of P4. -** <li> Table P4 should be a STRICT table. +** table of P4 unless P3>1, in which case P2 will be 1. +** <li> Table P4 is a STRICT table. ** </ul> ** ** If any precondition is false, an assertion fault occurs. @@ -97601,16 +98591,28 @@ case OP_TypeCheck: { Table *pTab; Column *aCol; int i; + int nCol; assert( pOp->p4type==P4_TABLE ); pTab = pOp->p4.pTab; assert( pTab->tabFlags & TF_Strict ); - assert( pTab->nNVCol==pOp->p2 ); + assert( pOp->p3>=0 && pOp->p3<pTab->nCol+2 ); aCol = pTab->aCol; pIn1 = &aMem[pOp->p1]; - for(i=0; i<pTab->nCol; i++){ - if( aCol[i].colFlags & COLFLAG_GENERATED ){ - if( aCol[i].colFlags & COLFLAG_VIRTUAL ) continue; + if( pOp->p3<2 ){ + assert( pTab->nNVCol==pOp->p2 ); + i = 0; + nCol = pTab->nCol; + }else{ + i = pOp->p3-2; + nCol = i+1; + assert( i<pTab->nCol ); + assert( aCol[i].colFlags & COLFLAG_VIRTUAL ); + assert( pOp->p2==1 ); + } + for(; i<nCol; i++){ + if( (aCol[i].colFlags & COLFLAG_GENERATED)!=0 && pOp->p3<2 ){ + if( (aCol[i].colFlags & COLFLAG_VIRTUAL)!=0 ) continue; if( pOp->p3 ){ pIn1++; continue; } } assert( pIn1 < &aMem[pOp->p1+pOp->p2] ); @@ -97932,7 +98934,7 @@ case OP_MakeRecord: { len = (u32)pRec->n; serial_type = (len*2) + 12 + ((pRec->flags & MEM_Str)!=0); if( pRec->flags & MEM_Zero ){ - serial_type += pRec->u.nZero*2; + serial_type += (u32)pRec->u.nZero*2; if( nData ){ if( sqlite3VdbeMemExpandBlob(pRec) ) goto no_mem; len += pRec->u.nZero; @@ -98199,7 +99201,7 @@ case OP_Savepoint: { */ int isTransaction = pSavepoint->pNext==0 && db->isTransactionSavepoint; if( isTransaction && p1==SAVEPOINT_RELEASE ){ - if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ goto vdbe_return; } db->autoCommit = 1; @@ -98317,7 +99319,7 @@ case OP_AutoCommit: { "SQL statements in progress"); rc = SQLITE_BUSY; goto abort_due_to_error; - }else if( (rc = sqlite3VdbeCheckFk(p, 1))!=SQLITE_OK ){ + }else if( (rc = sqlite3VdbeCheckFkDeferred(p))!=SQLITE_OK ){ goto vdbe_return; }else{ db->autoCommit = (u8)desiredAutoCommit; @@ -99689,7 +100691,7 @@ case OP_Found: { /* jump, in3, ncycle */ if( rc ) goto no_mem; pIdxKey = sqlite3VdbeAllocUnpackedRecord(pC->pKeyInfo); if( pIdxKey==0 ) goto no_mem; - sqlite3VdbeRecordUnpack(pC->pKeyInfo, r.aMem->n, r.aMem->z, pIdxKey); + sqlite3VdbeRecordUnpack(r.aMem->n, r.aMem->z, pIdxKey); pIdxKey->default_rc = 0; rc = sqlite3BtreeIndexMoveto(pC->uc.pCursor, pIdxKey, &pC->seekResult); sqlite3DbFreeNN(db, pIdxKey); @@ -100687,6 +101689,32 @@ case OP_Rewind: { /* jump0, ncycle */ break; } +/* Opcode: IfEmpty P1 P2 * * * +** Synopsis: if( empty(P1) ) goto P2 +** +** Check to see if the b-tree table that cursor P1 references is empty +** and jump to P2 if it is. +*/ +case OP_IfEmpty: { /* jump */ + VdbeCursor *pC; + BtCursor *pCrsr; + int res; + + assert( pOp->p1>=0 && pOp->p1<p->nCursor ); + assert( pOp->p2>=0 && pOp->p2<p->nOp ); + + pC = p->apCsr[pOp->p1]; + assert( pC!=0 ); + assert( pC->eCurType==CURTYPE_BTREE ); + pCrsr = pC->uc.pCursor; + assert( pCrsr ); + rc = sqlite3BtreeIsEmpty(pCrsr, &res); + if( rc ) goto abort_due_to_error; + VdbeBranchTaken(res!=0,2); + if( res ) goto jump_to_p2; + break; +} + /* Opcode: Next P1 P2 P3 * P5 ** ** Advance cursor P1 so that it points to the next key/data pair in its @@ -102223,6 +103251,7 @@ case OP_Checkpoint: { || pOp->p2==SQLITE_CHECKPOINT_FULL || pOp->p2==SQLITE_CHECKPOINT_RESTART || pOp->p2==SQLITE_CHECKPOINT_TRUNCATE + || pOp->p2==SQLITE_CHECKPOINT_NOOP ); rc = sqlite3Checkpoint(db, pOp->p1, pOp->p2, &aRes[1], &aRes[2]); if( rc ){ @@ -102558,7 +103587,14 @@ case OP_VOpen: { /* ncycle */ const sqlite3_module *pModule; assert( p->bIsReader ); - pCur = 0; + pCur = p->apCsr[pOp->p1]; + if( pCur!=0 + && ALWAYS( pCur->eCurType==CURTYPE_VTAB ) + && ALWAYS( pCur->uc.pVCur->pVtab==pOp->p4.pVtab->pVtab ) + ){ + /* This opcode is a no-op if the cursor is already open */ + break; + } pVCur = 0; pVtab = pOp->p4.pVtab->pVtab; if( pVtab==0 || NEVER(pVtab->pModule==0) ){ @@ -103500,8 +104536,7 @@ abort_due_to_error: p->rc = rc; sqlite3SystemError(db, rc); testcase( sqlite3GlobalConfig.xLog!=0 ); - sqlite3_log(rc, "statement aborts at %d: %s; [%s]", - (int)(pOp - aOp), p->zErrMsg, p->zSql); + sqlite3VdbeLogAbort(p, rc, pOp, aOp); if( p->eVdbeState==VDBE_RUN_STATE ) sqlite3VdbeHalt(p); if( rc==SQLITE_IOERR_NOMEM ) sqlite3OomFault(db); if( rc==SQLITE_CORRUPT && db->autoCommit==0 ){ @@ -103962,7 +104997,7 @@ static int blobReadWrite( int iOffset, int (*xCall)(BtCursor*, u32, u32, void*) ){ - int rc; + int rc = SQLITE_OK; Incrblob *p = (Incrblob *)pBlob; Vdbe *v; sqlite3 *db; @@ -104002,17 +105037,32 @@ static int blobReadWrite( ** using the incremental-blob API, this works. For the sessions module ** anyhow. */ - sqlite3_int64 iKey; - iKey = sqlite3BtreeIntegerKey(p->pCsr); - assert( v->apCsr[0]!=0 ); - assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); - sqlite3VdbePreUpdateHook( - v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol - ); + if( sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ){ + /* If the cursor is not currently valid, try to reseek it. This + ** always either fails or finds the correct row - the cursor will + ** have been marked permanently CURSOR_INVALID if the open row has + ** been deleted. */ + int bDiff = 0; + rc = sqlite3BtreeCursorRestore(p->pCsr, &bDiff); + assert( bDiff==0 || sqlite3BtreeCursorIsValidNN(p->pCsr)==0 ); + } + if( sqlite3BtreeCursorIsValidNN(p->pCsr) ){ + sqlite3_int64 iKey; + iKey = sqlite3BtreeIntegerKey(p->pCsr); + assert( v->apCsr[0]!=0 ); + assert( v->apCsr[0]->eCurType==CURTYPE_BTREE ); + sqlite3VdbePreUpdateHook( + v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1, p->iCol + ); + } } + if( rc==SQLITE_OK ){ + rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); + } +#else + rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); #endif - rc = xCall(p->pCsr, iOffset+p->iOffset, n, z); sqlite3BtreeLeaveCursor(p->pCsr); if( rc==SQLITE_ABORT ){ sqlite3VdbeFinalize(v); @@ -104401,6 +105451,7 @@ struct SortSubtask { SorterCompare xCompare; /* Compare function to use */ SorterFile file; /* Temp file for level-0 PMAs */ SorterFile file2; /* Space for other PMAs */ + u64 nSpill; /* Total bytes written by this task */ }; @@ -104521,6 +105572,7 @@ struct PmaWriter { int iBufEnd; /* Last byte of buffer to write */ i64 iWriteOff; /* Offset of start of buffer in file */ sqlite3_file *pFd; /* File handle to write to */ + u64 nPmaSpill; /* Total number of bytes written */ }; /* @@ -104865,7 +105917,7 @@ static int vdbeSorterCompareTail( ){ UnpackedRecord *r2 = pTask->pUnpacked; if( *pbKey2Cached==0 ){ - sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + sqlite3VdbeRecordUnpack(nKey2, pKey2, r2); *pbKey2Cached = 1; } return sqlite3VdbeRecordCompareWithSkip(nKey1, pKey1, r2, 1); @@ -104892,7 +105944,7 @@ static int vdbeSorterCompare( ){ UnpackedRecord *r2 = pTask->pUnpacked; if( !*pbKey2Cached ){ - sqlite3VdbeRecordUnpack(pTask->pSorter->pKeyInfo, nKey2, pKey2, r2); + sqlite3VdbeRecordUnpack(nKey2, pKey2, r2); *pbKey2Cached = 1; } return sqlite3VdbeRecordCompare(nKey1, pKey1, r2); @@ -104932,6 +105984,7 @@ static int vdbeSorterCompareText( ); } }else{ + assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 ); assert( !(pTask->pSorter->pKeyInfo->aSortFlags[0]&KEYINFO_ORDER_BIGNULL) ); if( pTask->pSorter->pKeyInfo->aSortFlags[0] ){ res = res * -1; @@ -104995,6 +106048,7 @@ static int vdbeSorterCompareInt( } } + assert( pTask->pSorter->pKeyInfo->aSortFlags!=0 ); if( res==0 ){ if( pTask->pSorter->pKeyInfo->nKeyField>1 ){ res = vdbeSorterCompareTail( @@ -105068,7 +106122,8 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( assert( pCsr->eCurType==CURTYPE_SORTER ); assert( sizeof(KeyInfo) + UMXV(pCsr->pKeyInfo->nKeyField)*sizeof(CollSeq*) < 0x7fffffff ); - szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nKeyField); + assert( pCsr->pKeyInfo->nKeyField<=pCsr->pKeyInfo->nAllField ); + szKeyInfo = SZ_KEYINFO(pCsr->pKeyInfo->nAllField); sz = SZ_VDBESORTER(nWorker+1); pSorter = (VdbeSorter*)sqlite3DbMallocZero(db, sz + szKeyInfo); @@ -105082,7 +106137,12 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit( pKeyInfo->db = 0; if( nField && nWorker==0 ){ pKeyInfo->nKeyField = nField; + assert( nField<=pCsr->pKeyInfo->nAllField ); } + /* It is OK that pKeyInfo reuses the aSortFlags field from pCsr->pKeyInfo, + ** since the pCsr->pKeyInfo->aSortFlags[] array is invariant and lives + ** longer that pSorter. */ + assert( pKeyInfo->aSortFlags==pCsr->pKeyInfo->aSortFlags ); sqlite3BtreeEnter(pBt); pSorter->pgsz = pgsz = sqlite3BtreeGetPageSize(pBt); sqlite3BtreeLeave(pBt); @@ -105371,6 +106431,12 @@ SQLITE_PRIVATE void sqlite3VdbeSorterClose(sqlite3 *db, VdbeCursor *pCsr){ assert( pCsr->eCurType==CURTYPE_SORTER ); pSorter = pCsr->uc.pSorter; if( pSorter ){ + /* Increment db->nSpill by the total number of bytes of data written + ** to temp files by this sort operation. */ + int ii; + for(ii=0; ii<pSorter->nTask; ii++){ + db->nSpill += pSorter->aTask[ii].nSpill; + } sqlite3VdbeSorterReset(db, pSorter); sqlite3_free(pSorter->list.aMemory); sqlite3DbFree(db, pSorter); @@ -105596,6 +106662,7 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); + p->nPmaSpill += (p->iBufEnd - p->iBufStart); p->iBufStart = p->iBufEnd = 0; p->iWriteOff += p->nBuffer; } @@ -105612,17 +106679,20 @@ static void vdbePmaWriteBlob(PmaWriter *p, u8 *pData, int nData){ ** required. Otherwise, return an SQLite error code. ** ** Before returning, set *piEof to the offset immediately following the -** last byte written to the file. +** last byte written to the file. Also, increment (*pnSpill) by the total +** number of bytes written to the file. */ -static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof){ +static int vdbePmaWriterFinish(PmaWriter *p, i64 *piEof, u64 *pnSpill){ int rc; if( p->eFWErr==0 && ALWAYS(p->aBuffer) && p->iBufEnd>p->iBufStart ){ p->eFWErr = sqlite3OsWrite(p->pFd, &p->aBuffer[p->iBufStart], p->iBufEnd - p->iBufStart, p->iWriteOff + p->iBufStart ); + p->nPmaSpill += (p->iBufEnd - p->iBufStart); } *piEof = (p->iWriteOff + p->iBufEnd); + *pnSpill += p->nPmaSpill; sqlite3_free(p->aBuffer); rc = p->eFWErr; memset(p, 0, sizeof(PmaWriter)); @@ -105702,7 +106772,7 @@ static int vdbeSorterListToPMA(SortSubtask *pTask, SorterList *pList){ if( pList->aMemory==0 ) sqlite3_free(p); } pList->pList = p; - rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof); + rc = vdbePmaWriterFinish(&writer, &pTask->file.iEof, &pTask->nSpill); } vdbeSorterWorkDebug(pTask, "exit"); @@ -106016,7 +107086,7 @@ static int vdbeIncrPopulate(IncrMerger *pIncr){ rc = vdbeMergeEngineStep(pIncr->pMerger, &dummy); } - rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof); + rc2 = vdbePmaWriterFinish(&writer, &pOut->iEof, &pTask->nSpill); if( rc==SQLITE_OK ) rc = rc2; vdbeSorterPopulateDebug(pTask, "exit"); return rc; @@ -106862,7 +107932,7 @@ SQLITE_PRIVATE int sqlite3VdbeSorterCompare( assert( r2->nField==nKeyCol ); pKey = vdbeSorterRowkey(pSorter, &nKey); - sqlite3VdbeRecordUnpack(pKeyInfo, nKey, pKey, r2); + sqlite3VdbeRecordUnpack(nKey, pKey, r2); for(i=0; i<nKeyCol; i++){ if( r2->aMem[i].flags & MEM_Null ){ *pRes = -1; @@ -108407,10 +109477,13 @@ static int lookupName( if( cnt>0 ){ if( pItem->fg.isUsing==0 || sqlite3IdListIndex(pItem->u3.pUsing, zCol)<0 + || pMatch==pItem ){ /* Two or more tables have the same column name which is - ** not joined by USING. This is an error. Signal as much - ** by clearing pFJMatch and letting cnt go above 1. */ + ** not joined by USING. Or, a single table has two columns + ** that match a USING term (if pMatch==pItem). These are both + ** "ambiguous column name" errors. Signal as much by clearing + ** pFJMatch and letting cnt go above 1. */ sqlite3ExprListDelete(db, pFJMatch); pFJMatch = 0; }else @@ -108960,8 +110033,8 @@ static void notValidImpl( /* ** Expression p should encode a floating point value between 1.0 and 0.0. -** Return 1024 times this value. Or return -1 if p is not a floating point -** value between 1.0 and 0.0. +** Return 134,217,728 (2^27) times this value. Or return -1 if p is not +** a floating point value between 1.0 and 0.0. */ static int exprProbability(Expr *p){ double r = -1.0; @@ -109392,11 +110465,13 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ return WRC_Prune; } #ifndef SQLITE_OMIT_SUBQUERY + case TK_EXISTS: case TK_SELECT: - case TK_EXISTS: testcase( pExpr->op==TK_EXISTS ); #endif case TK_IN: { testcase( pExpr->op==TK_IN ); + testcase( pExpr->op==TK_EXISTS ); + testcase( pExpr->op==TK_SELECT ); if( ExprUseXSelect(pExpr) ){ int nRef = pNC->nRef; testcase( pNC->ncFlags & NC_IsCheck ); @@ -109404,6 +110479,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ testcase( pNC->ncFlags & NC_IdxExpr ); testcase( pNC->ncFlags & NC_GenCol ); assert( pExpr->x.pSelect ); + if( pExpr->op==TK_EXISTS ) pParse->bHasExists = 1; if( pNC->ncFlags & NC_SelfRef ){ notValidImpl(pParse, pNC, "subqueries", pExpr, pExpr); }else{ @@ -110314,14 +111390,17 @@ SQLITE_PRIVATE int sqlite3ResolveSelfReference( SrcList *pSrc; /* Fake SrcList for pParse->pNewTable */ NameContext sNC; /* Name context for pParse->pNewTable */ int rc; - u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ + union { + SrcList sSrc; + u8 srcSpace[SZ_SRCLIST_1]; /* Memory space for the fake SrcList */ + } uSrc; assert( type==0 || pTab!=0 ); assert( type==NC_IsCheck || type==NC_PartIdx || type==NC_IdxExpr || type==NC_GenCol || pTab==0 ); memset(&sNC, 0, sizeof(sNC)); - pSrc = (SrcList*)srcSpace; - memset(pSrc, 0, SZ_SRCLIST_1); + memset(&uSrc, 0, sizeof(uSrc)); + pSrc = &uSrc.sSrc; if( pTab ){ pSrc->nSrc = 1; pSrc->a[0].zName = pTab->zName; @@ -111584,6 +112663,11 @@ SQLITE_PRIVATE void sqlite3ExprAddFunctionOrderBy( sqlite3ExprListDelete(db, pOrderBy); return; } + if( pOrderBy->nExpr>db->aLimit[SQLITE_LIMIT_COLUMN] ){ + sqlite3ErrorMsg(pParse, "too many terms in ORDER BY clause"); + sqlite3ExprListDelete(db, pOrderBy); + return; + } pOB = sqlite3ExprAlloc(db, TK_ORDER, 0, 0); if( pOB==0 ){ @@ -112719,6 +113803,85 @@ SQLITE_PRIVATE Expr *sqlite3ExprSimplifiedAndOr(Expr *pExpr){ } /* +** Return true if it might be advantageous to compute the right operand +** of expression pExpr first, before the left operand. +** +** Normally the left operand is computed before the right operand. But if +** the left operand contains a subquery and the right does not, then it +** might be more efficient to compute the right operand first. +*/ +static int exprEvalRhsFirst(Expr *pExpr){ + if( ExprHasProperty(pExpr->pLeft, EP_Subquery) + && !ExprHasProperty(pExpr->pRight, EP_Subquery) + ){ + return 1; + }else{ + return 0; + } +} + +/* +** Compute the two operands of a binary operator. +** +** If either operand contains a subquery, then the code strives to +** compute the operand containing the subquery second. If the other +** operand evalutes to NULL, then a jump is made. The address of the +** IsNull operand that does this jump is returned. The caller can use +** this to optimize the computation so as to avoid doing the potentially +** expensive subquery. +** +** If no optimization opportunities exist, return 0. +*/ +static int exprComputeOperands( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* The comparison expression */ + int *pR1, /* OUT: Register holding the left operand */ + int *pR2, /* OUT: Register holding the right operand */ + int *pFree1, /* OUT: Temp register to free if not zero */ + int *pFree2 /* OUT: Another temp register to free if not zero */ +){ + int addrIsNull; + int r1, r2; + Vdbe *v = pParse->pVdbe; + + assert( v!=0 ); + /* + ** If the left operand contains a (possibly expensive) subquery and the + ** right operand does not and the right operation might be NULL, + ** then compute the right operand first and do an IsNull jump if the + ** right operand evalutes to NULL. + */ + if( exprEvalRhsFirst(pExpr) && sqlite3ExprCanBeNull(pExpr->pRight) ){ + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2); + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r2); + VdbeComment((v, "skip left operand")); + VdbeCoverage(v); + }else{ + r2 = 0; /* Silence a false-positive uninit-var warning in MSVC */ + addrIsNull = 0; + } + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pFree1); + if( addrIsNull==0 ){ + /* + ** If the right operand contains a subquery and the left operand does not + ** and the left operand might be NULL, then do an IsNull check + ** check on the left operand before computing the right operand. + */ + if( ExprHasProperty(pExpr->pRight, EP_Subquery) + && sqlite3ExprCanBeNull(pExpr->pLeft) + ){ + addrIsNull = sqlite3VdbeAddOp1(v, OP_IsNull, r1); + VdbeComment((v, "skip right operand")); + VdbeCoverage(v); + } + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pFree2); + } + *pR1 = r1; + *pR2 = r2; + return addrIsNull; +} + +/* ** pExpr is a TK_FUNCTION node. Try to determine whether or not the ** function is a constant function. A function is constant if all of ** the following are true: @@ -114162,17 +115325,23 @@ SQLITE_PRIVATE int sqlite3CodeSubselect(Parse *pParse, Expr *pExpr){ VdbeComment((v, "Init EXISTS result")); } if( pSel->pLimit ){ - /* The subquery already has a limit. If the pre-existing limit is X - ** then make the new limit X<>0 so that the new limit is either 1 or 0 */ - sqlite3 *db = pParse->db; - pLimit = sqlite3Expr(db, TK_INTEGER, "0"); - if( pLimit ){ - pLimit->affExpr = SQLITE_AFF_NUMERIC; - pLimit = sqlite3PExpr(pParse, TK_NE, - sqlite3ExprDup(db, pSel->pLimit->pLeft, 0), pLimit); + /* The subquery already has a limit. If the pre-existing limit X is + ** not already integer value 1 or 0, then make the new limit X<>0 so that + ** the new limit is either 1 or 0 */ + Expr *pLeft = pSel->pLimit->pLeft; + if( ExprHasProperty(pLeft, EP_IntValue)==0 + || (pLeft->u.iValue!=1 && pLeft->u.iValue!=0) + ){ + sqlite3 *db = pParse->db; + pLimit = sqlite3Expr(db, TK_INTEGER, "0"); + if( pLimit ){ + pLimit->affExpr = SQLITE_AFF_NUMERIC; + pLimit = sqlite3PExpr(pParse, TK_NE, + sqlite3ExprDup(db, pLeft, 0), pLimit); + } + sqlite3ExprDeferredDelete(pParse, pLeft); + pSel->pLimit->pLeft = pLimit; } - sqlite3ExprDeferredDelete(pParse, pSel->pLimit->pLeft); - pSel->pLimit->pLeft = pLimit; }else{ /* If there is no pre-existing limit add a limit of 1 */ pLimit = sqlite3Expr(pParse->db, TK_INTEGER, "1"); @@ -114260,7 +115429,6 @@ static void sqlite3ExprCodeIN( int rRhsHasNull = 0; /* Register that is true if RHS contains NULL values */ int eType; /* Type of the RHS */ int rLhs; /* Register(s) holding the LHS values */ - int rLhsOrig; /* LHS values prior to reordering by aiMap[] */ Vdbe *v; /* Statement under construction */ int *aiMap = 0; /* Map from vector field to index column */ char *zAff = 0; /* Affinity string for comparisons */ @@ -114323,19 +115491,8 @@ static void sqlite3ExprCodeIN( ** by code generated below. */ assert( pParse->okConstFactor==okConstFactor ); pParse->okConstFactor = 0; - rLhsOrig = exprCodeVector(pParse, pLeft, &iDummy); + rLhs = exprCodeVector(pParse, pLeft, &iDummy); pParse->okConstFactor = okConstFactor; - for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ - if( i==nVector ){ - /* LHS fields are not reordered */ - rLhs = rLhsOrig; - }else{ - /* Need to reorder the LHS fields according to aiMap */ - rLhs = sqlite3GetTempRange(pParse, nVector); - for(i=0; i<nVector; i++){ - sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); - } - } /* If sqlite3FindInIndex() did not find or create an index that is ** suitable for evaluating the IN operator, then evaluate using a @@ -114350,6 +115507,7 @@ static void sqlite3ExprCodeIN( int r2, regToFree; int regCkNull = 0; int ii; + assert( nVector==1 ); assert( ExprUseXList(pExpr) ); pList = pExpr->x.pList; pColl = sqlite3ExprCollSeq(pParse, pExpr->pLeft); @@ -114391,6 +115549,26 @@ static void sqlite3ExprCodeIN( goto sqlite3ExprCodeIN_finished; } + if( eType!=IN_INDEX_ROWID ){ + /* If this IN operator will use an index, then the order of columns in the + ** vector might be different from the order in the index. In that case, + ** we need to reorder the LHS values to be in index order. Run Affinity + ** before reordering the columns, so that the affinity is correct. + */ + sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); + for(i=0; i<nVector && aiMap[i]==i; i++){} /* Are LHS fields reordered? */ + if( i!=nVector ){ + /* Need to reorder the LHS fields according to aiMap */ + int rLhsOrig = rLhs; + rLhs = sqlite3GetTempRange(pParse, nVector); + for(i=0; i<nVector; i++){ + sqlite3VdbeAddOp3(v, OP_Copy, rLhsOrig+i, rLhs+aiMap[i], 0); + } + sqlite3ReleaseTempReg(pParse, rLhsOrig); + } + } + + /* Step 2: Check to see if the LHS contains any NULL columns. If the ** LHS does contain NULLs then the result must be either FALSE or NULL. ** We will then skip the binary search of the RHS. @@ -114417,11 +115595,11 @@ static void sqlite3ExprCodeIN( /* In this case, the RHS is the ROWID of table b-tree and so we also ** know that the RHS is non-NULL. Hence, we combine steps 3 and 4 ** into a single opcode. */ + assert( nVector==1 ); sqlite3VdbeAddOp3(v, OP_SeekRowid, iTab, destIfFalse, rLhs); VdbeCoverage(v); addrTruthOp = sqlite3VdbeAddOp0(v, OP_Goto); /* Return True */ }else{ - sqlite3VdbeAddOp4(v, OP_Affinity, rLhs, nVector, 0, zAff, nVector); if( destIfFalse==destIfNull ){ /* Combine Step 3 and Step 5 into a single opcode */ if( ExprHasProperty(pExpr, EP_Subrtn) ){ @@ -114499,7 +115677,6 @@ static void sqlite3ExprCodeIN( sqlite3VdbeJumpHere(v, addrTruthOp); sqlite3ExprCodeIN_finished: - if( rLhs!=rLhsOrig ) sqlite3ReleaseTempReg(pParse, rLhs); VdbeComment((v, "end IN expr")); sqlite3ExprCodeIN_oom_error: sqlite3DbFree(pParse->db, aiMap); @@ -114614,7 +115791,12 @@ SQLITE_PRIVATE void sqlite3ExprCodeGeneratedColumn( iAddr = 0; } sqlite3ExprCodeCopy(pParse, sqlite3ColumnExpr(pTab,pCol), regOut); - if( pCol->affinity>=SQLITE_AFF_TEXT ){ + if( (pCol->colFlags & COLFLAG_VIRTUAL)!=0 + && (pTab->tabFlags & TF_Strict)!=0 + ){ + int p3 = 2+(int)(pCol - pTab->aCol); + sqlite3VdbeAddOp4(v, OP_TypeCheck, regOut, 1, p3, (char*)pTab, P4_TABLE); + }else if( pCol->affinity>=SQLITE_AFF_TEXT ){ sqlite3VdbeAddOp4(v, OP_Affinity, regOut, 1, 0, &pCol->affinity, 1); } if( iAddr ) sqlite3VdbeJumpHere(v, iAddr); @@ -115052,6 +116234,80 @@ static int exprPartidxExprLookup(Parse *pParse, Expr *pExpr, int iTarget){ return 0; } +/* +** Generate code that evaluates an AND or OR operator leaving a +** boolean result in a register. pExpr is the AND/OR expression. +** Store the result in the "target" register. Use short-circuit +** evaluation to avoid computing both operands, if possible. +** +** The code generated might require the use of a temporary register. +** If it does, then write the number of that temporary register +** into *pTmpReg. If not, leave *pTmpReg unchanged. +*/ +static SQLITE_NOINLINE int exprCodeTargetAndOr( + Parse *pParse, /* Parsing context */ + Expr *pExpr, /* AND or OR expression to be coded */ + int target, /* Put result in this register, guaranteed */ + int *pTmpReg /* Write a temporary register here */ +){ + int op; /* The opcode. TK_AND or TK_OR */ + int skipOp; /* Opcode for the branch that skips one operand */ + int addrSkip; /* Branch instruction that skips one of the operands */ + int regSS = 0; /* Register holding computed operand when other omitted */ + int r1, r2; /* Registers for left and right operands, respectively */ + Expr *pAlt; /* Alternative, simplified expression */ + Vdbe *v; /* statement being coded */ + + assert( pExpr!=0 ); + op = pExpr->op; + assert( op==TK_AND || op==TK_OR ); + assert( TK_AND==OP_And ); testcase( op==TK_AND ); + assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + assert( pParse->pVdbe!=0 ); + v = pParse->pVdbe; + pAlt = sqlite3ExprSimplifiedAndOr(pExpr); + if( pAlt!=pExpr ){ + r1 = sqlite3ExprCodeTarget(pParse, pAlt, target); + sqlite3VdbeAddOp3(v, OP_And, r1, r1, target); + return target; + } + skipOp = op==TK_AND ? OP_IfNot : OP_If; + if( exprEvalRhsFirst(pExpr) ){ + /* Compute the right operand first. Skip the computation of the left + ** operand if the right operand fully determines the result */ + r2 = regSS = sqlite3ExprCodeTarget(pParse, pExpr->pRight, target); + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r2); + VdbeComment((v, "skip left operand")); + VdbeCoverage(v); + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, pTmpReg); + }else{ + /* Compute the left operand first */ + r1 = sqlite3ExprCodeTarget(pParse, pExpr->pLeft, target); + if( ExprHasProperty(pExpr->pRight, EP_Subquery) ){ + /* Skip over the computation of the right operand if the right + ** operand is a subquery and the left operand completely determines + ** the result */ + regSS = r1; + addrSkip = sqlite3VdbeAddOp1(v, skipOp, r1); + VdbeComment((v, "skip right operand")); + VdbeCoverage(v); + }else{ + addrSkip = regSS = 0; + } + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, pTmpReg); + } + sqlite3VdbeAddOp3(v, op, r2, r1, target); + testcase( (*pTmpReg)==0 ); + if( addrSkip ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrSkip); + sqlite3VdbeAddOp3(v, OP_Or, regSS, regSS, target); + VdbeComment((v, "short-circut value")); + } + return target; +} + + /* ** Generate code into the current Vdbe to evaluate the given @@ -115307,11 +116563,17 @@ expr_code_doover: case TK_NE: case TK_EQ: { Expr *pLeft = pExpr->pLeft; + int addrIsNull = 0; if( sqlite3ExprIsVector(pLeft) ){ codeVectorCompare(pParse, pExpr, target, op, p5); }else{ - r1 = sqlite3ExprCodeTemp(pParse, pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) && p5!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + } sqlite3VdbeAddOp2(v, OP_Integer, 1, inReg); codeCompare(pParse, pLeft, pExpr->pRight, op, r1, r2, sqlite3VdbeCurrentAddr(v)+2, p5, @@ -115326,6 +116588,11 @@ expr_code_doover: sqlite3VdbeAddOp2(v, OP_Integer, 0, inReg); }else{ sqlite3VdbeAddOp3(v, OP_ZeroOrNull, r1, inReg, r2); + if( addrIsNull ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrIsNull); + sqlite3VdbeAddOp2(v, OP_Null, 0, inReg); + } } testcase( regFree1==0 ); testcase( regFree2==0 ); @@ -115333,7 +116600,10 @@ expr_code_doover: break; } case TK_AND: - case TK_OR: + case TK_OR: { + inReg = exprCodeTargetAndOr(pParse, pExpr, target, ®Free1); + break; + } case TK_PLUS: case TK_STAR: case TK_MINUS: @@ -115344,8 +116614,7 @@ expr_code_doover: case TK_LSHIFT: case TK_RSHIFT: case TK_CONCAT: { - assert( TK_AND==OP_And ); testcase( op==TK_AND ); - assert( TK_OR==OP_Or ); testcase( op==TK_OR ); + int addrIsNull; assert( TK_PLUS==OP_Add ); testcase( op==TK_PLUS ); assert( TK_MINUS==OP_Subtract ); testcase( op==TK_MINUS ); assert( TK_REM==OP_Remainder ); testcase( op==TK_REM ); @@ -115355,11 +116624,23 @@ expr_code_doover: assert( TK_LSHIFT==OP_ShiftLeft ); testcase( op==TK_LSHIFT ); assert( TK_RSHIFT==OP_ShiftRight ); testcase( op==TK_RSHIFT ); assert( TK_CONCAT==OP_Concat ); testcase( op==TK_CONCAT ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } sqlite3VdbeAddOp3(v, op, r2, r1, target); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + sqlite3VdbeAddOp2(v, OP_Goto, 0, sqlite3VdbeCurrentAddr(v)+2); + sqlite3VdbeJumpHere(v, addrIsNull); + sqlite3VdbeAddOp2(v, OP_Null, 0, target); + VdbeComment((v, "short-circut value")); + } break; } case TK_UMINUS: { @@ -116227,17 +117508,27 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); if( pAlt!=pExpr ){ sqlite3ExprIfTrue(pParse, pAlt, dest, jumpIfNull); - }else if( op==TK_AND ){ - int d2 = sqlite3VdbeMakeLabel(pParse); - testcase( jumpIfNull==0 ); - sqlite3ExprIfFalse(pParse, pExpr->pLeft, d2, - jumpIfNull^SQLITE_JUMPIFNULL); - sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); - sqlite3VdbeResolveLabel(v, d2); }else{ - testcase( jumpIfNull==0 ); - sqlite3ExprIfTrue(pParse, pExpr->pLeft, dest, jumpIfNull); - sqlite3ExprIfTrue(pParse, pExpr->pRight, dest, jumpIfNull); + Expr *pFirst, *pSecond; + if( exprEvalRhsFirst(pExpr) ){ + pFirst = pExpr->pRight; + pSecond = pExpr->pLeft; + }else{ + pFirst = pExpr->pLeft; + pSecond = pExpr->pRight; + } + if( op==TK_AND ){ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pFirst, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + }else{ + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pFirst, dest, jumpIfNull); + sqlite3ExprIfTrue(pParse, pSecond, dest, jumpIfNull); + } } break; } @@ -116276,10 +117567,16 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int case TK_GE: case TK_NE: case TK_EQ: { + int addrIsNull; if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; - testcase( jumpIfNull==0 ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, jumpIfNull, ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); @@ -116294,6 +117591,13 @@ SQLITE_PRIVATE void sqlite3ExprIfTrue(Parse *pParse, Expr *pExpr, int dest, int VdbeCoverageIf(v, op==OP_Ne && jumpIfNull!=SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + if( jumpIfNull ){ + sqlite3VdbeChangeP2(v, addrIsNull, dest); + }else{ + sqlite3VdbeJumpHere(v, addrIsNull); + } + } break; } case TK_ISNULL: @@ -116401,17 +117705,27 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int Expr *pAlt = sqlite3ExprSimplifiedAndOr(pExpr); if( pAlt!=pExpr ){ sqlite3ExprIfFalse(pParse, pAlt, dest, jumpIfNull); - }else if( pExpr->op==TK_AND ){ - testcase( jumpIfNull==0 ); - sqlite3ExprIfFalse(pParse, pExpr->pLeft, dest, jumpIfNull); - sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); }else{ - int d2 = sqlite3VdbeMakeLabel(pParse); - testcase( jumpIfNull==0 ); - sqlite3ExprIfTrue(pParse, pExpr->pLeft, d2, - jumpIfNull^SQLITE_JUMPIFNULL); - sqlite3ExprIfFalse(pParse, pExpr->pRight, dest, jumpIfNull); - sqlite3VdbeResolveLabel(v, d2); + Expr *pFirst, *pSecond; + if( exprEvalRhsFirst(pExpr) ){ + pFirst = pExpr->pRight; + pSecond = pExpr->pLeft; + }else{ + pFirst = pExpr->pLeft; + pSecond = pExpr->pRight; + } + if( pExpr->op==TK_AND ){ + testcase( jumpIfNull==0 ); + sqlite3ExprIfFalse(pParse, pFirst, dest, jumpIfNull); + sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull); + }else{ + int d2 = sqlite3VdbeMakeLabel(pParse); + testcase( jumpIfNull==0 ); + sqlite3ExprIfTrue(pParse, pFirst, d2, + jumpIfNull^SQLITE_JUMPIFNULL); + sqlite3ExprIfFalse(pParse, pSecond, dest, jumpIfNull); + sqlite3VdbeResolveLabel(v, d2); + } } break; } @@ -116453,10 +117767,16 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int case TK_GE: case TK_NE: case TK_EQ: { + int addrIsNull; if( sqlite3ExprIsVector(pExpr->pLeft) ) goto default_expr; - testcase( jumpIfNull==0 ); - r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); - r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + if( ExprHasProperty(pExpr, EP_Subquery) && jumpIfNull!=SQLITE_NULLEQ ){ + addrIsNull = exprComputeOperands(pParse, pExpr, + &r1, &r2, ®Free1, ®Free2); + }else{ + r1 = sqlite3ExprCodeTemp(pParse, pExpr->pLeft, ®Free1); + r2 = sqlite3ExprCodeTemp(pParse, pExpr->pRight, ®Free2); + addrIsNull = 0; + } codeCompare(pParse, pExpr->pLeft, pExpr->pRight, op, r1, r2, dest, jumpIfNull,ExprHasProperty(pExpr,EP_Commuted)); assert(TK_LT==OP_Lt); testcase(op==OP_Lt); VdbeCoverageIf(v,op==OP_Lt); @@ -116471,6 +117791,13 @@ SQLITE_PRIVATE void sqlite3ExprIfFalse(Parse *pParse, Expr *pExpr, int dest, int VdbeCoverageIf(v, op==OP_Ne && jumpIfNull==SQLITE_NULLEQ); testcase( regFree1==0 ); testcase( regFree2==0 ); + if( addrIsNull ){ + if( jumpIfNull ){ + sqlite3VdbeChangeP2(v, addrIsNull, dest); + }else{ + sqlite3VdbeJumpHere(v, addrIsNull); + } + } break; } case TK_ISNULL: @@ -123436,6 +124763,16 @@ SQLITE_PRIVATE Table *sqlite3LocateTable( if( pMod==0 && sqlite3_strnicmp(zName, "pragma_", 7)==0 ){ pMod = sqlite3PragmaVtabRegister(db, zName); } +#ifndef SQLITE_OMIT_JSON + if( pMod==0 && sqlite3_strnicmp(zName, "json", 4)==0 ){ + pMod = sqlite3JsonVtabRegister(db, zName); + } +#endif +#ifdef SQLITE_ENABLE_CARRAY + if( pMod==0 && sqlite3_stricmp(zName, "carray")==0 ){ + pMod = sqlite3CarrayRegister(db); + } +#endif if( pMod && sqlite3VtabEponymousTableInit(pParse, pMod) ){ testcase( pMod->pEpoTab==0 ); return pMod->pEpoTab; @@ -124074,7 +125411,7 @@ SQLITE_PRIVATE int sqlite3TableColumnToIndex(Index *pIdx, int iCol){ int i; i16 iCol16; assert( iCol>=(-1) && iCol<=SQLITE_MAX_COLUMN ); - assert( pIdx->nColumn<=SQLITE_MAX_COLUMN+1 ); + assert( pIdx->nColumn<=SQLITE_MAX_COLUMN*2 ); iCol16 = iCol; for(i=0; i<pIdx->nColumn; i++){ if( iCol16==pIdx->aiColumn[i] ){ @@ -124371,6 +125708,9 @@ SQLITE_PRIVATE void sqlite3StartTable( sqlite3VdbeAddOp3(v, OP_Insert, 0, reg3, reg1); sqlite3VdbeChangeP5(v, OPFLAG_APPEND); sqlite3VdbeAddOp0(v, OP_Close); + }else if( db->init.imposterTable ){ + pTable->tabFlags |= TF_Imposter; + if( db->init.imposterTable>=2 ) pTable->tabFlags |= TF_Readonly; } /* Normal (non-error) return. */ @@ -128140,16 +129480,22 @@ SQLITE_PRIVATE void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pI ** are deleted by this function. */ SQLITE_PRIVATE SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){ - assert( p1 && p1->nSrc==1 ); + assert( p1 ); + assert( p2 || pParse->nErr ); + assert( p2==0 || p2->nSrc>=1 ); + testcase( p1->nSrc==0 ); if( p2 ){ - SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, 1); + int nOld = p1->nSrc; + SrcList *pNew = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, nOld); if( pNew==0 ){ sqlite3SrcListDelete(pParse->db, p2); }else{ p1 = pNew; - memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(SrcItem)); + memcpy(&p1->a[nOld], p2->a, p2->nSrc*sizeof(SrcItem)); + assert( nOld==1 || (p2->a[0].fg.jointype & JT_LTORJ)==0 ); + assert( p1->nSrc>=1 ); + p1->a[0].fg.jointype |= (JT_LTORJ & p2->a[0].fg.jointype); sqlite3DbFree(pParse->db, p2); - p1->a[0].fg.jointype |= (JT_LTORJ & p1->a[1].fg.jointype); } } return p1; @@ -128660,14 +130006,19 @@ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoOfIndex(Parse *pParse, Index *pIdx){ } if( pParse->nErr ){ assert( pParse->rc==SQLITE_ERROR_MISSING_COLLSEQ ); - if( pIdx->bNoQuery==0 ){ + if( pIdx->bNoQuery==0 + && sqlite3HashFind(&pIdx->pSchema->idxHash, pIdx->zName) + ){ /* Deactivate the index because it contains an unknown collating ** sequence. The only way to reactive the index is to reload the ** schema. Adding the missing collating sequence later does not ** reactive the index. The application had the chance to register ** the missing index using the collation-needed callback. For ** simplicity, SQLite will not give the application a second chance. - */ + ** + ** Except, do not do this if the index is not in the schema hash + ** table. In this case the index is currently being constructed + ** by a CREATE INDEX statement, and retrying will not help. */ pIdx->bNoQuery = 1; pParse->rc = SQLITE_ERROR_RETRY; } @@ -130864,7 +132215,7 @@ static void *contextMalloc(sqlite3_context *context, i64 nByte){ sqlite3 *db = sqlite3_context_db_handle(context); assert( nByte>0 ); testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH] ); - testcase( nByte==db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); + testcase( nByte==(i64)db->aLimit[SQLITE_LIMIT_LENGTH]+1 ); if( nByte>db->aLimit[SQLITE_LIMIT_LENGTH] ){ sqlite3_result_error_toobig(context); z = 0; @@ -131535,7 +132886,7 @@ SQLITE_PRIVATE void sqlite3QuoteValue(StrAccum *pStr, sqlite3_value *pValue, int */ static int isNHex(const char *z, int N, u32 *pVal){ int i; - int v = 0; + u32 v = 0; for(i=0; i<N; i++){ if( !sqlite3Isxdigit(z[i]) ) return 0; v = (v<<4) + sqlite3HexToInt(z[i]); @@ -132048,6 +133399,7 @@ static void concatFuncCore( ){ i64 j, n = 0; int i; + int bNotNull = 0; /* True after at least NOT NULL argument seen */ char *z; for(i=0; i<argc; i++){ n += sqlite3_value_bytes(argv[i]); @@ -132064,12 +133416,13 @@ static void concatFuncCore( int k = sqlite3_value_bytes(argv[i]); const char *v = (const char*)sqlite3_value_text(argv[i]); if( v!=0 ){ - if( j>0 && nSep>0 ){ + if( bNotNull && nSep>0 ){ memcpy(&z[j], zSep, nSep); j += nSep; } memcpy(&z[j], v, k); j += k; + bNotNull = 1; } } } @@ -133015,6 +134368,502 @@ static void signFunc( sqlite3_result_int(context, x<0.0 ? -1 : x>0.0 ? +1 : 0); } +#if defined(SQLITE_ENABLE_PERCENTILE) +/*********************************************************************** +** This section implements the percentile(Y,P) SQL function and similar. +** Requirements: +** +** (1) The percentile(Y,P) function is an aggregate function taking +** exactly two arguments. +** +** (2) If the P argument to percentile(Y,P) is not the same for every +** row in the aggregate then an error is thrown. The word "same" +** in the previous sentence means that the value differ by less +** than 0.001. +** +** (3) If the P argument to percentile(Y,P) evaluates to anything other +** than a number in the range of 0.0 to 100.0 inclusive then an +** error is thrown. +** +** (4) If any Y argument to percentile(Y,P) evaluates to a value that +** is not NULL and is not numeric then an error is thrown. +** +** (5) If any Y argument to percentile(Y,P) evaluates to plus or minus +** infinity then an error is thrown. (SQLite always interprets NaN +** values as NULL.) +** +** (6) Both Y and P in percentile(Y,P) can be arbitrary expressions, +** including CASE WHEN expressions. +** +** (7) The percentile(Y,P) aggregate is able to handle inputs of at least +** one million (1,000,000) rows. +** +** (8) If there are no non-NULL values for Y, then percentile(Y,P) +** returns NULL. +** +** (9) If there is exactly one non-NULL value for Y, the percentile(Y,P) +** returns the one Y value. +** +** (10) If there N non-NULL values of Y where N is two or more and +** the Y values are ordered from least to greatest and a graph is +** drawn from 0 to N-1 such that the height of the graph at J is +** the J-th Y value and such that straight lines are drawn between +** adjacent Y values, then the percentile(Y,P) function returns +** the height of the graph at P*(N-1)/100. +** +** (11) The percentile(Y,P) function always returns either a floating +** point number or NULL. +** +** (12) The percentile(Y,P) is implemented as a single C99 source-code +** file that compiles into a shared-library or DLL that can be loaded +** into SQLite using the sqlite3_load_extension() interface. +** +** (13) A separate median(Y) function is the equivalent percentile(Y,50). +** +** (14) A separate percentile_cont(Y,P) function is equivalent to +** percentile(Y,P/100.0). In other words, the fraction value in +** the second argument is in the range of 0 to 1 instead of 0 to 100. +** +** (15) A separate percentile_disc(Y,P) function is like +** percentile_cont(Y,P) except that instead of returning the weighted +** average of the nearest two input values, it returns the next lower +** value. So the percentile_disc(Y,P) will always return a value +** that was one of the inputs. +** +** (16) All of median(), percentile(Y,P), percentile_cont(Y,P) and +** percentile_disc(Y,P) can be used as window functions. +** +** Differences from standard SQL: +** +** * The percentile_cont(X,P) function is equivalent to the following in +** standard SQL: +** +** (percentile_cont(P) WITHIN GROUP (ORDER BY X)) +** +** The SQLite syntax is much more compact. The standard SQL syntax +** is also supported if SQLite is compiled with the +** -DSQLITE_ENABLE_ORDERED_SET_AGGREGATES option. +** +** * No median(X) function exists in the SQL standard. App developers +** are expected to write "percentile_cont(0.5)WITHIN GROUP(ORDER BY X)". +** +** * No percentile(Y,P) function exists in the SQL standard. Instead of +** percential(Y,P), developers must write this: +** "percentile_cont(P/100.0) WITHIN GROUP (ORDER BY Y)". Note that +** the fraction parameter to percentile() goes from 0 to 100 whereas +** the fraction parameter in SQL standard percentile_cont() goes from +** 0 to 1. +** +** Implementation notes as of 2024-08-31: +** +** * The regular aggregate-function versions of these routines work +** by accumulating all values in an array of doubles, then sorting +** that array using quicksort before computing the answer. Thus +** the runtime is O(NlogN) where N is the number of rows of input. +** +** * For the window-function versions of these routines, the array of +** inputs is sorted as soon as the first value is computed. Thereafter, +** the array is kept in sorted order using an insert-sort. This +** results in O(N*K) performance where K is the size of the window. +** One can imagine alternative implementations that give O(N*logN*logK) +** performance, but they require more complex logic and data structures. +** The developers have elected to keep the asymptotically slower +** algorithm for now, for simplicity, under the theory that window +** functions are seldom used and when they are, the window size K is +** often small. The developers might revisit that decision later, +** should the need arise. +*/ + +/* The following object is the group context for a single percentile() +** aggregate. Remember all input Y values until the very end. +** Those values are accumulated in the Percentile.a[] array. +*/ +typedef struct Percentile Percentile; +struct Percentile { + u64 nAlloc; /* Number of slots allocated for a[] */ + u64 nUsed; /* Number of slots actually used in a[] */ + char bSorted; /* True if a[] is already in sorted order */ + char bKeepSorted; /* True if advantageous to keep a[] sorted */ + char bPctValid; /* True if rPct is valid */ + double rPct; /* Fraction. 0.0 to 1.0 */ + double *a; /* Array of Y values */ +}; + +/* +** Return TRUE if the input floating-point number is an infinity. +*/ +static int percentIsInfinity(double r){ + sqlite3_uint64 u; + assert( sizeof(u)==sizeof(r) ); + memcpy(&u, &r, sizeof(u)); + return ((u>>52)&0x7ff)==0x7ff; +} + +/* +** Return TRUE if two doubles differ by 0.001 or less. +*/ +static int percentSameValue(double a, double b){ + a -= b; + return a>=-0.001 && a<=0.001; +} + +/* +** Search p (which must have p->bSorted) looking for an entry with +** value y. Return the index of that entry. +** +** If bExact is true, return -1 if the entry is not found. +** +** If bExact is false, return the index at which a new entry with +** value y should be insert in order to keep the values in sorted +** order. The smallest return value in this case will be 0, and +** the largest return value will be p->nUsed. +*/ +static i64 percentBinarySearch(Percentile *p, double y, int bExact){ + i64 iFirst = 0; /* First element of search range */ + i64 iLast = (i64)p->nUsed - 1; /* Last element of search range */ + while( iLast>=iFirst ){ + i64 iMid = (iFirst+iLast)/2; + double x = p->a[iMid]; + if( x<y ){ + iFirst = iMid + 1; + }else if( x>y ){ + iLast = iMid - 1; + }else{ + return iMid; + } + } + if( bExact ) return -1; + return iFirst; +} + +/* +** Generate an error for a percentile function. +** +** The error format string must have exactly one occurrence of "%%s()" +** (with two '%' characters). That substring will be replaced by the name +** of the function. +*/ +static void percentError(sqlite3_context *pCtx, const char *zFormat, ...){ + char *zMsg1; + char *zMsg2; + va_list ap; + + va_start(ap, zFormat); + zMsg1 = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + zMsg2 = zMsg1 ? sqlite3_mprintf(zMsg1, sqlite3VdbeFuncName(pCtx)) : 0; + sqlite3_result_error(pCtx, zMsg2, -1); + sqlite3_free(zMsg1); + sqlite3_free(zMsg2); +} + +/* +** The "step" function for percentile(Y,P) is called once for each +** input row. +*/ +static void percentStep(sqlite3_context *pCtx, int argc, sqlite3_value **argv){ + Percentile *p; + double rPct; + int eType; + double y; + assert( argc==2 || argc==1 ); + + if( argc==1 ){ + /* Requirement 13: median(Y) is the same as percentile(Y,50). */ + rPct = 0.5; + }else{ + /* P must be a number between 0 and 100 for percentile() or between + ** 0.0 and 1.0 for percentile_cont() and percentile_disc(). + ** + ** The user-data is an integer which is 10 times the upper bound. + */ + double mxFrac = (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&2)? 100.0 : 1.0; + eType = sqlite3_value_numeric_type(argv[1]); + rPct = sqlite3_value_double(argv[1])/mxFrac; + if( (eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT) + || rPct<0.0 || rPct>1.0 + ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not between 0.0 and %.1f", + (double)mxFrac); + return; + } + } + + /* Allocate the session context. */ + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + if( p==0 ) return; + + /* Remember the P value. Throw an error if the P value is different + ** from any prior row, per Requirement (2). */ + if( !p->bPctValid ){ + p->rPct = rPct; + p->bPctValid = 1; + }else if( !percentSameValue(p->rPct,rPct) ){ + percentError(pCtx, "the fraction argument to %%s()" + " is not the same for all input rows"); + return; + } + + /* Ignore rows for which Y is NULL */ + eType = sqlite3_value_type(argv[0]); + if( eType==SQLITE_NULL ) return; + + /* If not NULL, then Y must be numeric. Otherwise throw an error. + ** Requirement 4 */ + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ + percentError(pCtx, "input to %%s() is not numeric"); + return; + } + + /* Throw an error if the Y value is infinity or NaN */ + y = sqlite3_value_double(argv[0]); + if( percentIsInfinity(y) ){ + percentError(pCtx, "Inf input to %%s()"); + return; + } + + /* Allocate and store the Y */ + if( p->nUsed>=p->nAlloc ){ + u64 n = p->nAlloc*2 + 250; + double *a = sqlite3_realloc64(p->a, sizeof(double)*n); + if( a==0 ){ + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); + sqlite3_result_error_nomem(pCtx); + return; + } + p->nAlloc = n; + p->a = a; + } + if( p->nUsed==0 ){ + p->a[p->nUsed++] = y; + p->bSorted = 1; + }else if( !p->bSorted || y>=p->a[p->nUsed-1] ){ + p->a[p->nUsed++] = y; + }else if( p->bKeepSorted ){ + i64 i; + i = percentBinarySearch(p, y, 0); + if( i<(int)p->nUsed ){ + memmove(&p->a[i+1], &p->a[i], (p->nUsed-i)*sizeof(p->a[0])); + } + p->a[i] = y; + p->nUsed++; + }else{ + p->a[p->nUsed++] = y; + p->bSorted = 0; + } +} + +/* +** Interchange two doubles. +*/ +#define SWAP_DOUBLE(X,Y) {double ttt=(X);(X)=(Y);(Y)=ttt;} + +/* +** Sort an array of doubles. +** +** Algorithm: quicksort +** +** This is implemented separately rather than using the qsort() routine +** from the standard library because: +** +** (1) To avoid a dependency on qsort() +** (2) To avoid the function call to the comparison routine for each +** comparison. +*/ +static void percentSort(double *a, unsigned int n){ + int iLt; /* Entries before a[iLt] are less than rPivot */ + int iGt; /* Entries at or after a[iGt] are greater than rPivot */ + int i; /* Loop counter */ + double rPivot; /* The pivot value */ + + assert( n>=2 ); + if( a[0]>a[n-1] ){ + SWAP_DOUBLE(a[0],a[n-1]) + } + if( n==2 ) return; + iGt = n-1; + i = n/2; + if( a[0]>a[i] ){ + SWAP_DOUBLE(a[0],a[i]) + }else if( a[i]>a[iGt] ){ + SWAP_DOUBLE(a[i],a[iGt]) + } + if( n==3 ) return; + rPivot = a[i]; + iLt = i = 1; + do{ + if( a[i]<rPivot ){ + if( i>iLt ) SWAP_DOUBLE(a[i],a[iLt]) + iLt++; + i++; + }else if( a[i]>rPivot ){ + do{ + iGt--; + }while( iGt>i && a[iGt]>rPivot ); + SWAP_DOUBLE(a[i],a[iGt]) + }else{ + i++; + } + }while( i<iGt ); + if( iLt>=2 ) percentSort(a, iLt); + if( n-iGt>=2 ) percentSort(a+iGt, n-iGt); + +/* Uncomment for testing */ +#if 0 + for(i=0; i<n-1; i++){ + assert( a[i]<=a[i+1] ); + } +#endif +} + + +/* +** The "inverse" function for percentile(Y,P) is called to remove a +** row that was previously inserted by "step". +*/ +static void percentInverse(sqlite3_context *pCtx,int argc,sqlite3_value **argv){ + Percentile *p; + int eType; + double y; + i64 i; + assert( argc==2 || argc==1 ); + + /* Allocate the session context. */ + p = (Percentile*)sqlite3_aggregate_context(pCtx, sizeof(*p)); + assert( p!=0 ); + + /* Ignore rows for which Y is NULL */ + eType = sqlite3_value_type(argv[0]); + if( eType==SQLITE_NULL ) return; + + /* If not NULL, then Y must be numeric. Otherwise throw an error. + ** Requirement 4 */ + if( eType!=SQLITE_INTEGER && eType!=SQLITE_FLOAT ){ + return; + } + + /* Ignore the Y value if it is infinity or NaN */ + y = sqlite3_value_double(argv[0]); + if( percentIsInfinity(y) ){ + return; + } + if( p->bSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + p->bKeepSorted = 1; + + /* Find and remove the row */ + i = percentBinarySearch(p, y, 1); + if( i>=0 ){ + p->nUsed--; + if( i<(int)p->nUsed ){ + memmove(&p->a[i], &p->a[i+1], (p->nUsed - i)*sizeof(p->a[0])); + } + } +} + +/* +** Compute the final output of percentile(). Clean up all allocated +** memory if and only if bIsFinal is true. +*/ +static void percentCompute(sqlite3_context *pCtx, int bIsFinal){ + Percentile *p; + int settings = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx))&1; /* Discrete? */ + unsigned i1, i2; + double v1, v2; + double ix, vx; + p = (Percentile*)sqlite3_aggregate_context(pCtx, 0); + if( p==0 ) return; + if( p->a==0 ) return; + if( p->nUsed ){ + if( p->bSorted==0 ){ + assert( p->nUsed>1 ); + percentSort(p->a, p->nUsed); + p->bSorted = 1; + } + ix = p->rPct*(p->nUsed-1); + i1 = (unsigned)ix; + if( settings & 1 ){ + vx = p->a[i1]; + }else{ + i2 = ix==(double)i1 || i1==p->nUsed-1 ? i1 : i1+1; + v1 = p->a[i1]; + v2 = p->a[i2]; + vx = v1 + (v2-v1)*(ix-i1); + } + sqlite3_result_double(pCtx, vx); + } + if( bIsFinal ){ + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); + }else{ + p->bKeepSorted = 1; + } +} +static void percentFinal(sqlite3_context *pCtx){ + percentCompute(pCtx, 1); +} +static void percentValue(sqlite3_context *pCtx){ + percentCompute(pCtx, 0); +} +/****** End of percentile family of functions ******/ +#endif /* SQLITE_ENABLE_PERCENTILE */ + +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) +/* +** Implementation of sqlite_filestat(SCHEMA). +** +** Return JSON text that describes low-level debug/diagnostic information +** about the sqlite3_file object associated with SCHEMA. +*/ +static void filestatFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zDbName; + sqlite3_str *pStr; + Btree *pBtree; + + zDbName = (const char*)sqlite3_value_text(argv[0]); + pBtree = sqlite3DbNameToBtree(db, zDbName); + if( pBtree ){ + Pager *pPager; + sqlite3_file *fd; + int rc; + sqlite3BtreeEnter(pBtree); + pPager = sqlite3BtreePager(pBtree); + assert( pPager!=0 ); + fd = sqlite3PagerFile(pPager); + pStr = sqlite3_str_new(db); + if( pStr==0 ){ + sqlite3_result_error_nomem(context); + }else{ + sqlite3_str_append(pStr, "{\"db\":", 6); + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr); + if( rc ) sqlite3_str_append(pStr, "null", 4); + fd = sqlite3PagerJrnlFile(pPager); + if( fd && fd->pMethods!=0 ){ + sqlite3_str_appendall(pStr, ",\"journal\":"); + rc = sqlite3OsFileControl(fd, SQLITE_FCNTL_FILESTAT, pStr); + if( rc ) sqlite3_str_append(pStr, "null", 4); + } + sqlite3_str_append(pStr, "}", 1); + sqlite3_result_text(context, sqlite3_str_finish(pStr), -1, + sqlite3_free); + } + sqlite3BtreeLeave(pBtree); + }else{ + sqlite3_result_text(context, "{}", 2, SQLITE_STATIC); + } +} +#endif /* SQLITE_DEBUG || SQLITE_ENABLE_FILESTAT */ + #ifdef SQLITE_DEBUG /* ** Implementation of fpdecode(x,y,z) function. @@ -133173,6 +135022,9 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ #ifdef SQLITE_ENABLE_OFFSET_SQL_FUNC INLINE_FUNC(sqlite_offset, 1, INLINEFUNC_sqlite_offset, 0 ), #endif +#if defined(SQLITE_DEBUG) || defined(SQLITE_ENABLE_FILESTAT) + FUNCTION(sqlite_filestat, 1, 0, 0, filestatFunc ), +#endif FUNCTION(ltrim, 1, 1, 0, trimFunc ), FUNCTION(ltrim, 2, 1, 0, trimFunc ), FUNCTION(rtrim, 1, 2, 0, trimFunc ), @@ -133245,6 +135097,21 @@ SQLITE_PRIVATE void sqlite3RegisterBuiltinFunctions(void){ WAGGREGATE(string_agg, 2, 0, 0, groupConcatStep, groupConcatFinalize, groupConcatValue, groupConcatInverse, 0), +#ifdef SQLITE_ENABLE_PERCENTILE + WAGGREGATE(median, 1, 0,0, percentStep, + percentFinal, percentValue, percentInverse, + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), + WAGGREGATE(percentile, 2, 0x2,0, percentStep, + percentFinal, percentValue, percentInverse, + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), + WAGGREGATE(percentile_cont, 2, 0,0, percentStep, + percentFinal, percentValue, percentInverse, + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), + WAGGREGATE(percentile_disc, 2, 0x1,0, percentStep, + percentFinal, percentValue, percentInverse, + SQLITE_INNOCUOUS|SQLITE_SELFORDER1), +#endif /* SQLITE_ENABLE_PERCENTILE */ + LIKEFUNC(glob, 2, &globInfo, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), #ifdef SQLITE_CASE_SENSITIVE_LIKE LIKEFUNC(like, 2, &likeInfoAlt, SQLITE_FUNC_LIKE|SQLITE_FUNC_CASE), @@ -134999,12 +136866,15 @@ SQLITE_PRIVATE void sqlite3TableAffinity(Vdbe *v, Table *pTab, int iReg){ ** by one slot and insert a new OP_TypeCheck where the current ** OP_MakeRecord is found */ VdbeOp *pPrev; + int p3; sqlite3VdbeAppendP4(v, pTab, P4_TABLE); pPrev = sqlite3VdbeGetLastOp(v); assert( pPrev!=0 ); assert( pPrev->opcode==OP_MakeRecord || sqlite3VdbeDb(v)->mallocFailed ); pPrev->opcode = OP_TypeCheck; - sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, pPrev->p3); + p3 = pPrev->p3; + pPrev->p3 = 0; + sqlite3VdbeAddOp3(v, OP_MakeRecord, pPrev->p1, pPrev->p2, p3); }else{ /* Insert an isolated OP_Typecheck */ sqlite3VdbeAddOp2(v, OP_TypeCheck, iReg, pTab->nNVCol); @@ -138739,6 +140609,10 @@ struct sqlite3_api_routines { int (*set_clientdata)(sqlite3*, const char*, void*, void(*)(void*)); /* Version 3.50.0 and later */ int (*setlk_timeout)(sqlite3*,int,int); + /* Version 3.51.0 and later */ + int (*set_errmsg)(sqlite3*,int,const char*); + int (*db_status64)(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); + }; /* @@ -139074,6 +140948,9 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_set_clientdata sqlite3_api->set_clientdata /* Version 3.50.0 and later */ #define sqlite3_setlk_timeout sqlite3_api->setlk_timeout +/* Version 3.51.0 and later */ +#define sqlite3_set_errmsg sqlite3_api->set_errmsg +#define sqlite3_db_status64 sqlite3_api->db_status64 #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) @@ -139597,7 +141474,10 @@ static const sqlite3_api_routines sqlite3Apis = { sqlite3_get_clientdata, sqlite3_set_clientdata, /* Version 3.50.0 and later */ - sqlite3_setlk_timeout + sqlite3_setlk_timeout, + /* Version 3.51.0 and later */ + sqlite3_set_errmsg, + sqlite3_db_status64 }; /* True if x is the directory separator character @@ -141060,6 +142940,22 @@ static int integrityCheckResultRow(Vdbe *v){ } /* +** Should table pTab be skipped when doing an integrity_check? +** Return true or false. +** +** If pObjTab is not null, the return true if pTab matches pObjTab. +** +** If pObjTab is null, then return true only if pTab is an imposter table. +*/ +static int tableSkipIntegrityCheck(const Table *pTab, const Table *pObjTab){ + if( pObjTab ){ + return pTab!=pObjTab; + }else{ + return (pTab->tabFlags & TF_Imposter)!=0; + } +} + +/* ** Process a pragma statement. ** ** Pragmas are of this form: @@ -142404,7 +144300,7 @@ SQLITE_PRIVATE void sqlite3Pragma( Table *pTab = sqliteHashData(x); /* Current table */ Index *pIdx; /* An index on pTab */ int nIdx; /* Number of indexes on pTab */ - if( pObjTab && pObjTab!=pTab ) continue; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( HasRowid(pTab) ) cnt++; for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){ cnt++; } } @@ -142417,7 +144313,7 @@ SQLITE_PRIVATE void sqlite3Pragma( for(x=sqliteHashFirst(pTbls); x; x=sqliteHashNext(x)){ Table *pTab = sqliteHashData(x); Index *pIdx; - if( pObjTab && pObjTab!=pTab ) continue; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( HasRowid(pTab) ) aRoot[++cnt] = pTab->tnum; for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){ aRoot[++cnt] = pIdx->tnum; @@ -142448,7 +144344,7 @@ SQLITE_PRIVATE void sqlite3Pragma( int iTab = 0; Table *pTab = sqliteHashData(x); Index *pIdx; - if( pObjTab && pObjTab!=pTab ) continue; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( HasRowid(pTab) ){ iTab = cnt++; }else{ @@ -142484,7 +144380,7 @@ SQLITE_PRIVATE void sqlite3Pragma( int r2; /* Previous key for WITHOUT ROWID tables */ int mxCol; /* Maximum non-virtual column number */ - if( pObjTab && pObjTab!=pTab ) continue; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( !IsOrdinaryTable(pTab) ) continue; if( isQuick || HasRowid(pTab) ){ pPk = 0; @@ -142808,7 +144704,7 @@ SQLITE_PRIVATE void sqlite3Pragma( Table *pTab = sqliteHashData(x); sqlite3_vtab *pVTab; int a1; - if( pObjTab && pObjTab!=pTab ) continue; + if( tableSkipIntegrityCheck(pTab,pObjTab) ) continue; if( IsOrdinaryTable(pTab) ) continue; if( !IsVirtual(pTab) ) continue; if( pTab->nCol<=0 ){ @@ -143040,6 +144936,8 @@ SQLITE_PRIVATE void sqlite3Pragma( eMode = SQLITE_CHECKPOINT_RESTART; }else if( sqlite3StrICmp(zRight, "truncate")==0 ){ eMode = SQLITE_CHECKPOINT_TRUNCATE; + }else if( sqlite3StrICmp(zRight, "noop")==0 ){ + eMode = SQLITE_CHECKPOINT_NOOP; } } pParse->nMem = 3; @@ -144606,9 +146504,11 @@ static int sqlite3LockAndPrepare( rc = sqlite3Prepare(db, zSql, nBytes, prepFlags, pOld, ppStmt, pzTail); assert( rc==SQLITE_OK || *ppStmt==0 ); if( rc==SQLITE_OK || db->mallocFailed ) break; - }while( (rc==SQLITE_ERROR_RETRY && (cnt++)<SQLITE_MAX_PREPARE_RETRY) - || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt++)==0) ); + cnt++; + }while( (rc==SQLITE_ERROR_RETRY && ALWAYS(cnt<=SQLITE_MAX_PREPARE_RETRY)) + || (rc==SQLITE_SCHEMA && (sqlite3ResetOneSchema(db,-1), cnt)==1) ); sqlite3BtreeLeaveAll(db); + assert( rc!=SQLITE_ERROR_RETRY ); rc = sqlite3ApiExit(db, rc); assert( (rc&db->errMask)==rc ); db->busyHandler.nBusy = 0; @@ -145223,7 +147123,7 @@ static int tableAndColumnIndex( int iEnd, /* Last member of pSrc->a[] to check */ const char *zCol, /* Name of the column we are looking for */ int *piTab, /* Write index of pSrc->a[] here */ - int *piCol, /* Write index of pSrc->a[*piTab].pTab->aCol[] here */ + int *piCol, /* Write index of pSrc->a[*piTab].pSTab->aCol[] here */ int bIgnoreHidden /* Ignore hidden columns */ ){ int i; /* For looping over tables in pSrc */ @@ -145282,8 +147182,7 @@ SQLITE_PRIVATE void sqlite3SetJoinExpr(Expr *p, int iTable, u32 joinFlag){ assert( !ExprHasProperty(p, EP_TokenOnly|EP_Reduced) ); ExprSetVVAProperty(p, EP_NoReduce); p->w.iJoin = iTable; - if( p->op==TK_FUNCTION ){ - assert( ExprUseXList(p) ); + if( ExprUseXList(p) ){ if( p->x.pList ){ int i; for(i=0; i<p->x.pList->nExpr; i++){ @@ -145499,6 +147398,7 @@ static int sqlite3ProcessJoin(Parse *pParse, Select *p){ p->pWhere = sqlite3ExprAnd(pParse, p->pWhere, pRight->u3.pOn); pRight->u3.pOn = 0; pRight->fg.isOn = 1; + p->selFlags |= SF_OnToWhere; } } return 0; @@ -146385,7 +148285,10 @@ static void selectInnerLoop( */ SQLITE_PRIVATE KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){ int nExtra = (N+X)*(sizeof(CollSeq*)+1); - KeyInfo *p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); + KeyInfo *p; + assert( X>=0 ); + if( NEVER(N+X>0xffff) ) return (KeyInfo*)sqlite3OomFault(db); + p = sqlite3DbMallocRawNN(db, SZ_KEYINFO(0) + nExtra); if( p ){ p->aSortFlags = (u8*)&p->aColl[N+X]; p->nKeyField = (u16)N; @@ -146952,6 +148855,10 @@ static void generateColumnTypes( #endif sqlite3VdbeSetColName(v, i, COLNAME_DECLTYPE, zType, SQLITE_TRANSIENT); } +#else + UNUSED_PARAMETER(pParse); + UNUSED_PARAMETER(pTabList); + UNUSED_PARAMETER(pEList); #endif /* !defined(SQLITE_OMIT_DECLTYPE) */ } @@ -147871,8 +149778,10 @@ static int multiSelect( int priorOp; /* The SRT_ operation to apply to prior selects */ Expr *pLimit; /* Saved values of p->nLimit */ int addr; + int emptyBypass = 0; /* IfEmpty opcode to bypass RHS */ SelectDest uniondest; + testcase( p->op==TK_EXCEPT ); testcase( p->op==TK_UNION ); priorOp = SRT_Union; @@ -147910,6 +149819,8 @@ static int multiSelect( */ if( p->op==TK_EXCEPT ){ op = SRT_Except; + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, unionTab); + VdbeCoverage(v); }else{ assert( p->op==TK_UNION ); op = SRT_Union; @@ -147930,6 +149841,7 @@ static int multiSelect( if( p->op==TK_UNION ){ p->nSelectRow = sqlite3LogEstAdd(p->nSelectRow, pPrior->nSelectRow); } + if( emptyBypass ) sqlite3VdbeJumpHere(v, emptyBypass); sqlite3ExprDelete(db, p->pLimit); p->pLimit = pLimit; p->iLimit = 0; @@ -147960,9 +149872,10 @@ static int multiSelect( int tab1, tab2; int iCont, iBreak, iStart; Expr *pLimit; - int addr; + int addr, iLimit, iOffset; SelectDest intersectdest; int r1; + int emptyBypass; /* INTERSECT is different from the others since it requires ** two temporary tables. Hence it has its own case. Begin @@ -147987,14 +149900,28 @@ static int multiSelect( goto multi_select_end; } + /* Initialize LIMIT counters before checking to see if the LHS + ** is empty, in case the jump is taken */ + iBreak = sqlite3VdbeMakeLabel(pParse); + computeLimitRegisters(pParse, p, iBreak); + emptyBypass = sqlite3VdbeAddOp1(v, OP_IfEmpty, tab1); VdbeCoverage(v); + /* Code the current SELECT into temporary table "tab2" */ addr = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, tab2, 0); assert( p->addrOpenEphm[1] == -1 ); p->addrOpenEphm[1] = addr; - p->pPrior = 0; + + /* Disable prior SELECTs and the LIMIT counters during the computation + ** of the RHS select */ pLimit = p->pLimit; + iLimit = p->iLimit; + iOffset = p->iOffset; + p->pPrior = 0; p->pLimit = 0; + p->iLimit = 0; + p->iOffset = 0; + intersectdest.iSDParm = tab2; ExplainQueryPlan((pParse, 1, "%s USING TEMP B-TREE", sqlite3SelectOpName(p->op))); @@ -148007,19 +149934,21 @@ static int multiSelect( p->nSelectRow = pPrior->nSelectRow; } sqlite3ExprDelete(db, p->pLimit); + + /* Reinstate the LIMIT counters prior to running the final intersect */ p->pLimit = pLimit; + p->iLimit = iLimit; + p->iOffset = iOffset; /* Generate code to take the intersection of the two temporary ** tables. */ if( rc ) break; assert( p->pEList ); - iBreak = sqlite3VdbeMakeLabel(pParse); - iCont = sqlite3VdbeMakeLabel(pParse); - computeLimitRegisters(pParse, p, iBreak); - sqlite3VdbeAddOp2(v, OP_Rewind, tab1, iBreak); VdbeCoverage(v); + sqlite3VdbeAddOp1(v, OP_Rewind, tab1); r1 = sqlite3GetTempReg(pParse); iStart = sqlite3VdbeAddOp2(v, OP_RowData, tab1, r1); + iCont = sqlite3VdbeMakeLabel(pParse); sqlite3VdbeAddOp4Int(v, OP_NotFound, tab2, iCont, r1, 0); VdbeCoverage(v); sqlite3ReleaseTempReg(pParse, r1); @@ -148029,6 +149958,7 @@ static int multiSelect( sqlite3VdbeAddOp2(v, OP_Next, tab1, iStart); VdbeCoverage(v); sqlite3VdbeResolveLabel(v, iBreak); sqlite3VdbeAddOp2(v, OP_Close, tab2, 0); + sqlite3VdbeJumpHere(v, emptyBypass); sqlite3VdbeAddOp2(v, OP_Close, tab1, 0); break; } @@ -148677,7 +150607,7 @@ static int multiSelectOrderBy( ** ## About "isOuterJoin": ** ** The isOuterJoin column indicates that the replacement will occur into a -** position in the parent that NULL-able due to an OUTER JOIN. Either the +** position in the parent that is NULL-able due to an OUTER JOIN. Either the ** target slot in the parent is the right operand of a LEFT JOIN, or one of ** the left operands of a RIGHT JOIN. In either case, we need to potentially ** bypass the substituted expression with OP_IfNullRow. @@ -148707,6 +150637,7 @@ typedef struct SubstContext { int iTable; /* Replace references to this table */ int iNewTable; /* New table number */ int isOuterJoin; /* Add TK_IF_NULL_ROW opcodes on each replacement */ + int nSelDepth; /* Depth of sub-query recursion. Top==1 */ ExprList *pEList; /* Replacement expressions */ ExprList *pCList; /* Collation sequences for replacement expr */ } SubstContext; @@ -148814,6 +150745,9 @@ static Expr *substExpr( if( pExpr->op==TK_IF_NULL_ROW && pExpr->iTable==pSubst->iTable ){ pExpr->iTable = pSubst->iNewTable; } + if( pExpr->op==TK_AGG_FUNCTION && pExpr->op2>=pSubst->nSelDepth ){ + pExpr->op2--; + } pExpr->pLeft = substExpr(pSubst, pExpr->pLeft); pExpr->pRight = substExpr(pSubst, pExpr->pRight); if( ExprUseXSelect(pExpr) ){ @@ -148851,6 +150785,7 @@ static void substSelect( SrcItem *pItem; int i; if( !p ) return; + pSubst->nSelDepth++; do{ substExprList(pSubst, p->pEList); substExprList(pSubst, p->pGroupBy); @@ -148868,6 +150803,7 @@ static void substSelect( } } }while( doPrior && (p = p->pPrior)!=0 ); + pSubst->nSelDepth--; } #endif /* !defined(SQLITE_OMIT_SUBQUERY) || !defined(SQLITE_OMIT_VIEW) */ @@ -149479,7 +151415,7 @@ static int flattenSubquery( ** complete, since there may still exist Expr.pTab entries that ** refer to the subquery even after flattening. Ticket #3346. ** - ** pSubitem->pTab is always non-NULL by test restrictions and tests above. + ** pSubitem->pSTab is always non-NULL by test restrictions and tests above. */ if( ALWAYS(pSubitem->pSTab!=0) ){ Table *pTabToDel = pSubitem->pSTab; @@ -149509,17 +151445,12 @@ static int flattenSubquery( pSub = pSub1; for(pParent=p; pParent; pParent=pParent->pPrior, pSub=pSub->pPrior){ int nSubSrc; - u8 jointype = 0; - u8 ltorj = pSrc->a[iFrom].fg.jointype & JT_LTORJ; + u8 jointype = pSubitem->fg.jointype; assert( pSub!=0 ); pSubSrc = pSub->pSrc; /* FROM clause of subquery */ nSubSrc = pSubSrc->nSrc; /* Number of terms in subquery FROM clause */ pSrc = pParent->pSrc; /* FROM clause of the outer query */ - if( pParent==p ){ - jointype = pSubitem->fg.jointype; /* First time through the loop */ - } - /* The subquery uses a single slot of the FROM clause of the outer ** query. If the subquery has more than one element in its FROM clause, ** then expand the outer query to make space for it to hold all elements @@ -149539,6 +151470,7 @@ static int flattenSubquery( pSrc = sqlite3SrcListEnlarge(pParse, pSrc, nSubSrc-1,iFrom+1); if( pSrc==0 ) break; pParent->pSrc = pSrc; + pSubitem = &pSrc->a[iFrom]; } /* Transfer the FROM clause terms from the subquery into the @@ -149553,11 +151485,10 @@ static int flattenSubquery( || pItem->u4.zDatabase==0 ); if( pItem->fg.isUsing ) sqlite3IdListDelete(db, pItem->u3.pUsing); *pItem = pSubSrc->a[i]; - pItem->fg.jointype |= ltorj; + pItem->fg.jointype |= (jointype & JT_LTORJ); memset(&pSubSrc->a[i], 0, sizeof(pSubSrc->a[i])); } - pSrc->a[iFrom].fg.jointype &= JT_LTORJ; - pSrc->a[iFrom].fg.jointype |= jointype | ltorj; + pSubitem->fg.jointype |= jointype; /* Now begin substituting subquery result set expressions for ** references to the iParent in the outer query. @@ -149609,6 +151540,7 @@ static int flattenSubquery( x.iTable = iParent; x.iNewTable = iNewParent; x.isOuterJoin = isOuterJoin; + x.nSelDepth = 0; x.pEList = pSub->pEList; x.pCList = findLeftmostExprlist(pSub); substSelect(&x, pParent, 0); @@ -150194,6 +152126,7 @@ static int pushDownWhereTerms( x.iTable = pSrc->iCursor; x.iNewTable = pSrc->iCursor; x.isOuterJoin = 0; + x.nSelDepth = 0; x.pEList = pSubq->pEList; x.pCList = findLeftmostExprlist(pSubq); pNew = substExpr(&x, pNew); @@ -150591,7 +152524,7 @@ SQLITE_PRIVATE With *sqlite3WithPush(Parse *pParse, With *pWith, u8 bFree){ ** CTE expression, through routine checks to see if the reference is ** a recursive reference to the CTE. ** -** If pFrom matches a CTE according to either of these two above, pFrom->pTab +** If pFrom matches a CTE according to either of these two above, pFrom->pSTab ** and other fields are populated accordingly. ** ** Return 0 if no match is found. @@ -151629,6 +153562,7 @@ static void resetAccumulator(Parse *pParse, AggInfo *pAggInfo){ if( pFunc->bOBPayload ){ /* extra columns for the function arguments */ assert( ExprUseXList(pFunc->pFExpr) ); + assert( pFunc->pFExpr->x.pList!=0 ); nExtra += pFunc->pFExpr->x.pList->nExpr; } if( pFunc->bUseSubtype ){ @@ -152219,6 +154153,193 @@ static int fromClauseTermCanBeCoroutine( } /* +** Argument pWhere is the WHERE clause belonging to SELECT statement p. This +** function attempts to transform expressions of the form: +** +** EXISTS (SELECT ...) +** +** into joins. For example, given +** +** CREATE TABLE sailors(sid INTEGER PRIMARY KEY, name TEXT); +** CREATE TABLE reserves(sid INT, day DATE, PRIMARY KEY(sid, day)); +** +** SELECT name FROM sailors AS S WHERE EXISTS ( +** SELECT * FROM reserves AS R WHERE S.sid = R.sid AND R.day = '2022-10-25' +** ); +** +** the SELECT statement may be transformed as follows: +** +** SELECT name FROM sailors AS S, reserves AS R +** WHERE S.sid = R.sid AND R.day = '2022-10-25'; +** +** **Approximately**. Really, we have to ensure that the FROM-clause term +** that was formerly inside the EXISTS is only executed once. This is handled +** by setting the SrcItem.fg.fromExists flag, which then causes code in +** the where.c file to exit the corresponding loop after the first successful +** match (if any). +*/ +static SQLITE_NOINLINE void existsToJoin( + Parse *pParse, /* Parsing context */ + Select *p, /* The SELECT statement being optimized */ + Expr *pWhere /* part of the WHERE clause currently being examined */ +){ + if( pParse->nErr==0 + && pWhere!=0 + && !ExprHasProperty(pWhere, EP_OuterON|EP_InnerON) + && ALWAYS(p->pSrc!=0) + && p->pSrc->nSrc<BMS + ){ + if( pWhere->op==TK_AND ){ + Expr *pRight = pWhere->pRight; + existsToJoin(pParse, p, pWhere->pLeft); + existsToJoin(pParse, p, pRight); + } + else if( pWhere->op==TK_EXISTS ){ + Select *pSub = pWhere->x.pSelect; + Expr *pSubWhere = pSub->pWhere; + if( pSub->pSrc->nSrc==1 + && (pSub->selFlags & SF_Aggregate)==0 + && !pSub->pSrc->a[0].fg.isSubquery + && pSub->pLimit==0 + ){ + memset(pWhere, 0, sizeof(*pWhere)); + pWhere->op = TK_INTEGER; + pWhere->u.iValue = 1; + ExprSetProperty(pWhere, EP_IntValue); + + assert( p->pWhere!=0 ); + pSub->pSrc->a[0].fg.fromExists = 1; + pSub->pSrc->a[0].fg.jointype |= JT_CROSS; + p->pSrc = sqlite3SrcListAppendList(pParse, p->pSrc, pSub->pSrc); + if( pSubWhere ){ + p->pWhere = sqlite3PExpr(pParse, TK_AND, p->pWhere, pSubWhere); + pSub->pWhere = 0; + } + pSub->pSrc = 0; + sqlite3ParserAddCleanup(pParse, sqlite3SelectDeleteGeneric, pSub); +#if TREETRACE_ENABLED + if( sqlite3TreeTrace & 0x100000 ){ + TREETRACE(0x100000,pParse,p, + ("After EXISTS-to-JOIN optimization:\n")); + sqlite3TreeViewSelect(0, p, 0); + } +#endif + existsToJoin(pParse, p, pSubWhere); + } + } + } +} + +/* +** Type used for Walker callbacks by selectCheckOnClauses(). +*/ +typedef struct CheckOnCtx CheckOnCtx; +struct CheckOnCtx { + SrcList *pSrc; /* SrcList for this context */ + int iJoin; /* Cursor numbers must be =< than this */ + CheckOnCtx *pParent; /* Parent context */ +}; + +/* +** True if the SrcList passed as the only argument contains at least +** one RIGHT or FULL JOIN. False otherwise. +*/ +#define hasRightJoin(pSrc) (((pSrc)->a[0].fg.jointype & JT_LTORJ)!=0) + +/* +** The xExpr callback for the search of invalid ON clause terms. +*/ +static int selectCheckOnClausesExpr(Walker *pWalker, Expr *pExpr){ + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; + + /* Check if pExpr is root or near-root of an ON clause constraint that needs + ** to be checked to ensure that it does not refer to tables in its FROM + ** clause to the right of itself. i.e. it is either: + ** + ** + an ON clause on an OUTER join, or + ** + an ON clause on an INNER join within a FROM that features at + ** least one RIGHT or FULL join. + */ + if( (ExprHasProperty(pExpr, EP_OuterON)) + || (ExprHasProperty(pExpr, EP_InnerON) && hasRightJoin(pCtx->pSrc)) + ){ + /* If CheckOnCtx.iJoin is already set, then fall through and process + ** this expression node as normal. Or, if CheckOnCtx.iJoin is still 0, + ** set it to the cursor number of the RHS of the join to which this + ** ON expression was attached and then iterate through the entire + ** expression. */ + assert( pCtx->iJoin==0 || pCtx->iJoin==pExpr->w.iJoin ); + if( pCtx->iJoin==0 ){ + pCtx->iJoin = pExpr->w.iJoin; + sqlite3WalkExprNN(pWalker, pExpr); + pCtx->iJoin = 0; + return WRC_Prune; + } + } + + if( pExpr->op==TK_COLUMN ){ + /* A column expression. Find the SrcList (if any) to which it refers. + ** Then, if CheckOnCtx.iJoin indicates that this expression is part of an + ** ON clause from that SrcList (i.e. if iJoin is non-zero), check that it + ** does not refer to a table to the right of CheckOnCtx.iJoin. */ + do { + SrcList *pSrc = pCtx->pSrc; + int iTab = pExpr->iTable; + if( iTab>=pSrc->a[0].iCursor && iTab<=pSrc->a[pSrc->nSrc-1].iCursor ){ + if( pCtx->iJoin && iTab>pCtx->iJoin ){ + sqlite3ErrorMsg(pWalker->pParse, + "ON clause references tables to its right"); + return WRC_Abort; + } + break; + } + pCtx = pCtx->pParent; + }while( pCtx ); + } + return WRC_Continue; +} + +/* +** The xSelect callback for the search of invalid ON clause terms. +*/ +static int selectCheckOnClausesSelect(Walker *pWalker, Select *pSelect){ + CheckOnCtx *pCtx = pWalker->u.pCheckOnCtx; + if( pSelect->pSrc==pCtx->pSrc || pSelect->pSrc->nSrc==0 ){ + return WRC_Continue; + }else{ + CheckOnCtx sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pSrc = pSelect->pSrc; + sCtx.pParent = pCtx; + pWalker->u.pCheckOnCtx = &sCtx; + sqlite3WalkSelect(pWalker, pSelect); + pWalker->u.pCheckOnCtx = pCtx; + pSelect->selFlags &= ~SF_OnToWhere; + return WRC_Prune; + } +} + +/* +** Check all ON clauses in pSelect to verify that they do not reference +** columns to the right. +*/ +static void selectCheckOnClauses(Parse *pParse, Select *pSelect){ + Walker w; + CheckOnCtx sCtx; + assert( pSelect->selFlags & SF_OnToWhere ); + assert( pSelect->pSrc!=0 && pSelect->pSrc->nSrc>=2 ); + memset(&w, 0, sizeof(w)); + w.pParse = pParse; + w.xExprCallback = selectCheckOnClausesExpr; + w.xSelectCallback = selectCheckOnClausesSelect; + w.u.pCheckOnCtx = &sCtx; + memset(&sCtx, 0, sizeof(sCtx)); + sCtx.pSrc = pSelect->pSrc; + sqlite3WalkExprNN(&w, pSelect->pWhere); + pSelect->selFlags &= ~SF_OnToWhere; +} + +/* ** Generate byte-code for the SELECT statement given in the p argument. ** ** The results are returned according to the SelectDest structure. @@ -152345,6 +154466,18 @@ SQLITE_PRIVATE int sqlite3Select( } #endif + /* If the SELECT statement contains ON clauses that were moved into + ** the WHERE clause, go through and verify that none of the terms + ** in the ON clauses reference tables to the right of the ON clause. + ** Do this now, after name resolution, but before query flattening + */ + if( p->selFlags & SF_OnToWhere ){ + selectCheckOnClauses(pParse, p); + if( pParse->nErr ){ + goto select_end; + } + } + /* If the SF_UFSrcCheck flag is set, then this function is being called ** as part of populating the temp table for an UPDATE...FROM statement. ** In this case, it is an error if the target object (pSrc->a[0]) name @@ -152586,6 +154719,13 @@ SQLITE_PRIVATE int sqlite3Select( } #endif + /* If there may be an "EXISTS (SELECT ...)" in the WHERE clause, attempt + ** to change it into a join. */ + if( pParse->bHasExists && OptimizationEnabled(db,SQLITE_ExistsToJoin) ){ + existsToJoin(pParse, p, p->pWhere); + pTabList = p->pSrc; + } + /* Do the WHERE-clause constant propagation optimization if this is ** a join. No need to spend time on this operation for non-join queries ** as the equivalent optimization will be handled by query planner in @@ -153373,12 +155513,12 @@ SQLITE_PRIVATE int sqlite3Select( ** for the next GROUP BY batch. */ sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); - VdbeComment((v, "output one row")); + VdbeComment((v, "output one row of %d", p->selId)); sqlite3ExprCodeMove(pParse, iBMem, iAMem, pGroupBy->nExpr); sqlite3VdbeAddOp2(v, OP_IfPos, iAbortFlag, addrEnd); VdbeCoverage(v); VdbeComment((v, "check abort flag")); sqlite3VdbeAddOp2(v, OP_Gosub, regReset, addrReset); - VdbeComment((v, "reset accumulator")); + VdbeComment((v, "reset accumulator %d", p->selId)); /* Update the aggregate accumulators based on the content of ** the current row @@ -153386,7 +155526,7 @@ SQLITE_PRIVATE int sqlite3Select( sqlite3VdbeJumpHere(v, addr1); updateAccumulator(pParse, iUseFlag, pAggInfo, eDist); sqlite3VdbeAddOp2(v, OP_Integer, 1, iUseFlag); - VdbeComment((v, "indicate data in accumulator")); + VdbeComment((v, "indicate data in accumulator %d", p->selId)); /* End of the loop */ @@ -153403,7 +155543,7 @@ SQLITE_PRIVATE int sqlite3Select( /* Output the final row of result */ sqlite3VdbeAddOp2(v, OP_Gosub, regOutputRow, addrOutputRow); - VdbeComment((v, "output final row")); + VdbeComment((v, "output final row of %d", p->selId)); /* Jump over the subroutines */ @@ -153424,7 +155564,7 @@ SQLITE_PRIVATE int sqlite3Select( addrOutputRow = sqlite3VdbeCurrentAddr(v); sqlite3VdbeAddOp2(v, OP_IfPos, iUseFlag, addrOutputRow+2); VdbeCoverage(v); - VdbeComment((v, "Groupby result generator entry point")); + VdbeComment((v, "Groupby result generator entry point %d", p->selId)); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); finalizeAggFunctions(pParse, pAggInfo); sqlite3ExprIfFalse(pParse, pHaving, addrOutputRow+1, SQLITE_JUMPIFNULL); @@ -153432,14 +155572,14 @@ SQLITE_PRIVATE int sqlite3Select( &sDistinct, pDest, addrOutputRow+1, addrSetAbort); sqlite3VdbeAddOp1(v, OP_Return, regOutputRow); - VdbeComment((v, "end groupby result generator")); + VdbeComment((v, "end groupby result generator %d", p->selId)); /* Generate a subroutine that will reset the group-by accumulator */ sqlite3VdbeResolveLabel(v, addrReset); resetAccumulator(pParse, pAggInfo); sqlite3VdbeAddOp2(v, OP_Integer, 0, iUseFlag); - VdbeComment((v, "indicate accumulator empty")); + VdbeComment((v, "indicate accumulator %d empty", p->selId)); sqlite3VdbeAddOp1(v, OP_Return, regReset); if( distFlag!=0 && eDist!=WHERE_DISTINCT_NOOP ){ @@ -154903,7 +157043,10 @@ static void codeReturningTrigger( Returning *pReturning; Select sSelect; SrcList *pFrom; - u8 fromSpace[SZ_SRCLIST_1]; + union { + SrcList sSrc; + u8 fromSpace[SZ_SRCLIST_1]; + } uSrc; assert( v!=0 ); if( !pParse->bReturning ){ @@ -154919,8 +157062,8 @@ static void codeReturningTrigger( return; } memset(&sSelect, 0, sizeof(sSelect)); - pFrom = (SrcList*)fromSpace; - memset(pFrom, 0, SZ_SRCLIST_1); + memset(&uSrc, 0, sizeof(uSrc)); + pFrom = &uSrc.sSrc; sSelect.pEList = sqlite3ExprListDup(db, pReturning->pReturnEL, 0); sSelect.pSrc = pFrom; pFrom->nSrc = 1; @@ -157327,7 +159470,8 @@ SQLITE_PRIVATE SQLITE_NOINLINE int sqlite3RunVacuum( saved_nChange = db->nChange; saved_nTotalChange = db->nTotalChange; saved_mTrace = db->mTrace; - db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_Comments; + db->flags |= SQLITE_WriteSchema | SQLITE_IgnoreChecks | SQLITE_Comments + | SQLITE_AttachCreate | SQLITE_AttachWrite; db->mDbFlags |= DBFLAG_PreferBuiltin | DBFLAG_Vacuum; db->flags &= ~(u64)(SQLITE_ForeignKeys | SQLITE_ReverseOrder | SQLITE_Defensive | SQLITE_CountRows); @@ -159030,6 +161174,7 @@ struct WhereLevel { int iTabCur; /* The VDBE cursor used to access the table */ int iIdxCur; /* The VDBE cursor used to access pIdx */ int addrBrk; /* Jump here to break out of the loop */ + int addrHalt; /* Abort the query due to empty table or similar */ int addrNxt; /* Jump here to start the next IN combination */ int addrSkip; /* Jump here for next iteration of skip-scan */ int addrCont; /* Jump here to continue with the next loop cycle */ @@ -159235,6 +161380,9 @@ struct WhereTerm { u8 eMatchOp; /* Op for vtab MATCH/LIKE/GLOB/REGEXP terms */ int iParent; /* Disable pWC->a[iParent] when this term disabled */ int leftCursor; /* Cursor number of X in "X <op> <expr>" */ +#ifdef SQLITE_DEBUG + int iTerm; /* Which WhereTerm is this, for debug purposes */ +#endif union { struct { int leftColumn; /* Column number of X in "X <op> <expr>" */ @@ -159727,7 +161875,6 @@ SQLITE_PRIVATE void sqlite3WhereAddExplainText( #endif { VdbeOp *pOp = sqlite3VdbeGetOp(pParse->pVdbe, addr); - SrcItem *pItem = &pTabList->a[pLevel->iFrom]; sqlite3 *db = pParse->db; /* Database handle */ int isSearch; /* True for a SEARCH. False for SCAN. */ @@ -159750,7 +161897,10 @@ SQLITE_PRIVATE void sqlite3WhereAddExplainText( sqlite3StrAccumInit(&str, db, zBuf, sizeof(zBuf), SQLITE_MAX_LENGTH); str.printfFlags = SQLITE_PRINTF_INTERNAL; - sqlite3_str_appendf(&str, "%s %S", isSearch ? "SEARCH" : "SCAN", pItem); + sqlite3_str_appendf(&str, "%s %S%s", + isSearch ? "SEARCH" : "SCAN", + pItem, + pItem->fg.fromExists ? " EXISTS" : ""); if( (flags & (WHERE_IPK|WHERE_VIRTUALTABLE))==0 ){ const char *zFmt = 0; Index *pIdx; @@ -160994,6 +163144,7 @@ static SQLITE_NOINLINE void filterPullDown( int addrNxt, /* Jump here to bypass inner loops */ Bitmask notReady /* Loops that are not ready */ ){ + int saved_addrBrk; while( ++iLevel < pWInfo->nLevel ){ WhereLevel *pLevel = &pWInfo->a[iLevel]; WhereLoop *pLoop = pLevel->pWLoop; @@ -161002,7 +163153,7 @@ static SQLITE_NOINLINE void filterPullDown( /* ,--- Because sqlite3ConstructBloomFilter() has will not have set ** vvvvv--' pLevel->regFilter if this were true. */ if( NEVER(pLoop->prereq & notReady) ) continue; - assert( pLevel->addrBrk==0 ); + saved_addrBrk = pLevel->addrBrk; pLevel->addrBrk = addrNxt; if( pLoop->wsFlags & WHERE_IPK ){ WhereTerm *pTerm = pLoop->aLTerm[0]; @@ -161032,7 +163183,7 @@ static SQLITE_NOINLINE void filterPullDown( VdbeCoverage(pParse->pVdbe); } pLevel->regFilter = 0; - pLevel->addrBrk = 0; + pLevel->addrBrk = saved_addrBrk; } } @@ -161079,7 +163230,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3 *db; /* Database connection */ SrcItem *pTabItem; /* FROM clause term being coded */ int addrBrk; /* Jump here to break out of the loop */ - int addrHalt; /* addrBrk for the outermost loop */ int addrCont; /* Jump here to continue with next cycle */ int iRowidReg = 0; /* Rowid is stored in this register, if not zero */ int iReleaseReg = 0; /* Temp register to free before returning */ @@ -161123,7 +163273,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( ** there are no IN operators in the constraints, the "addrNxt" label ** is the same as "addrBrk". */ - addrBrk = pLevel->addrBrk = pLevel->addrNxt = sqlite3VdbeMakeLabel(pParse); + addrBrk = pLevel->addrNxt = pLevel->addrBrk; addrCont = pLevel->addrCont = sqlite3VdbeMakeLabel(pParse); /* If this is the right table of a LEFT OUTER JOIN, allocate and @@ -161139,14 +163289,6 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeComment((v, "init LEFT JOIN match flag")); } - /* Compute a safe address to jump to if we discover that the table for - ** this loop is empty and can never contribute content. */ - for(j=iLevel; j>0; j--){ - if( pWInfo->a[j].iLeftJoin ) break; - if( pWInfo->a[j].pRJ ) break; - } - addrHalt = pWInfo->a[j].addrBrk; - /* Special case of a FROM clause subquery implemented as a co-routine */ if( pTabItem->fg.viaCoroutine ){ int regYield; @@ -161385,7 +163527,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( VdbeCoverageIf(v, pX->op==TK_GE); sqlite3ReleaseTempReg(pParse, rTemp); }else{ - sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, addrHalt); + sqlite3VdbeAddOp2(v, bRev ? OP_Last : OP_Rewind, iCur, pLevel->addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); } @@ -161425,36 +163567,36 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( sqlite3VdbeChangeP5(v, SQLITE_AFF_NUMERIC | SQLITE_JUMPIFNULL); } }else if( pLoop->wsFlags & WHERE_INDEXED ){ - /* Case 4: A scan using an index. + /* Case 4: Search using an index. ** - ** The WHERE clause may contain zero or more equality - ** terms ("==" or "IN" operators) that refer to the N - ** left-most columns of the index. It may also contain - ** inequality constraints (>, <, >= or <=) on the indexed - ** column that immediately follows the N equalities. Only - ** the right-most column can be an inequality - the rest must - ** use the "==" and "IN" operators. For example, if the - ** index is on (x,y,z), then the following clauses are all - ** optimized: + ** The WHERE clause may contain zero or more equality + ** terms ("==" or "IN" or "IS" operators) that refer to the N + ** left-most columns of the index. It may also contain + ** inequality constraints (>, <, >= or <=) on the indexed + ** column that immediately follows the N equalities. Only + ** the right-most column can be an inequality - the rest must + ** use the "==", "IN", or "IS" operators. For example, if the + ** index is on (x,y,z), then the following clauses are all + ** optimized: ** - ** x=5 - ** x=5 AND y=10 - ** x=5 AND y<10 - ** x=5 AND y>5 AND y<10 - ** x=5 AND y=5 AND z<=10 + ** x=5 + ** x=5 AND y=10 + ** x=5 AND y<10 + ** x=5 AND y>5 AND y<10 + ** x=5 AND y=5 AND z<=10 ** - ** The z<10 term of the following cannot be used, only - ** the x=5 term: + ** The z<10 term of the following cannot be used, only + ** the x=5 term: ** - ** x=5 AND z<10 + ** x=5 AND z<10 ** - ** N may be zero if there are inequality constraints. - ** If there are no inequality constraints, then N is at - ** least one. + ** N may be zero if there are inequality constraints. + ** If there are no inequality constraints, then N is at + ** least one. ** - ** This case is also used when there are no WHERE clause - ** constraints but an index is selected anyway, in order - ** to force the output order to conform to an ORDER BY. + ** This case is also used when there are no WHERE clause + ** constraints but an index is selected anyway, in order + ** to force the output order to conform to an ORDER BY. */ static const u8 aStartOp[] = { 0, @@ -162180,7 +164322,7 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeOneLoopStart( codeCursorHint(pTabItem, pWInfo, pLevel, 0); pLevel->op = aStep[bRev]; pLevel->p1 = iCur; - pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev], iCur, addrHalt); + pLevel->p2 = 1 + sqlite3VdbeAddOp2(v, aStart[bRev],iCur,pLevel->addrHalt); VdbeCoverageIf(v, bRev==0); VdbeCoverageIf(v, bRev!=0); pLevel->p5 = SQLITE_STMTSTATUS_FULLSCAN_STEP; @@ -162452,7 +164594,10 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( WhereLoop *pLoop = pLevel->pWLoop; SrcItem *pTabItem = &pWInfo->pTabList->a[pLevel->iFrom]; SrcList *pFrom; - u8 fromSpace[SZ_SRCLIST_1]; + union { + SrcList sSrc; + u8 fromSpace[SZ_SRCLIST_1]; + } uSrc; Bitmask mAll = 0; int k; @@ -162496,7 +164641,7 @@ SQLITE_PRIVATE SQLITE_NOINLINE void sqlite3WhereRightJoinLoop( sqlite3ExprDup(pParse->db, pTerm->pExpr, 0)); } } - pFrom = (SrcList*)fromSpace; + pFrom = &uSrc.sSrc; pFrom->nSrc = 1; pFrom->nAlloc = 1; memcpy(&pFrom->a[0], pTabItem, sizeof(SrcItem)); @@ -163491,7 +165636,7 @@ static int termIsEquivalence(Parse *pParse, Expr *pExpr, SrcList *pSrc){ if( ExprHasProperty(pExpr, EP_OuterON) ) return 0; /* (3) */ assert( pSrc!=0 ); if( pExpr->op==TK_IS - && pSrc->nSrc + && pSrc->nSrc>=2 && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ return 0; /* (4) */ @@ -163667,6 +165812,9 @@ static void exprAnalyze( } assert( pWC->nTerm > idxTerm ); pTerm = &pWC->a[idxTerm]; +#ifdef SQLITE_DEBUG + pTerm->iTerm = idxTerm; +#endif pMaskSet = &pWInfo->sMaskSet; pExpr = pTerm->pExpr; assert( pExpr!=0 ); /* Because malloc() has not failed */ @@ -163710,21 +165858,7 @@ static void exprAnalyze( prereqAll |= x; extraRight = x-1; /* ON clause terms may not be used with an index ** on left table of a LEFT JOIN. Ticket #3015 */ - if( (prereqAll>>1)>=x ){ - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); - return; - } }else if( (prereqAll>>1)>=x ){ - /* The ON clause of an INNER JOIN references a table to its right. - ** Most other SQL database engines raise an error. But SQLite versions - ** 3.0 through 3.38 just put the ON clause constraint into the WHERE - ** clause and carried on. Beginning with 3.39, raise an error only - ** if there is a RIGHT or FULL JOIN in the query. This makes SQLite - ** more like other systems, and also preserves legacy. */ - if( ALWAYS(pSrc->nSrc>0) && (pSrc->a[0].fg.jointype & JT_LTORJ)!=0 ){ - sqlite3ErrorMsg(pParse, "ON clause references tables to its right"); - return; - } ExprClearProperty(pExpr, EP_InnerON); } } @@ -164081,7 +166215,7 @@ static void exprAnalyze( idxNew = whereClauseInsert(pWC, pNewExpr, TERM_VIRTUAL|TERM_DYNAMIC); testcase( idxNew==0 ); pNewTerm = &pWC->a[idxNew]; - pNewTerm->prereqRight = prereqExpr; + pNewTerm->prereqRight = prereqExpr | extraRight; pNewTerm->leftCursor = pLeft->iTable; pNewTerm->u.x.leftColumn = pLeft->iColumn; pNewTerm->eOperator = WO_AUX; @@ -164192,7 +166326,7 @@ static void whereAddLimitExpr( ** ** 1. The SELECT statement has a LIMIT clause, and ** 2. The SELECT statement is not an aggregate or DISTINCT query, and -** 3. The SELECT statement has exactly one object in its from clause, and +** 3. The SELECT statement has exactly one object in its FROM clause, and ** that object is a virtual table, and ** 4. There are no terms in the WHERE clause that will not be passed ** to the virtual table xBestIndex method. @@ -164229,8 +166363,22 @@ SQLITE_PRIVATE void SQLITE_NOINLINE sqlite3WhereAddLimit(WhereClause *pWC, Selec ** (leftCursor==iCsr) test below. */ continue; } - if( pWC->a[ii].leftCursor!=iCsr ) return; - if( pWC->a[ii].prereqRight!=0 ) return; + if( pWC->a[ii].leftCursor==iCsr && pWC->a[ii].prereqRight==0 ) continue; + + /* If this term has a parent with exactly one child, and the parent will + ** be passed through to xBestIndex, then this term can be ignored. */ + if( pWC->a[ii].iParent>=0 ){ + WhereTerm *pParent = &pWC->a[ pWC->a[ii].iParent ]; + if( pParent->leftCursor==iCsr + && pParent->prereqRight==0 + && pParent->nChild==1 + ){ + continue; + } + } + + /* This term will not be passed through. Do not add a LIMIT clause. */ + return; } /* Check condition (5). Return early if it is not met. */ @@ -164894,11 +167042,11 @@ static WhereTerm *whereScanNext(WhereScan *pScan){ pScan->pWC = pWC; pScan->k = k+1; #ifdef WHERETRACE_ENABLED - if( sqlite3WhereTrace & 0x20000 ){ + if( (sqlite3WhereTrace & 0x20000)!=0 && pScan->nEquiv>1 ){ int ii; - sqlite3DebugPrintf("SCAN-TERM %p: nEquiv=%d", - pTerm, pScan->nEquiv); - for(ii=0; ii<pScan->nEquiv; ii++){ + sqlite3DebugPrintf("EQUIVALENT TO {%d:%d} (due to TERM-%d):", + pScan->aiCur[0], pScan->aiColumn[0], pTerm->iTerm); + for(ii=1; ii<pScan->nEquiv; ii++){ sqlite3DebugPrintf(" {%d:%d}", pScan->aiCur[ii], pScan->aiColumn[ii]); } @@ -165669,7 +167817,9 @@ static SQLITE_NOINLINE void constructAutomaticIndex( VdbeCoverage(v); VdbeComment((v, "next row of %s", pSrc->pSTab->zName)); }else{ - addrTop = sqlite3VdbeAddOp1(v, OP_Rewind, pLevel->iTabCur); VdbeCoverage(v); + assert( pLevel->addrHalt ); + addrTop = sqlite3VdbeAddOp2(v, OP_Rewind,pLevel->iTabCur,pLevel->addrHalt); + VdbeCoverage(v); } if( pPartial ){ iContinue = sqlite3VdbeMakeLabel(pParse); @@ -165697,11 +167847,14 @@ static SQLITE_NOINLINE void constructAutomaticIndex( pSrc->u4.pSubq->regResult, pLevel->iIdxCur); sqlite3VdbeGoto(v, addrTop); pSrc->fg.viaCoroutine = 0; + sqlite3VdbeJumpHere(v, addrTop); }else{ sqlite3VdbeAddOp2(v, OP_Next, pLevel->iTabCur, addrTop+1); VdbeCoverage(v); sqlite3VdbeChangeP5(v, SQLITE_STMTSTATUS_AUTOINDEX); + if( (pSrc->fg.jointype & JT_LEFT)!=0 ){ + sqlite3VdbeJumpHere(v, addrTop); + } } - sqlite3VdbeJumpHere(v, addrTop); sqlite3ReleaseTempReg(pParse, regRecord); /* Jump here when skipping the initialization */ @@ -166853,6 +169006,7 @@ SQLITE_PRIVATE void sqlite3WhereTermPrint(WhereTerm *pTerm, int iTerm){ }else{ sqlite3_snprintf(sizeof(zLeft),zLeft,"left=%d", pTerm->leftCursor); } + iTerm = pTerm->iTerm = MAX(iTerm,pTerm->iTerm); sqlite3DebugPrintf( "TERM-%-3d %p %s %-12s op=%03x wtFlags=%04x", iTerm, pTerm, zType, zLeft, pTerm->eOperator, pTerm->wtFlags); @@ -167994,6 +170148,7 @@ static int whereLoopAddBtreeIndex( && pProbe->hasStat1!=0 && OptimizationEnabled(db, SQLITE_SkipScan) && pProbe->aiRowLogEst[saved_nEq+1]>=42 /* TUNING: Minimum for skip-scan */ + && pSrc->fg.fromExists==0 && (rc = whereLoopResize(db, pNew, pNew->nLTerm+1))==SQLITE_OK ){ LogEst nIter; @@ -169565,6 +171720,10 @@ static i8 wherePathSatisfiesOrderBy( && ((wctrlFlags&(WHERE_DISTINCTBY|WHERE_SORTBYGROUP))!=WHERE_DISTINCTBY) ){ obSat = obDone; + }else{ + /* No further ORDER BY terms may be matched. So this call should + ** return >=0, not -1. Clear isOrderDistinct to ensure it does so. */ + isOrderDistinct = 0; } break; } @@ -170310,8 +172469,15 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ ** mxChoice best-so-far paths. ** ** First look for an existing path among best-so-far paths - ** that covers the same set of loops and has the same isOrdered - ** setting as the current path candidate. + ** that: + ** (1) covers the same set of loops, and + ** (2) has a compatible isOrdered value. + ** + ** "Compatible isOrdered value" means either + ** (A) both have isOrdered==-1, or + ** (B) both have isOrder>=0, or + ** (C) ordering does not matter because this is the last round + ** of the solver. ** ** The term "((pTo->isOrdered^isOrdered)&0x80)==0" is equivalent ** to (pTo->isOrdered==(-1))==(isOrdered==(-1))" for the range @@ -170320,7 +172486,7 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ testcase( nTo==0 ); for(jj=0, pTo=aTo; jj<nTo; jj++, pTo++){ if( pTo->maskLoop==maskNew - && ((pTo->isOrdered^isOrdered)&0x80)==0 + && ( ((pTo->isOrdered^isOrdered)&0x80)==0 || iLoop==nLoop-1 ) ){ testcase( jj==nTo-1 ); break; @@ -170475,11 +172641,10 @@ static int wherePathSolver(WhereInfo *pWInfo, LogEst nRowEst){ return SQLITE_ERROR; } - /* Find the lowest cost path. pFrom will be left pointing to that path */ + /* Only one path is available, which is the best path */ + assert( nFrom==1 ); pFrom = aFrom; - for(ii=1; ii<nFrom; ii++){ - if( pFrom->rCost>aFrom[ii].rCost ) pFrom = &aFrom[ii]; - } + assert( pWInfo->nLevel==nLoop ); /* Load the lowest cost path into pWInfo */ for(iLoop=0; iLoop<nLoop; iLoop++){ @@ -170612,7 +172777,10 @@ static SQLITE_NOINLINE void whereInterstageHeuristic(WhereInfo *pWInfo){ for(i=0; i<pWInfo->nLevel; i++){ WhereLoop *p = pWInfo->a[i].pWLoop; if( p==0 ) break; - if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ) continue; + if( (p->wsFlags & WHERE_VIRTUALTABLE)!=0 ){ + /* Treat a vtab scan as similar to a full-table scan */ + break; + } if( (p->wsFlags & (WHERE_COLUMN_EQ|WHERE_COLUMN_NULL|WHERE_COLUMN_IN))!=0 ){ u8 iTab = p->iTab; WhereLoop *pLoop; @@ -171550,6 +173718,14 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( pTab = pTabItem->pSTab; iDb = sqlite3SchemaToIndex(db, pTab->pSchema); pLoop = pLevel->pWLoop; + pLevel->addrBrk = sqlite3VdbeMakeLabel(pParse); + if( ii==0 || (pTabItem[0].fg.jointype & JT_LEFT)!=0 ){ + pLevel->addrHalt = pLevel->addrBrk; + }else if( pWInfo->a[ii-1].pRJ ){ + pLevel->addrHalt = pWInfo->a[ii-1].addrBrk; + }else{ + pLevel->addrHalt = pWInfo->a[ii-1].addrHalt; + } if( (pTab->tabFlags & TF_Ephemeral)!=0 || IsView(pTab) ){ /* Do nothing */ }else @@ -171601,6 +173777,13 @@ SQLITE_PRIVATE WhereInfo *sqlite3WhereBegin( sqlite3VdbeAddOp4Dup8(v, OP_ColumnsUsed, pTabItem->iCursor, 0, 0, (const u8*)&pTabItem->colUsed, P4_INT64); #endif + if( ii>=2 + && (pTabItem[0].fg.jointype & (JT_LTORJ|JT_LEFT))==0 + && pLevel->addrHalt==pWInfo->a[0].addrHalt + ){ + sqlite3VdbeAddOp2(v, OP_IfEmpty, pTabItem->iCursor, pWInfo->iBreak); + VdbeCoverage(v); + } }else{ sqlite3TableLock(pParse, iDb, pTab->tnum, 0, pTab->zName); } @@ -171857,6 +174040,9 @@ 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); + } /* The common case: Advance to the next row */ if( pLevel->addrCont ) sqlite3VdbeResolveLabel(v, pLevel->addrCont); sqlite3VdbeAddOp3(v, pLevel->op, pLevel->p1, pLevel->p2, pLevel->p3); @@ -174707,7 +176893,7 @@ static int windowExprGtZero(Parse *pParse, Expr *pExpr){ ** ** ROWS BETWEEN <expr1> FOLLOWING AND <expr2> FOLLOWING ** -** ... loop started by sqlite3WhereBegin() ... +** ... loop started by sqlite3WhereBegin() ... ** if( new partition ){ ** Gosub flush ** } @@ -175225,6 +177411,12 @@ SQLITE_PRIVATE void sqlite3WindowCodeStep( addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, 0, 1); }else{ assert( pMWin->eEnd==TK_FOLLOWING ); + /* assert( regStart>=0 ); + ** regEnd = regEnd - regStart; + ** regStart = 0; */ + sqlite3VdbeAddOp3(v, OP_Subtract, regStart, regEnd, regEnd); + sqlite3VdbeAddOp2(v, OP_Integer, 0, regStart); + addrStart = sqlite3VdbeCurrentAddr(v); addrBreak1 = windowCodeOp(&s, WINDOW_RETURN_ROW, regEnd, 1); addrBreak2 = windowCodeOp(&s, WINDOW_AGGINVERSE, regStart, 1); @@ -181620,8 +183812,9 @@ static int analyzeFilterKeyword(const unsigned char *z, int lastToken){ ** Return the length (in bytes) of the token that begins at z[0]. ** Store the token type in *tokenType before returning. */ -SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ - int i, c; +SQLITE_PRIVATE i64 sqlite3GetToken(const unsigned char *z, int *tokenType){ + i64 i; + int c; switch( aiClass[*z] ){ /* Switch on the character-class of the first byte ** of the token. See the comment on the CC_ defines ** above. */ @@ -181949,7 +184142,7 @@ SQLITE_PRIVATE int sqlite3GetToken(const unsigned char *z, int *tokenType){ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ int nErr = 0; /* Number of errors encountered */ void *pEngine; /* The LEMON-generated LALR(1) parser */ - int n = 0; /* Length of the next token token */ + i64 n = 0; /* Length of the next token token */ int tokenType; /* type of the next token */ int lastTokenParsed = -1; /* type of the previous token */ sqlite3 *db = pParse->db; /* The database connection */ @@ -182052,13 +184245,13 @@ SQLITE_PRIVATE int sqlite3RunParser(Parse *pParse, const char *zSql){ }else if( tokenType!=TK_QNUMBER ){ Token x; x.z = zSql; - x.n = n; + x.n = (u32)n; sqlite3ErrorMsg(pParse, "unrecognized token: \"%T\"", &x); break; } } pParse->sLastToken.z = zSql; - pParse->sLastToken.n = n; + pParse->sLastToken.n = (u32)n; sqlite3Parser(pEngine, tokenType, pParse->sLastToken); lastTokenParsed = tokenType; zSql += n; @@ -182134,7 +184327,7 @@ SQLITE_PRIVATE char *sqlite3Normalize( ){ sqlite3 *db; /* The database connection */ int i; /* Next unread byte of zSql[] */ - int n; /* length of current token */ + i64 n; /* length of current token */ int tokenType; /* type of current token */ int prevType = 0; /* Previous non-whitespace token */ int nParen; /* Number of nested levels of parentheses */ @@ -182712,9 +184905,6 @@ static int (*const sqlite3BuiltinExtensions[])(sqlite3*) = { sqlite3DbstatRegister, #endif sqlite3TestExtInit, -#if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) - sqlite3JsonTableFunctions, -#endif #ifdef SQLITE_ENABLE_STMTVTAB sqlite3StmtVtabInit, #endif @@ -184170,6 +186360,9 @@ SQLITE_PRIVATE const char *sqlite3ErrName(int rc){ case SQLITE_OK: zName = "SQLITE_OK"; break; case SQLITE_ERROR: zName = "SQLITE_ERROR"; break; case SQLITE_ERROR_SNAPSHOT: zName = "SQLITE_ERROR_SNAPSHOT"; break; + case SQLITE_ERROR_RETRY: zName = "SQLITE_ERROR_RETRY"; break; + case SQLITE_ERROR_MISSING_COLLSEQ: + zName = "SQLITE_ERROR_MISSING_COLLSEQ"; break; case SQLITE_INTERNAL: zName = "SQLITE_INTERNAL"; break; case SQLITE_PERM: zName = "SQLITE_PERM"; break; case SQLITE_ABORT: zName = "SQLITE_ABORT"; break; @@ -185352,6 +187545,29 @@ SQLITE_API const char *sqlite3_errmsg(sqlite3 *db){ } /* +** Set the error code and error message associated with the database handle. +** +** This routine is intended to be called by outside extensions (ex: the +** Session extension). Internal logic should invoke sqlite3Error() or +** sqlite3ErrorWithMsg() directly. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zMsg){ + int rc = SQLITE_OK; + if( !sqlite3SafetyCheckSickOrOk(db) ){ + return SQLITE_MISUSE_BKPT; + } + sqlite3_mutex_enter(db->mutex); + if( zMsg ){ + sqlite3ErrorWithMsg(db, errcode, "%s", zMsg); + }else{ + sqlite3Error(db, errcode); + } + rc = sqlite3ApiExit(db, rc); + sqlite3_mutex_leave(db->mutex); + return rc; +} + +/* ** Return the byte offset of the most recent error */ SQLITE_API int sqlite3_error_offset(sqlite3 *db){ @@ -187175,13 +189391,15 @@ SQLITE_API int sqlite3_test_control(int op, ...){ break; } - /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, onOff, tnum); + /* sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, db, dbName, mode, tnum); ** ** This test control is used to create imposter tables. "db" is a pointer ** to the database connection. dbName is the database name (ex: "main" or - ** "temp") which will receive the imposter. "onOff" turns imposter mode on - ** or off. "tnum" is the root page of the b-tree to which the imposter - ** table should connect. + ** "temp") which will receive the imposter. "mode" turns imposter mode on + ** or off. mode==0 means imposter mode is off. mode==1 means imposter mode + ** is on. mode==2 means imposter mode is on but results in an imposter + ** table that is read-only unless writable_schema is on. "tnum" is the + ** root page of the b-tree to which the imposter table should connect. ** ** Enable imposter mode only when the schema has already been parsed. Then ** run a single CREATE TABLE statement to construct the imposter table in @@ -188418,6 +190636,13 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ #ifndef _FTSINT_H #define _FTSINT_H +/* +** Activate assert() only if SQLITE_TEST is enabled. +*/ +#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) +# define NDEBUG 1 +#endif + /* #include <assert.h> */ /* #include <stdlib.h> */ /* #include <stddef.h> */ @@ -188425,10 +190650,6 @@ SQLITE_PRIVATE void sqlite3ConnectionClosed(sqlite3 *db){ /* #include <string.h> */ /* #include <stdarg.h> */ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) -# define NDEBUG 1 -#endif - /* FTS3/FTS4 require virtual tables */ #ifdef SQLITE_OMIT_VIRTUALTABLE # undef SQLITE_ENABLE_FTS3 @@ -188872,13 +191093,6 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */ #define UNUSED_PARAMETER(x) (void)(x) /* -** Activate assert() only if SQLITE_TEST is enabled. -*/ -#if !defined(NDEBUG) && !defined(SQLITE_DEBUG) -# define NDEBUG 1 -#endif - -/* ** The TESTONLY macro is used to enclose variable declarations or ** other bits of code that are needed to support the arguments ** within testcase() and assert() macros. @@ -188898,7 +191112,7 @@ typedef sqlite3_int64 i64; /* 8-byte signed integer */ ** Macros needed to provide flexible arrays in a portable way */ #ifndef offsetof -# define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD)) +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define FLEXARRAY @@ -203152,8 +205366,8 @@ struct NodeWriter { ** to an appendable b-tree segment. */ struct IncrmergeWriter { - int nLeafEst; /* Space allocated for leaf blocks */ - int nWork; /* Number of leaf pages flushed */ + i64 nLeafEst; /* Space allocated for leaf blocks */ + i64 nWork; /* Number of leaf pages flushed */ sqlite3_int64 iAbsLevel; /* Absolute level of input segments */ int iIdx; /* Index of *output* segment in iAbsLevel+1 */ sqlite3_int64 iStart; /* Block number of first allocated block */ @@ -203899,7 +206113,7 @@ static int fts3IncrmergeWriter( ){ int rc; /* Return Code */ int i; /* Iterator variable */ - int nLeafEst = 0; /* Blocks allocated for leaf nodes */ + i64 nLeafEst = 0; /* Blocks allocated for leaf nodes */ sqlite3_stmt *pLeafEst = 0; /* SQL used to determine nLeafEst */ sqlite3_stmt *pFirstBlock = 0; /* SQL used to determine first block */ @@ -203909,7 +206123,7 @@ static int fts3IncrmergeWriter( sqlite3_bind_int64(pLeafEst, 1, iAbsLevel); sqlite3_bind_int64(pLeafEst, 2, pCsr->nSegment); if( SQLITE_ROW==sqlite3_step(pLeafEst) ){ - nLeafEst = sqlite3_column_int(pLeafEst, 0); + nLeafEst = sqlite3_column_int64(pLeafEst, 0); } rc = sqlite3_reset(pLeafEst); } @@ -205292,10 +207506,6 @@ SQLITE_PRIVATE int sqlite3Fts3Optimize(Fts3Table *p){ /* #include <string.h> */ /* #include <assert.h> */ -#ifndef SQLITE_AMALGAMATION -typedef sqlite3_int64 i64; -#endif - /* ** Characters that may appear in the second argument to matchinfo(). */ @@ -210149,7 +212359,7 @@ static u32 jsonTranslateBlobToText( jsonAppendChar(pOut, '\''); break; case 'v': - jsonAppendRawNZ(pOut, "\\u0009", 6); + jsonAppendRawNZ(pOut, "\\u000b", 6); break; case 'x': if( sz2<4 ){ @@ -210999,19 +213209,27 @@ static void jsonReturnTextJsonFromBlob( ** ** If the value is a primitive, return it as an SQL value. ** If the value is an array or object, return it as either -** JSON text or the BLOB encoding, depending on the JSON_B flag -** on the userdata. +** JSON text or the BLOB encoding, depending on the eMode flag +** as follows: +** +** eMode==0 JSONB if the JSON_B flag is set in userdata or +** text if the JSON_B flag is omitted from userdata. +** +** eMode==1 Text +** +** eMode==2 JSONB */ static void jsonReturnFromBlob( JsonParse *pParse, /* Complete JSON parse tree */ u32 i, /* Index of the node */ sqlite3_context *pCtx, /* Return value for this function */ - int textOnly /* return text JSON. Disregard user-data */ + int eMode /* Format of return: text of JSONB */ ){ u32 n, sz; int rc; sqlite3 *db = sqlite3_context_db_handle(pCtx); + assert( eMode>=0 && eMode<=2 ); n = jsonbPayloadSize(pParse, i, &sz); if( n==0 ){ sqlite3_result_error(pCtx, "malformed JSON", -1); @@ -211052,7 +213270,19 @@ static void jsonReturnFromBlob( rc = sqlite3DecOrHexToI64(z, &iRes); sqlite3DbFree(db, z); if( rc==0 ){ - sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); + if( iRes<0 ){ + /* A hexadecimal literal with 16 significant digits and with the + ** high-order bit set is a negative integer in SQLite (and hence + ** iRes comes back as negative) but should be interpreted as a + ** positive value if it occurs within JSON. The value is too + ** large to appear as an SQLite integer so it must be converted + ** into floating point. */ + double r; + r = (double)*(sqlite3_uint64*)&iRes; + sqlite3_result_double(pCtx, bNeg ? -r : r); + }else{ + sqlite3_result_int64(pCtx, bNeg ? -iRes : iRes); + } }else if( rc==3 && bNeg ){ sqlite3_result_int64(pCtx, SMALLEST_INT64); }else if( rc==1 ){ @@ -211130,8 +213360,14 @@ static void jsonReturnFromBlob( } case JSONB_ARRAY: case JSONB_OBJECT: { - int flags = textOnly ? 0 : SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)); - if( flags & JSON_BLOB ){ + if( eMode==0 ){ + if( (SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)) & JSON_BLOB)!=0 ){ + eMode = 2; + }else{ + eMode = 1; + } + } + if( eMode==2 ){ sqlite3_result_blob(pCtx, &pParse->aBlob[i], sz+n, SQLITE_TRANSIENT); }else{ jsonReturnTextJsonFromBlob(pCtx, &pParse->aBlob[i], sz+n); @@ -212778,6 +215014,7 @@ struct JsonEachCursor { u32 nRoot; /* Size of the root path in bytes */ u8 eType; /* Type of the container for element i */ u8 bRecursive; /* True for json_tree(). False for json_each() */ + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ u32 nParent; /* Current nesting depth */ u32 nParentAlloc; /* Space allocated for aParent[] */ JsonParent *aParent; /* Parent elements of i */ @@ -212789,6 +215026,8 @@ typedef struct JsonEachConnection JsonEachConnection; struct JsonEachConnection { sqlite3_vtab base; /* Base class - must be first */ sqlite3 *db; /* Database connection */ + u8 eMode; /* 1 for json_each(). 2 for jsonb_each() */ + u8 bRecursive; /* True for json_tree(). False for json_each() */ }; @@ -212831,6 +215070,8 @@ static int jsonEachConnect( if( pNew==0 ) return SQLITE_NOMEM; sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); pNew->db = db; + pNew->eMode = argv[0][4]=='b' ? 2 : 1; + pNew->bRecursive = argv[0][4+pNew->eMode]=='t'; } return rc; } @@ -212842,8 +215083,8 @@ static int jsonEachDisconnect(sqlite3_vtab *pVtab){ return SQLITE_OK; } -/* constructor for a JsonEachCursor object for json_each(). */ -static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ +/* constructor for a JsonEachCursor object for json_each()/json_tree(). */ +static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ JsonEachConnection *pVtab = (JsonEachConnection*)p; JsonEachCursor *pCur; @@ -212851,21 +215092,13 @@ static int jsonEachOpenEach(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ pCur = sqlite3DbMallocZero(pVtab->db, sizeof(*pCur)); if( pCur==0 ) return SQLITE_NOMEM; pCur->db = pVtab->db; + pCur->eMode = pVtab->eMode; + pCur->bRecursive = pVtab->bRecursive; jsonStringZero(&pCur->path); *ppCursor = &pCur->base; return SQLITE_OK; } -/* constructor for a JsonEachCursor object for json_tree(). */ -static int jsonEachOpenTree(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ - int rc = jsonEachOpenEach(p, ppCursor); - if( rc==SQLITE_OK ){ - JsonEachCursor *pCur = (JsonEachCursor*)*ppCursor; - pCur->bRecursive = 1; - } - return rc; -} - /* Reset a JsonEachCursor back to its original state. Free any memory ** held. */ static void jsonEachCursorReset(JsonEachCursor *p){ @@ -213070,7 +215303,7 @@ static int jsonEachColumn( } case JEACH_VALUE: { u32 i = jsonSkipLabel(p); - jsonReturnFromBlob(&p->sParse, i, ctx, 1); + jsonReturnFromBlob(&p->sParse, i, ctx, p->eMode); if( (p->sParse.aBlob[i] & 0x0f)>=JSONB_ARRAY ){ sqlite3_result_subtype(ctx, JSON_SUBTYPE); } @@ -213314,36 +215547,7 @@ static sqlite3_module jsonEachModule = { jsonEachBestIndex, /* xBestIndex */ jsonEachDisconnect, /* xDisconnect */ 0, /* xDestroy */ - jsonEachOpenEach, /* xOpen - open a cursor */ - jsonEachClose, /* xClose - close a cursor */ - jsonEachFilter, /* xFilter - configure scan constraints */ - jsonEachNext, /* xNext - advance a cursor */ - jsonEachEof, /* xEof - check for end of scan */ - jsonEachColumn, /* xColumn - read data */ - jsonEachRowid, /* xRowid - read data */ - 0, /* xUpdate */ - 0, /* xBegin */ - 0, /* xSync */ - 0, /* xCommit */ - 0, /* xRollback */ - 0, /* xFindMethod */ - 0, /* xRename */ - 0, /* xSavepoint */ - 0, /* xRelease */ - 0, /* xRollbackTo */ - 0, /* xShadowName */ - 0 /* xIntegrity */ -}; - -/* The methods of the json_tree virtual table. */ -static sqlite3_module jsonTreeModule = { - 0, /* iVersion */ - 0, /* xCreate */ - jsonEachConnect, /* xConnect */ - jsonEachBestIndex, /* xBestIndex */ - jsonEachDisconnect, /* xDisconnect */ - 0, /* xDestroy */ - jsonEachOpenTree, /* xOpen - open a cursor */ + jsonEachOpen, /* xOpen - open a cursor */ jsonEachClose, /* xClose - close a cursor */ jsonEachFilter, /* xFilter - configure scan constraints */ jsonEachNext, /* xNext - advance a cursor */ @@ -213432,22 +215636,21 @@ SQLITE_PRIVATE void sqlite3RegisterJsonFunctions(void){ #if !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) /* -** Register the JSON table-valued functions +** Register the JSON table-valued function named zName and return a +** pointer to its Module object. Return NULL if something goes wrong. */ -SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ - int rc = SQLITE_OK; - static const struct { - const char *zName; - sqlite3_module *pModule; - } aMod[] = { - { "json_each", &jsonEachModule }, - { "json_tree", &jsonTreeModule }, - }; +SQLITE_PRIVATE Module *sqlite3JsonVtabRegister(sqlite3 *db, const char *zName){ unsigned int i; - for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){ - rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0); + static const char *azModule[] = { + "json_each", "json_tree", "jsonb_each", "jsonb_tree" + }; + assert( sqlite3HashFind(&db->aModule, zName)==0 ); + for(i=0; i<sizeof(azModule)/sizeof(azModule[0]); i++){ + if( sqlite3StrICmp(azModule[i],zName)==0 ){ + return sqlite3VtabCreateModule(db, azModule[i], &jsonEachModule, 0, 0); + } } - return rc; + return 0; } #endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && !defined(SQLITE_OMIT_JSON) */ @@ -213517,7 +215720,7 @@ SQLITE_PRIVATE int sqlite3JsonTableFunctions(sqlite3 *db){ #else /* #include "sqlite3.h" */ #endif -SQLITE_PRIVATE int sqlite3GetToken(const unsigned char*,int*); /* In the SQLite core */ +SQLITE_PRIVATE sqlite3_int64 sqlite3GetToken(const unsigned char*,int*); /* In SQLite core */ /* #include <stddef.h> */ @@ -213552,7 +215755,7 @@ typedef unsigned int u32; # define NEVER(X) (X) #endif #ifndef offsetof -#define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD)) +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define FLEXARRAY @@ -214590,6 +216793,12 @@ static void resetCursor(RtreeCursor *pCsr){ pCsr->base.pVtab = (sqlite3_vtab*)pRtree; pCsr->pReadAux = pStmt; + /* The following will only fail if the previous sqlite3_step() call failed, + ** in which case the error has already been caught. This statement never + ** encounters an error within an sqlite3_column_xxx() function, as it + ** calls sqlite3_column_value(), which does not use malloc(). So it is safe + ** to ignore the error code here. */ + sqlite3_reset(pStmt); } /* @@ -227678,8 +229887,8 @@ typedef struct DbpageCursor DbpageCursor; struct DbpageCursor { sqlite3_vtab_cursor base; /* Base class. Must be first */ - int pgno; /* Current page number */ - int mxPgno; /* Last page to visit on this scan */ + Pgno pgno; /* Current page number */ + Pgno mxPgno; /* Last page to visit on this scan */ Pager *pPager; /* Pager being read/written */ DbPage *pPage1; /* Page 1 of the database */ int iDb; /* Index of database to analyze */ @@ -227816,7 +230025,7 @@ static int dbpageOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor){ }else{ memset(pCsr, 0, sizeof(DbpageCursor)); pCsr->base.pVtab = pVTab; - pCsr->pgno = -1; + pCsr->pgno = 0; } *ppCursor = (sqlite3_vtab_cursor *)pCsr; @@ -227869,7 +230078,8 @@ static int dbpageFilter( sqlite3 *db = pTab->db; Btree *pBt; - (void)idxStr; + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); /* Default setting is no rows of result */ pCsr->pgno = 1; @@ -227915,12 +230125,12 @@ static int dbpageColumn( int rc = SQLITE_OK; switch( i ){ case 0: { /* pgno */ - sqlite3_result_int(ctx, pCsr->pgno); + sqlite3_result_int64(ctx, (sqlite3_int64)pCsr->pgno); break; } case 1: { /* data */ DbPage *pDbPage = 0; - if( pCsr->pgno==((PENDING_BYTE/pCsr->szPage)+1) ){ + if( pCsr->pgno==(Pgno)((PENDING_BYTE/pCsr->szPage)+1) ){ /* The pending byte page. Assume it is zeroed out. Attempting to ** request this page from the page is an SQLITE_CORRUPT error. */ sqlite3_result_zeroblob(ctx, pCsr->szPage); @@ -227994,10 +230204,10 @@ static int dbpageUpdate( goto update_fail; } if( sqlite3_value_type(argv[0])==SQLITE_NULL ){ - pgno = (Pgno)sqlite3_value_int(argv[2]); + pgno = (Pgno)sqlite3_value_int64(argv[2]); isInsert = 1; }else{ - pgno = sqlite3_value_int(argv[0]); + pgno = (Pgno)sqlite3_value_int64(argv[0]); if( (Pgno)sqlite3_value_int(argv[1])!=pgno ){ zErr = "cannot insert"; goto update_fail; @@ -228049,7 +230259,8 @@ static int dbpageUpdate( memcpy(aPage, pData, szPage); pTab->pgnoTrunc = 0; } - }else{ + } + if( rc!=SQLITE_OK ){ pTab->pgnoTrunc = 0; } sqlite3PagerUnref(pDbPage); @@ -228132,6 +230343,536 @@ SQLITE_PRIVATE int sqlite3DbpageRegister(sqlite3 *db){ return SQLITE_OK; } #endif /* SQLITE_ENABLE_DBSTAT_VTAB */ /************** End of dbpage.c **********************************************/ +/************** Begin file carray.c ******************************************/ +/* +** 2016-06-29 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This file implements a table-valued-function that +** returns the values in a C-language array. +** Examples: +** +** SELECT * FROM carray($ptr,5) +** +** The query above returns 5 integers contained in a C-language array +** at the address $ptr. $ptr is a pointer to the array of integers. +** The pointer value must be assigned to $ptr using the +** sqlite3_bind_pointer() interface with a pointer type of "carray". +** For example: +** +** static int aX[] = { 53, 9, 17, 2231, 4, 99 }; +** int i = sqlite3_bind_parameter_index(pStmt, "$ptr"); +** sqlite3_bind_pointer(pStmt, i, aX, "carray", 0); +** +** There is an optional third parameter to determine the datatype of +** the C-language array. Allowed values of the third parameter are +** 'int32', 'int64', 'double', 'char*', 'struct iovec'. Example: +** +** SELECT * FROM carray($ptr,10,'char*'); +** +** The default value of the third parameter is 'int32'. +** +** HOW IT WORKS +** +** The carray "function" is really a virtual table with the +** following schema: +** +** CREATE TABLE carray( +** value, +** pointer HIDDEN, +** count HIDDEN, +** ctype TEXT HIDDEN +** ); +** +** If the hidden columns "pointer" and "count" are unconstrained, then +** the virtual table has no rows. Otherwise, the virtual table interprets +** the integer value of "pointer" as a pointer to the array and "count" +** as the number of elements in the array. The virtual table steps through +** the array, element by element. +*/ +#if !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) +/* #include "sqliteInt.h" */ +#if defined(_WIN32) || defined(__RTP__) || defined(_WRS_KERNEL) + struct iovec { + void *iov_base; + size_t iov_len; + }; +#else +# include <sys/uio.h> +#endif + +/* +** Names of allowed datatypes +*/ +static const char *azCarrayType[] = { + "int32", "int64", "double", "char*", "struct iovec" +}; + +/* +** Structure used to hold the sqlite3_carray_bind() information +*/ +typedef struct carray_bind carray_bind; +struct carray_bind { + void *aData; /* The data */ + int nData; /* Number of elements */ + int mFlags; /* Control flags */ + void (*xDel)(void*); /* Destructor for aData */ +}; + + +/* carray_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct carray_cursor carray_cursor; +struct carray_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_int64 iRowid; /* The rowid */ + void *pPtr; /* Pointer to the array of values */ + sqlite3_int64 iCnt; /* Number of integers in the array */ + unsigned char eType; /* One of the CARRAY_type values */ +}; + +/* +** The carrayConnect() method is invoked to create a new +** carray_vtab that describes the carray virtual table. +** +** Think of this routine as the constructor for carray_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the carray_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against carray will look like. +*/ +static int carrayConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + int rc; + +/* Column numbers */ +#define CARRAY_COLUMN_VALUE 0 +#define CARRAY_COLUMN_POINTER 1 +#define CARRAY_COLUMN_COUNT 2 +#define CARRAY_COLUMN_CTYPE 3 + + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(value,pointer hidden,count hidden,ctype hidden)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + } + return rc; +} + +/* +** This method is the destructor for carray_cursor objects. +*/ +static int carrayDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new carray_cursor object. +*/ +static int carrayOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + carray_cursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* +** Destructor for a carray_cursor. +*/ +static int carrayClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; +} + + +/* +** Advance a carray_cursor to its next row of output. +*/ +static int carrayNext(sqlite3_vtab_cursor *cur){ + carray_cursor *pCur = (carray_cursor*)cur; + pCur->iRowid++; + return SQLITE_OK; +} + +/* +** Return values of columns for the row at which the carray_cursor +** is currently pointing. +*/ +static int carrayColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + carray_cursor *pCur = (carray_cursor*)cur; + sqlite3_int64 x = 0; + switch( i ){ + case CARRAY_COLUMN_POINTER: return SQLITE_OK; + case CARRAY_COLUMN_COUNT: x = pCur->iCnt; break; + case CARRAY_COLUMN_CTYPE: { + sqlite3_result_text(ctx, azCarrayType[pCur->eType], -1, SQLITE_STATIC); + return SQLITE_OK; + } + default: { + switch( pCur->eType ){ + case CARRAY_INT32: { + int *p = (int*)pCur->pPtr; + sqlite3_result_int(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_INT64: { + sqlite3_int64 *p = (sqlite3_int64*)pCur->pPtr; + sqlite3_result_int64(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_DOUBLE: { + double *p = (double*)pCur->pPtr; + sqlite3_result_double(ctx, p[pCur->iRowid-1]); + return SQLITE_OK; + } + case CARRAY_TEXT: { + const char **p = (const char**)pCur->pPtr; + sqlite3_result_text(ctx, p[pCur->iRowid-1], -1, SQLITE_TRANSIENT); + return SQLITE_OK; + } + default: { + const struct iovec *p = (struct iovec*)pCur->pPtr; + assert( pCur->eType==CARRAY_BLOB ); + sqlite3_result_blob(ctx, p[pCur->iRowid-1].iov_base, + (int)p[pCur->iRowid-1].iov_len, SQLITE_TRANSIENT); + return SQLITE_OK; + } + } + } + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} + +/* +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. +*/ +static int carrayRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + carray_cursor *pCur = (carray_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} + +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int carrayEof(sqlite3_vtab_cursor *cur){ + carray_cursor *pCur = (carray_cursor*)cur; + return pCur->iRowid>pCur->iCnt; +} + +/* +** This method is called to "rewind" the carray_cursor object back +** to the first row of output. +*/ +static int carrayFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + carray_cursor *pCur = (carray_cursor *)pVtabCursor; + pCur->pPtr = 0; + pCur->iCnt = 0; + switch( idxNum ){ + case 1: { + carray_bind *pBind = sqlite3_value_pointer(argv[0], "carray-bind"); + if( pBind==0 ) break; + pCur->pPtr = pBind->aData; + pCur->iCnt = pBind->nData; + pCur->eType = pBind->mFlags & 0x07; + break; + } + case 2: + case 3: { + pCur->pPtr = sqlite3_value_pointer(argv[0], "carray"); + pCur->iCnt = pCur->pPtr ? sqlite3_value_int64(argv[1]) : 0; + if( idxNum<3 ){ + pCur->eType = CARRAY_INT32; + }else{ + unsigned char i; + const char *zType = (const char*)sqlite3_value_text(argv[2]); + for(i=0; i<sizeof(azCarrayType)/sizeof(azCarrayType[0]); i++){ + if( sqlite3_stricmp(zType, azCarrayType[i])==0 ) break; + } + if( i>=sizeof(azCarrayType)/sizeof(azCarrayType[0]) ){ + pVtabCursor->pVtab->zErrMsg = sqlite3_mprintf( + "unknown datatype: %Q", zType); + return SQLITE_ERROR; + }else{ + pCur->eType = i; + } + } + break; + } + } + pCur->iRowid = 1; + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the carray virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** idxNum is: +** +** 1 If only the pointer= constraint exists. In this case, the +** parameter must be bound using sqlite3_carray_bind(). +** +** 2 if the pointer= and count= constraints exist. +** +** 3 if the ctype= constraint also exists. +** +** idxNum is 0 otherwise and carray becomes an empty table. +*/ +static int carrayBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int ptrIdx = -1; /* Index of the pointer= constraint, or -1 if none */ + int cntIdx = -1; /* Index of the count= constraint, or -1 if none */ + int ctypeIdx = -1; /* Index of the ctype= constraint, or -1 if none */ + unsigned seen = 0; /* Bitmask of == constrainted columns */ + + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; i<pIdxInfo->nConstraint; i++, pConstraint++){ + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + if( pConstraint->iColumn>=0 ) seen |= 1 << pConstraint->iColumn; + if( pConstraint->usable==0 ) continue; + switch( pConstraint->iColumn ){ + case CARRAY_COLUMN_POINTER: + ptrIdx = i; + break; + case CARRAY_COLUMN_COUNT: + cntIdx = i; + break; + case CARRAY_COLUMN_CTYPE: + ctypeIdx = i; + break; + } + } + if( ptrIdx>=0 ){ + pIdxInfo->aConstraintUsage[ptrIdx].argvIndex = 1; + pIdxInfo->aConstraintUsage[ptrIdx].omit = 1; + pIdxInfo->estimatedCost = (double)1; + pIdxInfo->estimatedRows = 100; + pIdxInfo->idxNum = 1; + if( cntIdx>=0 ){ + pIdxInfo->aConstraintUsage[cntIdx].argvIndex = 2; + pIdxInfo->aConstraintUsage[cntIdx].omit = 1; + pIdxInfo->idxNum = 2; + if( ctypeIdx>=0 ){ + pIdxInfo->aConstraintUsage[ctypeIdx].argvIndex = 3; + pIdxInfo->aConstraintUsage[ctypeIdx].omit = 1; + pIdxInfo->idxNum = 3; + }else if( seen & (1<<CARRAY_COLUMN_CTYPE) ){ + /* In a three-argument carray(), we need to know the value of all + ** three arguments */ + return SQLITE_CONSTRAINT; + } + }else if( seen & (1<<CARRAY_COLUMN_COUNT) ){ + /* In a two-argument carray(), we need to know the value of both + ** arguments */ + return SQLITE_CONSTRAINT; + } + }else{ + pIdxInfo->estimatedCost = (double)2147483647; + pIdxInfo->estimatedRows = 2147483647; + pIdxInfo->idxNum = 0; + } + return SQLITE_OK; +} + +/* +** This following structure defines all the methods for the +** carray virtual table. +*/ +static sqlite3_module carrayModule = { + 0, /* iVersion */ + 0, /* xCreate */ + carrayConnect, /* xConnect */ + carrayBestIndex, /* xBestIndex */ + carrayDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + carrayOpen, /* xOpen - open a cursor */ + carrayClose, /* xClose - close a cursor */ + carrayFilter, /* xFilter - configure scan constraints */ + carrayNext, /* xNext - advance a cursor */ + carrayEof, /* xEof - check for end of scan */ + carrayColumn, /* xColumn - read data */ + carrayRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadow */ + 0 /* xIntegrity */ +}; + +/* +** Destructor for the carray_bind object +*/ +static void carrayBindDel(void *pPtr){ + carray_bind *p = (carray_bind*)pPtr; + if( p->xDel!=SQLITE_STATIC ){ + p->xDel(p->aData); + } + sqlite3_free(p); +} + +/* +** Invoke this interface in order to bind to the single-argument +** version of CARRAY(). +*/ +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, + int idx, + void *aData, + int nData, + int mFlags, + void (*xDestroy)(void*) +){ + carray_bind *pNew = 0; + int i; + int rc = SQLITE_OK; + + /* Ensure that the mFlags value is acceptable. */ + assert( CARRAY_INT32==0 && CARRAY_INT64==1 && CARRAY_DOUBLE==2 ); + assert( CARRAY_TEXT==3 && CARRAY_BLOB==4 ); + if( mFlags<CARRAY_INT32 || mFlags>CARRAY_BLOB ){ + rc = SQLITE_ERROR; + goto carray_bind_error; + } + + pNew = sqlite3_malloc64(sizeof(*pNew)); + if( pNew==0 ){ + rc = SQLITE_NOMEM; + goto carray_bind_error; + } + + pNew->nData = nData; + pNew->mFlags = mFlags; + if( xDestroy==SQLITE_TRANSIENT ){ + sqlite3_int64 sz = nData; + switch( mFlags ){ + case CARRAY_INT32: sz *= 4; break; + case CARRAY_INT64: sz *= 8; break; + case CARRAY_DOUBLE: sz *= 8; break; + case CARRAY_TEXT: sz *= sizeof(char*); break; + default: sz *= sizeof(struct iovec); break; + } + if( mFlags==CARRAY_TEXT ){ + for(i=0; i<nData; i++){ + const char *z = ((char**)aData)[i]; + if( z ) sz += strlen(z) + 1; + } + }else if( mFlags==CARRAY_BLOB ){ + for(i=0; i<nData; i++){ + sz += ((struct iovec*)aData)[i].iov_len; + } + } + + pNew->aData = sqlite3_malloc64( sz ); + if( pNew->aData==0 ){ + rc = SQLITE_NOMEM; + goto carray_bind_error; + } + + if( mFlags==CARRAY_TEXT ){ + char **az = (char**)pNew->aData; + char *z = (char*)&az[nData]; + for(i=0; i<nData; i++){ + const char *zData = ((char**)aData)[i]; + sqlite3_int64 n; + if( zData==0 ){ + az[i] = 0; + continue; + } + az[i] = z; + n = strlen(zData); + memcpy(z, zData, n+1); + z += n+1; + } + }else if( mFlags==CARRAY_BLOB ){ + struct iovec *p = (struct iovec*)pNew->aData; + unsigned char *z = (unsigned char*)&p[nData]; + for(i=0; i<nData; i++){ + size_t n = ((struct iovec*)aData)[i].iov_len; + p[i].iov_len = n; + p[i].iov_base = z; + z += n; + memcpy(p[i].iov_base, ((struct iovec*)aData)[i].iov_base, n); + } + }else{ + memcpy(pNew->aData, aData, sz); + } + pNew->xDel = sqlite3_free; + }else{ + pNew->aData = aData; + pNew->xDel = xDestroy; + } + return sqlite3_bind_pointer(pStmt, idx, pNew, "carray-bind", carrayBindDel); + + carray_bind_error: + if( xDestroy!=SQLITE_STATIC && xDestroy!=SQLITE_TRANSIENT ){ + xDestroy(aData); + } + sqlite3_free(pNew); + return rc; +} + +/* +** Invoke this routine to register the carray() function. +*/ +SQLITE_PRIVATE Module *sqlite3CarrayRegister(sqlite3 *db){ + return sqlite3VtabCreateModule(db, "carray", &carrayModule, 0, 0); +} + +#endif /* !defined(SQLITE_OMIT_VIRTUALTABLE) && defined(SQLITE_ENABLE_CARRAY) */ + +/************** End of carray.c **********************************************/ /************** Begin file sqlite3session.c **********************************/ #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -230950,6 +233691,19 @@ static int sessionAppendDelete( return rc; } +static int sessionPrepare( + sqlite3 *db, + sqlite3_stmt **pp, + char **pzErrmsg, + const char *zSql +){ + int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0); + if( pzErrmsg && rc!=SQLITE_OK ){ + *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + } + return rc; +} + /* ** Formulate and prepare a SELECT statement to retrieve a row from table ** zTab in database zDb based on its primary key. i.e. @@ -230971,12 +233725,12 @@ static int sessionSelectStmt( int nCol, /* Number of columns in table */ const char **azCol, /* Names of table columns */ u8 *abPK, /* PRIMARY KEY array */ - sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */ + sqlite3_stmt **ppStmt, /* OUT: Prepared SELECT statement */ + char **pzErrmsg /* OUT: Error message */ ){ int rc = SQLITE_OK; char *zSql = 0; const char *zSep = ""; - int nSql = -1; int i; SessionBuffer cols = {0, 0, 0}; @@ -231056,7 +233810,7 @@ static int sessionSelectStmt( #endif if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0); + rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql); } sqlite3_free(zSql); sqlite3_free(nooptest.aBuf); @@ -231220,7 +233974,7 @@ static int sessionGenerateChangeset( /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ rc = sessionSelectStmt(db, 0, pSession->zDb, - zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel + zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0 ); } @@ -232429,6 +235183,7 @@ struct SessionApplyCtx { u8 bRebase; /* True to collect rebase information */ u8 bIgnoreNoop; /* True to ignore no-op conflicts */ int bRowid; + char *zErr; /* Error message, if any */ }; /* Number of prepared UPDATE statements to cache. */ @@ -232654,7 +235409,7 @@ static int sessionDeleteRow( } if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0); + rc = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf); } sqlite3_free(buf.aBuf); @@ -232681,7 +235436,7 @@ static int sessionSelectRow( ){ /* TODO */ return sessionSelectStmt(db, p->bIgnoreNoop, - "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect + "main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect, &p->zErr ); } @@ -232718,16 +235473,12 @@ static int sessionInsertRow( sessionAppendStr(&buf, ")", &rc); if( rc==SQLITE_OK ){ - rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0); + rc = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf); } sqlite3_free(buf.aBuf); return rc; } -static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ - return sqlite3_prepare_v2(db, zSql, -1, pp, 0); -} - /* ** Prepare statements for applying changes to the sqlite_stat1 table. ** These are similar to those created by sessionSelectRow(), @@ -232737,14 +235488,14 @@ static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){ static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ int rc = sessionSelectRow(db, "sqlite_stat1", p); if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pInsert, + rc = sessionPrepare(db, &p->pInsert, 0, "INSERT INTO main.sqlite_stat1 VALUES(?1, " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, " "?3)" ); } if( rc==SQLITE_OK ){ - rc = sessionPrepare(db, &p->pDelete, + rc = sessionPrepare(db, &p->pDelete, 0, "DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS " "CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END " "AND (?4 OR stat IS ?3)" @@ -232968,7 +235719,7 @@ static int sessionConflictHandler( void *pCtx, /* First argument for conflict handler */ int *pbReplace /* OUT: Set to true if PK row is found */ ){ - int res = 0; /* Value returned by conflict handler */ + int res = SQLITE_CHANGESET_OMIT;/* Value returned by conflict handler */ int rc; int nCol; int op; @@ -232989,11 +235740,9 @@ static int sessionConflictHandler( if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ - if( p->bIgnoreNoop - && sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) + if( 0==p->bIgnoreNoop + || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) ){ - res = SQLITE_CHANGESET_OMIT; - }else{ pIter->pConflict = p->pSelect; res = xConflict(pCtx, eType, pIter); pIter->pConflict = 0; @@ -233007,7 +235756,9 @@ static int sessionConflictHandler( int nBlob = pIter->in.iNext - pIter->in.iCurrent; sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); return SQLITE_OK; - }else{ + }else if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE + || eType==SQLITE_CHANGESET_CONFLICT + ){ /* No other row with the new.* primary key. */ res = xConflict(pCtx, eType+1, pIter); if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; @@ -233105,7 +235856,7 @@ static int sessionApplyOneOp( sqlite3_step(p->pDelete); rc = sqlite3_reset(p->pDelete); - if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){ + if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){ rc = sessionConflictHandler( SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry ); @@ -233317,6 +236068,10 @@ static int sessionChangesetApply( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), int(*xConflict)( void *pCtx, /* Copy of fifth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -233457,6 +236212,9 @@ static int sessionChangesetApply( ** next change. A log message has already been issued. */ if( schemaMismatch ) continue; + /* If this is a call to apply_v3(), invoke xFilterIter here. */ + if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) continue; + rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx); } @@ -233503,6 +236261,7 @@ static int sessionChangesetApply( assert( sApply.bRebase || sApply.rebase.nBuf==0 ); if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){ + assert( ppRebase!=0 && pnRebase!=0 ); *ppRebase = (void*)sApply.rebase.aBuf; *pnRebase = sApply.rebase.nBuf; sApply.rebase.aBuf = 0; @@ -233520,22 +236279,74 @@ static int sessionChangesetApply( db->flags &= ~((u64)SQLITE_FkNoAction); db->aDb[0].pSchema->schema_cookie -= 32; } + + assert( rc!=SQLITE_OK || sApply.zErr==0 ); + sqlite3_set_errmsg(db, rc, sApply.zErr); + sqlite3_free(sApply.zErr); + sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } /* -** Apply the changeset passed via pChangeset/nChangeset to the main -** database attached to handle "db". +** This function is called by all six sqlite3changeset_apply() variants: +** +** + sqlite3changeset_apply() +** + sqlite3changeset_apply_v2() +** + sqlite3changeset_apply_v3() +** + sqlite3changeset_apply_strm() +** + sqlite3changeset_apply_strm_v2() +** + sqlite3changeset_apply_strm_v3() +** +** Arguments passed to this function are as follows: +** +** db: +** Database handle to apply changeset to main database of. +** +** nChangeset/pChangeset: +** These are both passed zero for the streaming variants. For the normal +** apply() functions, these are passed the size of and the buffer containing +** the changeset, respectively. +** +** xInput/pIn: +** These are both passed zero for the normal variants. For the streaming +** apply() functions, these are passed the input callback and context +** pointer, respectively. +** +** xFilter: +** The filter function as passed to apply() or apply_v2() (to filter by +** table name), if any. This is always NULL for apply_v3() calls. +** +** xFilterIter: +** The filter function as passed to apply_v3(), if any. +** +** xConflict: +** The conflict handler callback (must not be NULL). +** +** pCtx: +** The context pointer passed to the xFilter and xConflict handler callbacks. +** +** ppRebase, pnRebase: +** Zero for apply(). The rebase changeset output pointers, if any, for +** apply_v2() and apply_v3(). +** +** flags: +** Zero for apply(). The flags parameter for apply_v2() and apply_v3(). */ -SQLITE_API int sqlite3changeset_apply_v2( +static int sessionChangesetApplyV23( sqlite3 *db, /* Apply change to "main" db of this handle */ int nChangeset, /* Size of changeset in bytes */ void *pChangeset, /* Changeset blob */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ int(*xFilter)( void *pCtx, /* Copy of sixth arg to _apply() */ const char *zTab /* Table name */ ), + int(*xFilterIter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), int(*xConflict)( void *pCtx, /* Copy of sixth arg to _apply() */ int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ @@ -233546,19 +236357,75 @@ SQLITE_API int sqlite3changeset_apply_v2( int flags ){ sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1); - + int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); + int rc = sessionChangesetStart( + &pIter, xInput, pIn, nChangeset, pChangeset, bInverse, 1 + ); if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags + rc = sessionChangesetApply(db, pIter, + xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags ); } - return rc; } /* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +SQLITE_API int sqlite3changeset_apply_v2( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + const char *zTab /* Table name */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + +/* +** Apply the changeset passed via pChangeset/nChangeset to the main +** database attached to handle "db". +*/ +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing current change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} + +/* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. @@ -233578,8 +236445,10 @@ SQLITE_API int sqlite3changeset_apply( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2( - db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + nChangeset, pChangeset, 0, 0, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } @@ -233588,6 +236457,29 @@ SQLITE_API int sqlite3changeset_apply( ** attached to handle "db". Invoke the supplied conflict handler callback ** to resolve any conflicts encountered while applying the change. */ +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +){ + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + 0, xFilter, xConflict, pCtx, + ppRebase, pnRebase, flags + ); +} SQLITE_API int sqlite3changeset_apply_v2_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ @@ -233605,15 +236497,11 @@ SQLITE_API int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ){ - sqlite3_changeset_iter *pIter; /* Iterator to skip through changeset */ - int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT); - int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1); - if( rc==SQLITE_OK ){ - rc = sessionChangesetApply( - db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags - ); - } - return rc; + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + ppRebase, pnRebase, flags + ); } SQLITE_API int sqlite3changeset_apply_strm( sqlite3 *db, /* Apply change to "main" db of this handle */ @@ -233630,8 +236518,10 @@ SQLITE_API int sqlite3changeset_apply_strm( ), void *pCtx /* First argument passed to xConflict */ ){ - return sqlite3changeset_apply_v2_strm( - db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0 + return sessionChangesetApplyV23(db, + 0, 0, xInput, pIn, + xFilter, 0, xConflict, pCtx, + 0, 0, 0 ); } @@ -235603,27 +238493,20 @@ typedef sqlite3_uint64 u64; # define LARGEST_INT64 (0xffffffff|(((i64)0x7fffffff)<<32)) # define SMALLEST_INT64 (((i64)-1) - LARGEST_INT64) -/* The uptr type is an unsigned integer large enough to hold a pointer +/* +** This macro is used in a single assert() within fts5 to check that an +** allocation is aligned to an 8-byte boundary. But it is a complicated +** macro to get right for multiple platforms without generating warnings. +** So instead of reproducing the entire definition from sqliteInt.h, we +** just do without this assert() for the rare non-amalgamation builds. */ -#if defined(HAVE_STDINT_H) - typedef uintptr_t uptr; -#elif SQLITE_PTRSIZE==4 - typedef u32 uptr; -#else - typedef u64 uptr; -#endif - -#ifdef SQLITE_4_BYTE_ALIGNED_MALLOC -# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&3)==0) -#else -# define EIGHT_BYTE_ALIGNMENT(X) ((((uptr)(X) - (uptr)0)&7)==0) -#endif +#define EIGHT_BYTE_ALIGNMENT(x) 1 /* ** Macros needed to provide flexible arrays in a portable way */ #ifndef offsetof -# define offsetof(STRUCTURE,FIELD) ((size_t)((char*)&((STRUCTURE*)0)->FIELD)) +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) #endif #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) # define FLEXARRAY @@ -236365,7 +239248,7 @@ static int sqlite3Fts5ExprPattern( ** i64 iRowid = sqlite3Fts5ExprRowid(pExpr); ** } */ -static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, int bDesc); +static int sqlite3Fts5ExprFirst(Fts5Expr*, Fts5Index *pIdx, i64 iMin, i64, int bDesc); static int sqlite3Fts5ExprNext(Fts5Expr*, i64 iMax); static int sqlite3Fts5ExprEof(Fts5Expr*); static i64 sqlite3Fts5ExprRowid(Fts5Expr*); @@ -241934,7 +244817,13 @@ static int fts5ExprNodeFirst(Fts5Expr *pExpr, Fts5ExprNode *pNode){ ** Return SQLITE_OK if successful, or an SQLite error code otherwise. It ** is not considered an error if the query does not match any documents. */ -static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bDesc){ +static int sqlite3Fts5ExprFirst( + Fts5Expr *p, + Fts5Index *pIdx, + i64 iFirst, + i64 iLast, + int bDesc +){ Fts5ExprNode *pRoot = p->pRoot; int rc; /* Return code */ @@ -241956,6 +244845,9 @@ static int sqlite3Fts5ExprFirst(Fts5Expr *p, Fts5Index *pIdx, i64 iFirst, int bD assert( pRoot->bEof==0 ); rc = fts5ExprNodeNext(p, pRoot, 0, 0); } + if( fts5RowidCmp(p, pRoot->iRowid, iLast)>0 ){ + pRoot->bEof = 1; + } return rc; } @@ -244808,6 +247700,36 @@ struct Fts5SegIter { u8 bDel; /* True if the delete flag is set */ }; +static int fts5IndexCorruptRowid(Fts5Index *pIdx, i64 iRowid){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption found reading blob %lld from table \"%s\"", + iRowid, pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_ROWID(pIdx, iRowid) fts5IndexCorruptRowid(pIdx, iRowid) + +static int fts5IndexCorruptIter(Fts5Index *pIdx, Fts5SegIter *pIter){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption on page %d, segment %d, table \"%s\"", + pIter->iLeafPgno, pIter->pSeg->iSegid, pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_ITER(pIdx, pIter) fts5IndexCorruptIter(pIdx, pIter) + +static int fts5IndexCorruptIdx(Fts5Index *pIdx){ + pIdx->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(pIdx->pConfig, + "fts5: corruption in table \"%s\"", pIdx->pConfig->zName + ); + return SQLITE_CORRUPT_VTAB; +} +#define FTS5_CORRUPT_IDX(pIdx) fts5IndexCorruptIdx(pIdx) + + /* ** Array of tombstone pages. Reference counted. */ @@ -245097,13 +248019,13 @@ static Fts5Data *fts5DataRead(Fts5Index *p, i64 iRowid){ ** All the reasons those functions might return SQLITE_ERROR - missing ** table, missing row, non-blob/text in block column - indicate ** backing store corruption. */ - if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT; + if( rc==SQLITE_ERROR ) rc = FTS5_CORRUPT_ROWID(p, iRowid); if( rc==SQLITE_OK ){ u8 *aOut = 0; /* Read blob data into this buffer */ - int nByte = sqlite3_blob_bytes(p->pReader); - int szData = (sizeof(Fts5Data) + 7) & ~7; - sqlite3_int64 nAlloc = szData + nByte + FTS5_DATA_PADDING; + i64 nByte = sqlite3_blob_bytes(p->pReader); + i64 szData = (sizeof(Fts5Data) + 7) & ~7; + i64 nAlloc = szData + nByte + FTS5_DATA_PADDING; pRet = (Fts5Data*)sqlite3_malloc64(nAlloc); if( pRet ){ pRet->nn = nByte; @@ -245147,7 +248069,7 @@ static Fts5Data *fts5LeafRead(Fts5Index *p, i64 iRowid){ Fts5Data *pRet = fts5DataRead(p, iRowid); if( pRet ){ if( pRet->nn<4 || pRet->szLeaf>pRet->nn ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); fts5DataRelease(pRet); pRet = 0; } @@ -245506,8 +248428,14 @@ static Fts5Structure *fts5StructureReadUncached(Fts5Index *p){ /* TODO: Do we need this if the leaf-index is appended? Probably... */ memset(&pData->p[pData->nn], 0, FTS5_DATA_PADDING); p->rc = fts5StructureDecode(pData->p, pData->nn, &iCookie, &pRet); - if( p->rc==SQLITE_OK && (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){ - p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); + if( p->rc==SQLITE_OK ){ + if( (pConfig->pgsz==0 || pConfig->iCookie!=iCookie) ){ + p->rc = sqlite3Fts5ConfigLoad(pConfig, iCookie); + } + }else if( p->rc==SQLITE_CORRUPT_VTAB ){ + sqlite3Fts5ConfigErrmsg(p->pConfig, + "fts5: corrupt structure record for table \"%s\"", p->pConfig->zName + ); } fts5DataRelease(pData); if( p->rc!=SQLITE_OK ){ @@ -246130,7 +249058,7 @@ static void fts5SegIterLoadRowid(Fts5Index *p, Fts5SegIter *pIter){ while( iOff>=pIter->pLeaf->szLeaf ){ fts5SegIterNextPage(p, pIter); if( pIter->pLeaf==0 ){ - if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + if( p->rc==SQLITE_OK ) FTS5_CORRUPT_ITER(p, pIter); return; } iOff = 4; @@ -246162,7 +249090,7 @@ static void fts5SegIterLoadTerm(Fts5Index *p, Fts5SegIter *pIter, int nKeep){ iOff += fts5GetVarint32(&a[iOff], nNew); if( iOff+nNew>pIter->pLeaf->szLeaf || nKeep>pIter->term.n || nNew==0 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } pIter->term.n = nKeep; @@ -246357,7 +249285,7 @@ static void fts5SegIterReverseNewPage(Fts5Index *p, Fts5SegIter *pIter){ iRowidOff = fts5LeafFirstRowidOff(pNew); if( iRowidOff ){ if( iRowidOff>=pNew->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); }else{ pIter->pLeaf = pNew; pIter->iLeafOffset = iRowidOff; @@ -246591,7 +249519,7 @@ static void fts5SegIterNext( } assert_nc( iOff<pLeaf->szLeaf ); if( iOff>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } } @@ -246699,18 +249627,20 @@ static void fts5SegIterReverse(Fts5Index *p, Fts5SegIter *pIter){ fts5DataRelease(pIter->pLeaf); pIter->pLeaf = pLast; pIter->iLeafPgno = pgnoLast; - iOff = fts5LeafFirstRowidOff(pLast); - if( iOff>pLast->szLeaf ){ - p->rc = FTS5_CORRUPT; - return; - } - iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); - pIter->iLeafOffset = iOff; + if( p->rc==SQLITE_OK ){ + iOff = fts5LeafFirstRowidOff(pLast); + if( iOff>pLast->szLeaf ){ + FTS5_CORRUPT_ITER(p, pIter); + return; + } + iOff += fts5GetVarint(&pLast->p[iOff], (u64*)&pIter->iRowid); + pIter->iLeafOffset = iOff; - if( fts5LeafIsTermless(pLast) ){ - pIter->iEndofDoclist = pLast->nn+1; - }else{ - pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); + if( fts5LeafIsTermless(pLast) ){ + pIter->iEndofDoclist = pLast->nn+1; + }else{ + pIter->iEndofDoclist = fts5LeafFirstTermOff(pLast); + } } } @@ -246780,7 +249710,7 @@ static void fts5LeafSeek( iPgidx += fts5GetVarint32(&a[iPgidx], iTermOff); iOff = iTermOff; if( iOff>n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } @@ -246823,7 +249753,7 @@ static void fts5LeafSeek( iOff = iTermOff; if( iOff>=n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } @@ -246845,7 +249775,7 @@ static void fts5LeafSeek( iPgidx = (u32)pIter->pLeaf->szLeaf; iPgidx += fts5GetVarint32(&pIter->pLeaf->p[iPgidx], iOff); if( iOff<4 || (i64)iOff>=pIter->pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; }else{ nKeep = 0; @@ -246860,7 +249790,7 @@ static void fts5LeafSeek( search_success: if( (i64)iOff+nNew>n || nNew<1 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ITER(p, pIter); return; } pIter->iLeafOffset = iOff + nNew; @@ -247325,7 +250255,7 @@ static void fts5SegIterGotoPage( assert( iLeafPgno>pIter->iLeafPgno ); if( iLeafPgno>pIter->pSeg->pgnoLast ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); }else{ fts5DataRelease(pIter->pNextLeaf); pIter->pNextLeaf = 0; @@ -247340,7 +250270,7 @@ static void fts5SegIterGotoPage( u8 *a = pIter->pLeaf->p; int n = pIter->pLeaf->szLeaf; if( iOff<4 || iOff>=n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); }else{ iOff += fts5GetVarint(&a[iOff], (u64*)&pIter->iRowid); pIter->iLeafOffset = iOff; @@ -247819,7 +250749,7 @@ static void fts5ChunkIterate( if( nRem<=0 ){ break; }else if( pSeg->pSeg==0 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); return; }else{ pgno++; @@ -248922,7 +251852,7 @@ static void fts5TrimSegments(Fts5Index *p, Fts5Iter *pIter){ ** a single page has been assigned to more than one segment. In ** this case a prior iteration of this loop may have corrupted the ** segment currently being trimmed. */ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iLeafRowid); }else{ fts5BufferZero(&buf); fts5BufferGrow(&p->rc, &buf, pData->nn); @@ -249389,7 +252319,7 @@ static void fts5SecureDeleteOverflow( }else if( bDetailNone ){ break; }else if( iNext>=pLeaf->szLeaf || pLeaf->nn<pLeaf->szLeaf || iNext<4 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); break; }else{ int nShift = iNext - 4; @@ -249409,7 +252339,7 @@ static void fts5SecureDeleteOverflow( i1 += fts5GetVarint32(&aPg[i1], iFirst); if( iFirst<iNext ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); break; } aIdx = sqlite3Fts5MallocZero(&p->rc, (pLeaf->nn-pLeaf->szLeaf)+2); @@ -249632,14 +252562,14 @@ static void fts5DoSecureDelete( nSuffix = (nPrefix2 + nSuffix2) - nPrefix; if( (iKeyOff+nSuffix)>iPgIdx || (iNextOff+nSuffix2)>iPgIdx ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); }else{ if( iKey!=1 ){ iOff += sqlite3Fts5PutVarint(&aPg[iOff], nPrefix); } iOff += sqlite3Fts5PutVarint(&aPg[iOff], nSuffix); if( nPrefix2>pSeg->term.n ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); }else if( nPrefix2>nPrefix ){ memcpy(&aPg[iOff], &pSeg->term.p[nPrefix], nPrefix2-nPrefix); iOff += (nPrefix2-nPrefix); @@ -250063,7 +252993,7 @@ static Fts5Structure *fts5IndexOptimizeStruct( } nByte += (((i64)pStruct->nLevel)+1) * sizeof(Fts5StructureLevel); - assert( nByte==SZ_FTS5STRUCTURE(pStruct->nLevel+2) ); + assert( nByte==(i64)SZ_FTS5STRUCTURE(pStruct->nLevel+2) ); pNew = (Fts5Structure*)sqlite3Fts5MallocZero(&p->rc, nByte); if( pNew ){ @@ -250432,7 +253362,7 @@ static void fts5MergePrefixLists( } if( pHead==0 || pHead->pNext==0 ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_IDX(p); break; } @@ -250469,7 +253399,7 @@ static void fts5MergePrefixLists( assert_nc( tmp.n+nTail<=nTmp ); assert( tmp.n+nTail<=nTmp+nMerge*10 ); if( tmp.n+nTail>nTmp-FTS5_DATA_ZERO_PADDING ){ - if( p->rc==SQLITE_OK ) p->rc = FTS5_CORRUPT; + if( p->rc==SQLITE_OK ) FTS5_CORRUPT_IDX(p); break; } fts5BufferSafeAppendVarint(&out, (tmp.n+nTail) * 2); @@ -251038,11 +253968,14 @@ static int sqlite3Fts5IndexRollback(Fts5Index *p){ */ static int sqlite3Fts5IndexReinit(Fts5Index *p){ Fts5Structure *pTmp; - u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; + union { + Fts5Structure sFts; + u8 tmpSpace[SZ_FTS5STRUCTURE(1)]; + } uFts; fts5StructureInvalidate(p); fts5IndexDiscardData(p); - pTmp = (Fts5Structure*)tmpSpace; - memset(pTmp, 0, SZ_FTS5STRUCTURE(1)); + pTmp = &uFts.sFts; + memset(uFts.tmpSpace, 0, sizeof(uFts.tmpSpace)); if( p->pConfig->bContentlessDelete ){ pTmp->nOriginCntr = 1; } @@ -252502,19 +255435,27 @@ static int fts5TestUtf8(const char *z, int n){ /* ** This function is also purely an internal test. It does not contribute to ** FTS functionality, or even the integrity-check, in any way. +** +** This function sets output variable (*pbFail) to true if the test fails. Or +** leaves it unchanged if the test succeeds. */ static void fts5TestTerm( Fts5Index *p, Fts5Buffer *pPrev, /* Previous term */ const char *z, int n, /* Possibly new term to test */ u64 expected, - u64 *pCksum + u64 *pCksum, + int *pbFail ){ int rc = p->rc; if( pPrev->n==0 ){ fts5BufferSet(&rc, pPrev, n, (const u8*)z); }else - if( rc==SQLITE_OK && (pPrev->n!=n || memcmp(pPrev->p, z, n)) ){ + if( *pbFail==0 + && rc==SQLITE_OK + && (pPrev->n!=n || memcmp(pPrev->p, z, n)) + && (p->pHash==0 || p->pHash->nEntry==0) + ){ u64 cksum3 = *pCksum; const char *zTerm = (const char*)&pPrev->p[1]; /* term sans prefix-byte */ int nTerm = pPrev->n-1; /* Size of zTerm in bytes */ @@ -252564,7 +255505,7 @@ static void fts5TestTerm( fts5BufferSet(&rc, pPrev, n, (const u8*)z); if( rc==SQLITE_OK && cksum3!=expected ){ - rc = FTS5_CORRUPT; + *pbFail = 1; } *pCksum = cksum3; } @@ -252573,7 +255514,7 @@ static void fts5TestTerm( #else # define fts5TestDlidxReverse(x,y,z) -# define fts5TestTerm(u,v,w,x,y,z) +# define fts5TestTerm(t,u,v,w,x,y,z) #endif /* @@ -252598,14 +255539,17 @@ static void fts5IndexIntegrityCheckEmpty( for(i=iFirst; p->rc==SQLITE_OK && i<=iLast; i++){ Fts5Data *pLeaf = fts5DataRead(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); if( pLeaf ){ - if( !fts5LeafIsTermless(pLeaf) ) p->rc = FTS5_CORRUPT; - if( i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf) ) p->rc = FTS5_CORRUPT; + if( !fts5LeafIsTermless(pLeaf) + || (i>=iNoRowid && 0!=fts5LeafFirstRowidOff(pLeaf)) + ){ + FTS5_CORRUPT_ROWID(p, FTS5_SEGMENT_ROWID(pSeg->iSegid, i)); + } } fts5DataRelease(pLeaf); } } -static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ +static void fts5IntegrityCheckPgidx(Fts5Index *p, i64 iRowid, Fts5Data *pLeaf){ i64 iTermOff = 0; int ii; @@ -252623,12 +255567,12 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ iOff = iTermOff; if( iOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else if( iTermOff==nIncr ){ int nByte; iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); if( (iOff+nByte)>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else{ fts5BufferSet(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); } @@ -252637,7 +255581,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ iOff += fts5GetVarint32(&pLeaf->p[iOff], nKeep); iOff += fts5GetVarint32(&pLeaf->p[iOff], nByte); if( nKeep>buf1.n || (iOff+nByte)>pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRowid); }else{ buf1.n = nKeep; fts5BufferAppendBlob(&p->rc, &buf1, nByte, &pLeaf->p[iOff]); @@ -252645,7 +255589,7 @@ static void fts5IntegrityCheckPgidx(Fts5Index *p, Fts5Data *pLeaf){ if( p->rc==SQLITE_OK ){ res = fts5BufferCompare(&buf1, &buf2); - if( res<=0 ) p->rc = FTS5_CORRUPT; + if( res<=0 ) FTS5_CORRUPT_ROWID(p, iRowid); } } fts5BufferSet(&p->rc, &buf2, buf1.n, buf1.p); @@ -252706,7 +255650,7 @@ static void fts5IndexIntegrityCheckSegment( ** entry even if all the terms are removed from it by secure-delete ** operations. */ }else{ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRow); } }else{ @@ -252718,15 +255662,15 @@ static void fts5IndexIntegrityCheckSegment( iOff = fts5LeafFirstTermOff(pLeaf); iRowidOff = fts5LeafFirstRowidOff(pLeaf); if( iRowidOff>=iOff || iOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iRow); }else{ iOff += fts5GetVarint32(&pLeaf->p[iOff], nTerm); res = fts5Memcmp(&pLeaf->p[iOff], zIdxTerm, MIN(nTerm, nIdxTerm)); if( res==0 ) res = nTerm - nIdxTerm; - if( res<0 ) p->rc = FTS5_CORRUPT; + if( res<0 ) FTS5_CORRUPT_ROWID(p, iRow); } - fts5IntegrityCheckPgidx(p, pLeaf); + fts5IntegrityCheckPgidx(p, iRow, pLeaf); } fts5DataRelease(pLeaf); if( p->rc ) break; @@ -252756,7 +255700,7 @@ static void fts5IndexIntegrityCheckSegment( iKey = FTS5_SEGMENT_ROWID(iSegid, iPg); pLeaf = fts5DataRead(p, iKey); if( pLeaf ){ - if( fts5LeafFirstRowidOff(pLeaf)!=0 ) p->rc = FTS5_CORRUPT; + if( fts5LeafFirstRowidOff(pLeaf)!=0 ) FTS5_CORRUPT_ROWID(p, iKey); fts5DataRelease(pLeaf); } } @@ -252771,12 +255715,12 @@ static void fts5IndexIntegrityCheckSegment( int iRowidOff = fts5LeafFirstRowidOff(pLeaf); ASSERT_SZLEAF_OK(pLeaf); if( iRowidOff>=pLeaf->szLeaf ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iKey); }else if( bSecureDelete==0 || iRowidOff>0 ){ i64 iDlRowid = fts5DlidxIterRowid(pDlidx); fts5GetVarint(&pLeaf->p[iRowidOff], (u64*)&iRowid); if( iRowid<iDlRowid || (bSecureDelete==0 && iRowid!=iDlRowid) ){ - p->rc = FTS5_CORRUPT; + FTS5_CORRUPT_ROWID(p, iKey); } } fts5DataRelease(pLeaf); @@ -252828,6 +255772,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum /* Used by extra internal tests only run if NDEBUG is not defined */ u64 cksum3 = 0; /* Checksum based on contents of indexes */ Fts5Buffer term = {0,0,0}; /* Buffer used to hold most recent term */ + int bTestFail = 0; #endif const int flags = FTS5INDEX_QUERY_NOOUTPUT; @@ -252870,7 +255815,7 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum char *z = (char*)fts5MultiIterTerm(pIter, &n); /* If this is a new term, query for it. Update cksum3 with the results. */ - fts5TestTerm(p, &term, z, n, cksum2, &cksum3); + fts5TestTerm(p, &term, z, n, cksum2, &cksum3, &bTestFail); if( p->rc ) break; if( eDetail==FTS5_DETAIL_NONE ){ @@ -252888,15 +255833,26 @@ static int sqlite3Fts5IndexIntegrityCheck(Fts5Index *p, u64 cksum, int bUseCksum } } } - fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3); + fts5TestTerm(p, &term, 0, 0, cksum2, &cksum3, &bTestFail); fts5MultiIterFree(pIter); - if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ) p->rc = FTS5_CORRUPT; - - fts5StructureRelease(pStruct); + if( p->rc==SQLITE_OK && bUseCksum && cksum!=cksum2 ){ + p->rc = FTS5_CORRUPT; + sqlite3Fts5ConfigErrmsg(p->pConfig, + "fts5: checksum mismatch for table \"%s\"", p->pConfig->zName + ); + } #ifdef SQLITE_DEBUG + /* In SQLITE_DEBUG builds, expensive extra checks were run as part of + ** the integrity-check above. If no other errors were detected, but one + ** of these tests failed, set the result to SQLITE_CORRUPT_VTAB here. */ + if( p->rc==SQLITE_OK && bTestFail ){ + p->rc = FTS5_CORRUPT; + } fts5BufferFree(&term); #endif + + fts5StructureRelease(pStruct); fts5BufferFree(&poslist); return fts5IndexReturn(p); } @@ -254240,6 +257196,17 @@ static void fts5SetUniqueFlag(sqlite3_index_info *pIdxInfo){ #endif } +static void fts5SetEstimatedRows(sqlite3_index_info *pIdxInfo, i64 nRow){ +#if SQLITE_VERSION_NUMBER>=3008002 +#ifndef SQLITE_CORE + if( sqlite3_libversion_number()>=3008002 ) +#endif + { + pIdxInfo->estimatedRows = nRow; + } +#endif +} + static int fts5UsePatternMatch( Fts5Config *pConfig, struct sqlite3_index_constraint *p @@ -254375,7 +257342,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ nSeenMatch++; idxStr[iIdxStr++] = 'M'; sqlite3_snprintf(6, &idxStr[iIdxStr], "%d", iCol); - idxStr += strlen(&idxStr[iIdxStr]); + iIdxStr += (int)strlen(&idxStr[iIdxStr]); assert( idxStr[iIdxStr]=='\0' ); } pInfo->aConstraintUsage[i].argvIndex = ++iCons; @@ -254394,6 +257361,7 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ idxStr[iIdxStr++] = '='; bSeenEq = 1; pInfo->aConstraintUsage[i].argvIndex = ++iCons; + pInfo->aConstraintUsage[i].omit = 1; } } } @@ -254441,17 +257409,21 @@ static int fts5BestIndexMethod(sqlite3_vtab *pVTab, sqlite3_index_info *pInfo){ /* Calculate the estimated cost based on the flags set in idxFlags. */ if( bSeenEq ){ - pInfo->estimatedCost = nSeenMatch ? 1000.0 : 10.0; - if( nSeenMatch==0 ) fts5SetUniqueFlag(pInfo); - }else if( bSeenLt && bSeenGt ){ - pInfo->estimatedCost = nSeenMatch ? 5000.0 : 250000.0; - }else if( bSeenLt || bSeenGt ){ - pInfo->estimatedCost = nSeenMatch ? 7500.0 : 750000.0; - }else{ - pInfo->estimatedCost = nSeenMatch ? 10000.0 : 1000000.0; - } - for(i=1; i<nSeenMatch; i++){ - pInfo->estimatedCost *= 0.4; + pInfo->estimatedCost = nSeenMatch ? 1000.0 : 25.0; + fts5SetUniqueFlag(pInfo); + fts5SetEstimatedRows(pInfo, 1); + }else{ + if( bSeenLt && bSeenGt ){ + pInfo->estimatedCost = nSeenMatch ? 5000.0 : 750000.0; + }else if( bSeenLt || bSeenGt ){ + pInfo->estimatedCost = nSeenMatch ? 7500.0 : 2250000.0; + }else{ + pInfo->estimatedCost = nSeenMatch ? 10000.0 : 3000000.0; + } + for(i=1; i<nSeenMatch; i++){ + pInfo->estimatedCost *= 0.4; + } + fts5SetEstimatedRows(pInfo, (i64)(pInfo->estimatedCost / 4.0)); } pInfo->idxNum = idxFlags; @@ -254650,7 +257622,9 @@ static int fts5CursorReseek(Fts5Cursor *pCsr, int *pbSkip){ int bDesc = pCsr->bDesc; i64 iRowid = sqlite3Fts5ExprRowid(pCsr->pExpr); - rc = sqlite3Fts5ExprFirst(pCsr->pExpr, pTab->p.pIndex, iRowid, bDesc); + rc = sqlite3Fts5ExprFirst( + pCsr->pExpr, pTab->p.pIndex, iRowid, pCsr->iLastRowid, bDesc + ); if( rc==SQLITE_OK && iRowid!=sqlite3Fts5ExprRowid(pCsr->pExpr) ){ *pbSkip = 1; } @@ -254822,7 +257796,9 @@ static int fts5CursorFirstSorted( static int fts5CursorFirst(Fts5FullTable *pTab, Fts5Cursor *pCsr, int bDesc){ int rc; Fts5Expr *pExpr = pCsr->pExpr; - rc = sqlite3Fts5ExprFirst(pExpr, pTab->p.pIndex, pCsr->iFirstRowid, bDesc); + rc = sqlite3Fts5ExprFirst( + pExpr, pTab->p.pIndex, pCsr->iFirstRowid, pCsr->iLastRowid, bDesc + ); if( sqlite3Fts5ExprEof(pExpr) ){ CsrFlagSet(pCsr, FTS5CSR_EOF); } @@ -257307,7 +260283,7 @@ static void fts5SourceIdFunc( ){ assert( nArg==0 ); UNUSED_PARAM2(nArg, apUnused); - sqlite3_result_text(pCtx, "fts5: 2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3", -1, SQLITE_TRANSIENT); + sqlite3_result_text(pCtx, "fts5: 2025-11-04 19:38:17 fb2c931ae597f8d00a37574ff67aeed3eced4e5547f9120744ae4bfa8e74527b", -1, SQLITE_TRANSIENT); } /* @@ -257330,9 +260306,9 @@ static void fts5LocaleFunc( sqlite3_value **apArg /* Function arguments */ ){ const char *zLocale = 0; - int nLocale = 0; + i64 nLocale = 0; const char *zText = 0; - int nText = 0; + i64 nText = 0; assert( nArg==2 ); UNUSED_PARAM(nArg); @@ -257349,10 +260325,10 @@ static void fts5LocaleFunc( Fts5Global *p = (Fts5Global*)sqlite3_user_data(pCtx); u8 *pBlob = 0; u8 *pCsr = 0; - int nBlob = 0; + i64 nBlob = 0; nBlob = FTS5_LOCALE_HDR_SIZE + nLocale + 1 + nText; - pBlob = (u8*)sqlite3_malloc(nBlob); + pBlob = (u8*)sqlite3_malloc64(nBlob); if( pBlob==0 ){ sqlite3_result_error_nomem(pCtx); return; @@ -257430,8 +260406,9 @@ static int fts5IntegrityMethod( " FTS5 table %s.%s: %s", zSchema, zTabname, sqlite3_errstr(rc)); } + }else if( (rc&0xff)==SQLITE_CORRUPT ){ + rc = SQLITE_OK; } - sqlite3Fts5IndexCloseReader(pTab->p.pIndex); pTab->p.pConfig->pzErrmsg = 0; diff --git a/src/3rdparty/sqlite/sqlite3.h b/src/3rdparty/sqlite/sqlite3.h index c2ed750305b..70a4a1b1a5e 100644 --- a/src/3rdparty/sqlite/sqlite3.h +++ b/src/3rdparty/sqlite/sqlite3.h @@ -146,9 +146,12 @@ extern "C" { ** [sqlite3_libversion_number()], [sqlite3_sourceid()], ** [sqlite_version()] and [sqlite_source_id()]. */ -#define SQLITE_VERSION "3.50.4" -#define SQLITE_VERSION_NUMBER 3050004 -#define SQLITE_SOURCE_ID "2025-07-30 19:33:53 4d8adfb30e03f9cf27f800a2c1ba3c48fb4ca1b08b0f5ed59a4d5ecbf45e20a3" +#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" /* ** CAPI3REF: Run-Time Library Version Numbers @@ -168,9 +171,9 @@ extern "C" { ** assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 ); ** </pre></blockquote>)^ ** -** ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] -** macro. ^The sqlite3_libversion() function returns a pointer to the -** to the sqlite3_version[] string constant. The sqlite3_libversion() +** ^The sqlite3_version[] string constant contains the text of the +** [SQLITE_VERSION] macro. ^The sqlite3_libversion() function returns a +** pointer to the sqlite3_version[] string constant. The sqlite3_libversion() ** function is provided for use in DLLs since DLL users usually do not have ** direct access to string constants within the DLL. ^The ** sqlite3_libversion_number() function returns an integer equal to @@ -370,7 +373,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** without having to use a lot of C code. ** ** ^The sqlite3_exec() interface runs zero or more UTF-8 encoded, -** semicolon-separate SQL statements passed into its 2nd argument, +** semicolon-separated SQL statements passed into its 2nd argument, ** in the context of the [database connection] passed in as its 1st ** argument. ^If the callback function of the 3rd argument to ** sqlite3_exec() is not NULL, then it is invoked for each result row @@ -403,7 +406,7 @@ typedef int (*sqlite3_callback)(void*,int,char**, char**); ** result row is NULL then the corresponding string pointer for the ** sqlite3_exec() callback is a NULL pointer. ^The 4th argument to the ** sqlite3_exec() callback is an array of pointers to strings where each -** entry represents the name of corresponding result column as obtained +** entry represents the name of a corresponding result column as obtained ** from [sqlite3_column_name()]. ** ** ^If the 2nd parameter to sqlite3_exec() is a NULL pointer, a pointer @@ -497,6 +500,9 @@ SQLITE_API int sqlite3_exec( #define SQLITE_ERROR_MISSING_COLLSEQ (SQLITE_ERROR | (1<<8)) #define SQLITE_ERROR_RETRY (SQLITE_ERROR | (2<<8)) #define SQLITE_ERROR_SNAPSHOT (SQLITE_ERROR | (3<<8)) +#define SQLITE_ERROR_RESERVESIZE (SQLITE_ERROR | (4<<8)) +#define SQLITE_ERROR_KEY (SQLITE_ERROR | (5<<8)) +#define SQLITE_ERROR_UNABLE (SQLITE_ERROR | (6<<8)) #define SQLITE_IOERR_READ (SQLITE_IOERR | (1<<8)) #define SQLITE_IOERR_SHORT_READ (SQLITE_IOERR | (2<<8)) #define SQLITE_IOERR_WRITE (SQLITE_IOERR | (3<<8)) @@ -531,6 +537,8 @@ SQLITE_API int sqlite3_exec( #define SQLITE_IOERR_DATA (SQLITE_IOERR | (32<<8)) #define SQLITE_IOERR_CORRUPTFS (SQLITE_IOERR | (33<<8)) #define SQLITE_IOERR_IN_PAGE (SQLITE_IOERR | (34<<8)) +#define SQLITE_IOERR_BADKEY (SQLITE_IOERR | (35<<8)) +#define SQLITE_IOERR_CODEC (SQLITE_IOERR | (36<<8)) #define SQLITE_LOCKED_SHAREDCACHE (SQLITE_LOCKED | (1<<8)) #define SQLITE_LOCKED_VTAB (SQLITE_LOCKED | (2<<8)) #define SQLITE_BUSY_RECOVERY (SQLITE_BUSY | (1<<8)) @@ -589,7 +597,7 @@ SQLITE_API int sqlite3_exec( ** Note in particular that passing the SQLITE_OPEN_EXCLUSIVE flag into ** [sqlite3_open_v2()] does *not* cause the underlying database file ** to be opened using O_EXCL. Passing SQLITE_OPEN_EXCLUSIVE into -** [sqlite3_open_v2()] has historically be a no-op and might become an +** [sqlite3_open_v2()] has historically been a no-op and might become an ** error in future versions of SQLite. */ #define SQLITE_OPEN_READONLY 0x00000001 /* Ok for sqlite3_open_v2() */ @@ -683,7 +691,7 @@ SQLITE_API int sqlite3_exec( ** SQLite uses one of these integer values as the second ** argument to calls it makes to the xLock() and xUnlock() methods ** of an [sqlite3_io_methods] object. These values are ordered from -** lest restrictive to most restrictive. +** least restrictive to most restrictive. ** ** The argument to xLock() is always SHARED or higher. The argument to ** xUnlock is either SHARED or NONE. @@ -924,7 +932,7 @@ struct sqlite3_io_methods { ** connection. See also [SQLITE_FCNTL_FILE_POINTER]. ** ** <li>[[SQLITE_FCNTL_SYNC_OMITTED]] -** No longer in use. +** The SQLITE_FCNTL_SYNC_OMITTED file-control is no longer used. ** ** <li>[[SQLITE_FCNTL_SYNC]] ** The [SQLITE_FCNTL_SYNC] opcode is generated internally by SQLite and @@ -999,7 +1007,7 @@ struct sqlite3_io_methods { ** ** <li>[[SQLITE_FCNTL_VFSNAME]] ** ^The [SQLITE_FCNTL_VFSNAME] opcode can be used to obtain the names of -** all [VFSes] in the VFS stack. The names are of all VFS shims and the +** all [VFSes] in the VFS stack. The names of all VFS shims and the ** final bottom-level VFS are written into memory obtained from ** [sqlite3_malloc()] and the result is stored in the char* variable ** that the fourth parameter of [sqlite3_file_control()] points to. @@ -1013,7 +1021,7 @@ struct sqlite3_io_methods { ** ^The [SQLITE_FCNTL_VFS_POINTER] opcode finds a pointer to the top-level ** [VFSes] currently in use. ^(The argument X in ** sqlite3_file_control(db,SQLITE_FCNTL_VFS_POINTER,X) must be -** of type "[sqlite3_vfs] **". This opcodes will set *X +** of type "[sqlite3_vfs] **". This opcode will set *X ** to a pointer to the top-level VFS.)^ ** ^When there are multiple VFS shims in the stack, this opcode finds the ** upper-most shim only. @@ -1203,7 +1211,7 @@ struct sqlite3_io_methods { ** <li>[[SQLITE_FCNTL_EXTERNAL_READER]] ** The EXPERIMENTAL [SQLITE_FCNTL_EXTERNAL_READER] opcode is used to detect ** whether or not there is a database client in another process with a wal-mode -** transaction open on the database or not. It is only available on unix.The +** transaction open on the database or not. It is only available on unix. The ** (void*) argument passed with this file-control should be a pointer to a ** value of type (int). The integer value is set to 1 if the database is a wal ** mode database and there exists at least one client in another process that @@ -1221,6 +1229,15 @@ struct sqlite3_io_methods { ** database is not a temp db, then the [SQLITE_FCNTL_RESET_CACHE] file-control ** purges the contents of the in-memory page cache. If there is an open ** transaction, or if the db is a temp-db, this opcode is a no-op, not an error. +** +** <li>[[SQLITE_FCNTL_FILESTAT]] +** The [SQLITE_FCNTL_FILESTAT] opcode returns low-level diagnostic information +** about the [sqlite3_file] objects used access the database and journal files +** for the given schema. The fourth parameter to [sqlite3_file_control()] +** should be an initialized [sqlite3_str] pointer. JSON text describing +** various aspects of the sqlite3_file object is appended to the sqlite3_str. +** The SQLITE_FCNTL_FILESTAT opcode is usually a no-op, unless compile-time +** options are used to enable it. ** </ul> */ #define SQLITE_FCNTL_LOCKSTATE 1 @@ -1266,6 +1283,7 @@ struct sqlite3_io_methods { #define SQLITE_FCNTL_RESET_CACHE 42 #define SQLITE_FCNTL_NULL_IO 43 #define SQLITE_FCNTL_BLOCK_ON_CONNECT 44 +#define SQLITE_FCNTL_FILESTAT 45 /* deprecated names */ #define SQLITE_GET_LOCKPROXYFILE SQLITE_FCNTL_GET_LOCKPROXYFILE @@ -1628,7 +1646,7 @@ struct sqlite3_vfs { ** SQLite interfaces so that an application usually does not need to ** invoke sqlite3_initialize() directly. For example, [sqlite3_open()] ** calls sqlite3_initialize() so the SQLite library will be automatically -** initialized when [sqlite3_open()] is called if it has not be initialized +** initialized when [sqlite3_open()] is called if it has not been initialized ** already. ^However, if SQLite is compiled with the [SQLITE_OMIT_AUTOINIT] ** compile-time option, then the automatic calls to sqlite3_initialize() ** are omitted and the application must call sqlite3_initialize() directly @@ -1885,21 +1903,21 @@ struct sqlite3_mem_methods { ** The [sqlite3_mem_methods] ** structure is filled with the currently defined memory allocation routines.)^ ** This option can be used to overload the default memory allocation -** routines with a wrapper that simulations memory allocation failure or +** routines with a wrapper that simulates memory allocation failure or ** tracks memory usage, for example. </dd> ** ** [[SQLITE_CONFIG_SMALL_MALLOC]] <dt>SQLITE_CONFIG_SMALL_MALLOC</dt> -** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes single argument of +** <dd> ^The SQLITE_CONFIG_SMALL_MALLOC option takes a single argument of ** type int, interpreted as a boolean, which if true provides a hint to ** SQLite that it should avoid large memory allocations if possible. ** SQLite will run faster if it is free to make large memory allocations, -** but some application might prefer to run slower in exchange for +** but some applications might prefer to run slower in exchange for ** guarantees about memory fragmentation that are possible if large ** allocations are avoided. This hint is normally off. ** </dd> ** ** [[SQLITE_CONFIG_MEMSTATUS]] <dt>SQLITE_CONFIG_MEMSTATUS</dt> -** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes single argument of type int, +** <dd> ^The SQLITE_CONFIG_MEMSTATUS option takes a single argument of type int, ** interpreted as a boolean, which enables or disables the collection of ** memory allocation statistics. ^(When memory allocation statistics are ** disabled, the following SQLite interfaces become non-operational: @@ -1944,7 +1962,7 @@ struct sqlite3_mem_methods { ** ^If pMem is NULL and N is non-zero, then each database connection ** does an initial bulk allocation for page cache memory ** from [sqlite3_malloc()] sufficient for N cache lines if N is positive or -** of -1024*N bytes if N is negative, . ^If additional +** of -1024*N bytes if N is negative. ^If additional ** page cache memory is needed beyond what is provided by the initial ** allocation, then SQLite goes to [sqlite3_malloc()] separately for each ** additional cache line. </dd> @@ -1973,7 +1991,7 @@ struct sqlite3_mem_methods { ** <dd> ^(The SQLITE_CONFIG_MUTEX option takes a single argument which is a ** pointer to an instance of the [sqlite3_mutex_methods] structure. ** The argument specifies alternative low-level mutex routines to be used -** in place the mutex routines built into SQLite.)^ ^SQLite makes a copy of +** in place of the mutex routines built into SQLite.)^ ^SQLite makes a copy of ** the content of the [sqlite3_mutex_methods] structure before the call to ** [sqlite3_config()] returns. ^If SQLite is compiled with ** the [SQLITE_THREADSAFE | SQLITE_THREADSAFE=0] compile-time option then @@ -2015,7 +2033,7 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_CONFIG_GETPCACHE2]] <dt>SQLITE_CONFIG_GETPCACHE2</dt> ** <dd> ^(The SQLITE_CONFIG_GETPCACHE2 option takes a single argument which -** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies of +** is a pointer to an [sqlite3_pcache_methods2] object. SQLite copies off ** the current page cache implementation into that object.)^ </dd> ** ** [[SQLITE_CONFIG_LOG]] <dt>SQLITE_CONFIG_LOG</dt> @@ -2032,7 +2050,7 @@ struct sqlite3_mem_methods { ** the logger function is a copy of the first parameter to the corresponding ** [sqlite3_log()] call and is intended to be a [result code] or an ** [extended result code]. ^The third parameter passed to the logger is -** log message after formatting via [sqlite3_snprintf()]. +** a log message after formatting via [sqlite3_snprintf()]. ** The SQLite logging interface is not reentrant; the logger function ** supplied by the application must not invoke any SQLite interface. ** In a multi-threaded application, the application-defined logger @@ -2223,7 +2241,7 @@ struct sqlite3_mem_methods { ** These constants are the available integer configuration options that ** can be passed as the second parameter to the [sqlite3_db_config()] interface. ** -** The [sqlite3_db_config()] interface is a var-args functions. It takes a +** The [sqlite3_db_config()] interface is a var-args function. It takes a ** variable number of parameters, though always at least two. The number of ** parameters passed into sqlite3_db_config() depends on which of these ** constants is given as the second parameter. This documentation page @@ -2335,17 +2353,20 @@ struct sqlite3_mem_methods { ** ** [[SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER]] ** <dt>SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER</dt> -** <dd> ^This option is used to enable or disable the -** [fts3_tokenizer()] function which is part of the -** [FTS3] full-text search engine extension. -** There must be two additional arguments. -** The first argument is an integer which is 0 to disable fts3_tokenizer() or -** positive to enable fts3_tokenizer() or negative to leave the setting -** unchanged. -** The second parameter is a pointer to an integer into which -** is written 0 or 1 to indicate whether fts3_tokenizer is disabled or enabled -** following this call. The second parameter may be a NULL pointer, in -** which case the new setting is not reported back. </dd> +** <dd> ^This option is used to enable or disable using the +** [fts3_tokenizer()] function - part of the [FTS3] full-text search engine +** extension - without using bound parameters as the parameters. Doing so +** is disabled by default. There must be two additional arguments. The first +** argument is an integer. If it is passed 0, then using fts3_tokenizer() +** without bound parameters is disabled. If it is passed a positive value, +** then calling fts3_tokenizer without bound parameters is enabled. If it +** is passed a negative value, this setting is not modified - this can be +** used to query for the current setting. The second parameter is a pointer +** to an integer into which is written 0 or 1 to indicate the current value +** of this setting (after it is modified, if applicable). The second +** parameter may be a NULL pointer, in which case the value of the setting +** is not reported back. Refer to [FTS3] documentation for further details. +** </dd> ** ** [[SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION]] ** <dt>SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION</dt> @@ -2357,8 +2378,8 @@ struct sqlite3_mem_methods { ** When the first argument to this interface is 1, then only the C-API is ** enabled and the SQL function remains disabled. If the first argument to ** this interface is 0, then both the C-API and the SQL function are disabled. -** If the first argument is -1, then no changes are made to state of either the -** C-API or the SQL function. +** If the first argument is -1, then no changes are made to the state of either +** the C-API or the SQL function. ** The second parameter is a pointer to an integer into which ** is written 0 or 1 to indicate whether [sqlite3_load_extension()] interface ** is disabled or enabled following this call. The second parameter may @@ -2476,7 +2497,7 @@ struct sqlite3_mem_methods { ** [[SQLITE_DBCONFIG_LEGACY_ALTER_TABLE]] ** <dt>SQLITE_DBCONFIG_LEGACY_ALTER_TABLE</dt> ** <dd>The SQLITE_DBCONFIG_LEGACY_ALTER_TABLE option activates or deactivates -** the legacy behavior of the [ALTER TABLE RENAME] command such it +** the legacy behavior of the [ALTER TABLE RENAME] command such that it ** behaves as it did prior to [version 3.24.0] (2018-06-04). See the ** "Compatibility Notice" on the [ALTER TABLE RENAME documentation] for ** additional information. This feature can also be turned on and off @@ -2525,7 +2546,7 @@ struct sqlite3_mem_methods { ** <dt>SQLITE_DBCONFIG_LEGACY_FILE_FORMAT</dt> ** <dd>The SQLITE_DBCONFIG_LEGACY_FILE_FORMAT option activates or deactivates ** the legacy file format flag. When activated, this flag causes all newly -** created database file to have a schema format version number (the 4-byte +** created database files to have a schema format version number (the 4-byte ** integer found at offset 44 into the database header) of 1. This in turn ** means that the resulting database file will be readable and writable by ** any SQLite version back to 3.0.0 ([dateof:3.0.0]). Without this setting, @@ -2552,7 +2573,7 @@ struct sqlite3_mem_methods { ** the database handle both when the SQL statement is prepared and when it ** is stepped. The flag is set (collection of statistics is enabled) ** by default. <p>This option takes two arguments: an integer and a pointer to -** an integer.. The first argument is 1, 0, or -1 to enable, disable, or +** an integer. The first argument is 1, 0, or -1 to enable, disable, or ** leave unchanged the statement scanstatus option. If the second argument ** is not NULL, then the value of the statement scanstatus setting after ** processing the first argument is written into the integer that the second @@ -2595,8 +2616,8 @@ struct sqlite3_mem_methods { ** <dd>The SQLITE_DBCONFIG_ENABLE_ATTACH_WRITE option enables or disables the ** ability of the [ATTACH DATABASE] SQL command to open a database for writing. ** This capability is enabled by default. Applications can disable or -** reenable this capability using the current DBCONFIG option. If the -** the this capability is disabled, the [ATTACH] command will still work, +** reenable this capability using the current DBCONFIG option. If +** this capability is disabled, the [ATTACH] command will still work, ** but the database will be opened read-only. If this option is disabled, ** then the ability to create a new database using [ATTACH] is also disabled, ** regardless of the value of the [SQLITE_DBCONFIG_ENABLE_ATTACH_CREATE] @@ -2630,7 +2651,7 @@ struct sqlite3_mem_methods { ** ** <p>Most of the SQLITE_DBCONFIG options take two arguments, so that the ** overall call to [sqlite3_db_config()] has a total of four parameters. -** The first argument (the third parameter to sqlite3_db_config()) is a integer. +** The first argument (the third parameter to sqlite3_db_config()) is an integer. ** The second argument is a pointer to an integer. If the first argument is 1, ** then the option becomes enabled. If the first integer argument is 0, then the ** option is disabled. If the first argument is -1, then the option setting @@ -2920,7 +2941,7 @@ SQLITE_API int sqlite3_is_interrupted(sqlite3*); ** ^These routines return 0 if the statement is incomplete. ^If a ** memory allocation fails, then SQLITE_NOMEM is returned. ** -** ^These routines do not parse the SQL statements thus +** ^These routines do not parse the SQL statements and thus ** will not detect syntactically incorrect SQL. ** ** ^(If SQLite has not been initialized using [sqlite3_initialize()] prior @@ -3037,7 +3058,7 @@ SQLITE_API int sqlite3_busy_timeout(sqlite3*, int ms); ** indefinitely if possible. The results of passing any other negative value ** are undefined. ** -** Internally, each SQLite database handle store two timeout values - the +** Internally, each SQLite database handle stores two timeout values - the ** busy-timeout (used for rollback mode databases, or if the VFS does not ** support blocking locks) and the setlk-timeout (used for blocking locks ** on wal-mode databases). The sqlite3_busy_timeout() method sets both @@ -3067,7 +3088,7 @@ SQLITE_API int sqlite3_setlk_timeout(sqlite3*, int ms, int flags); ** This is a legacy interface that is preserved for backwards compatibility. ** Use of this interface is not recommended. ** -** Definition: A <b>result table</b> is memory data structure created by the +** Definition: A <b>result table</b> is a memory data structure created by the ** [sqlite3_get_table()] interface. A result table records the ** complete query results from one or more queries. ** @@ -3210,7 +3231,7 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); ** ^Calling sqlite3_free() with a pointer previously returned ** by sqlite3_malloc() or sqlite3_realloc() releases that memory so ** that it might be reused. ^The sqlite3_free() routine is -** a no-op if is called with a NULL pointer. Passing a NULL pointer +** a no-op if it is called with a NULL pointer. Passing a NULL pointer ** to sqlite3_free() is harmless. After being freed, memory ** should neither be read nor written. Even reading previously freed ** memory might result in a segmentation fault or other severe error. @@ -3228,13 +3249,13 @@ SQLITE_API char *sqlite3_vsnprintf(int,char*,const char*, va_list); ** sqlite3_free(X). ** ^sqlite3_realloc(X,N) returns a pointer to a memory allocation ** of at least N bytes in size or NULL if insufficient memory is available. -** ^If M is the size of the prior allocation, then min(N,M) bytes -** of the prior allocation are copied into the beginning of buffer returned +** ^If M is the size of the prior allocation, then min(N,M) bytes of the +** prior allocation are copied into the beginning of the buffer returned ** by sqlite3_realloc(X,N) and the prior allocation is freed. ** ^If sqlite3_realloc(X,N) returns NULL and N is positive, then the ** prior allocation is not freed. ** -** ^The sqlite3_realloc64(X,N) interfaces works the same as +** ^The sqlite3_realloc64(X,N) interface works the same as ** sqlite3_realloc(X,N) except that N is a 64-bit unsigned integer instead ** of a 32-bit signed integer. ** @@ -3284,7 +3305,7 @@ SQLITE_API sqlite3_uint64 sqlite3_msize(void*); ** was last reset. ^The values returned by [sqlite3_memory_used()] and ** [sqlite3_memory_highwater()] include any overhead ** added by SQLite in its implementation of [sqlite3_malloc()], -** but not overhead added by the any underlying system library +** but not overhead added by any underlying system library ** routines that [sqlite3_malloc()] may call. ** ** ^The memory high-water mark is reset to the current value of @@ -3736,7 +3757,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** there is no harm in trying.) ** ** ^(<dt>[SQLITE_OPEN_SHAREDCACHE]</dt> -** <dd>The database is opened [shared cache] enabled, overriding +** <dd>The database is opened with [shared cache] enabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** The [use of shared cache mode is discouraged] and hence shared cache @@ -3744,7 +3765,7 @@ SQLITE_API void sqlite3_progress_handler(sqlite3*, int, int(*)(void*), void*); ** this option is a no-op. ** ** ^(<dt>[SQLITE_OPEN_PRIVATECACHE]</dt> -** <dd>The database is opened [shared cache] disabled, overriding +** <dd>The database is opened with [shared cache] disabled, overriding ** the default shared cache setting provided by ** [sqlite3_enable_shared_cache()].)^ ** @@ -4162,7 +4183,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); ** subsequent calls to other SQLite interface functions.)^ ** ** ^The sqlite3_errstr(E) interface returns the English-language text -** that describes the [result code] E, as UTF-8, or NULL if E is not an +** that describes the [result code] E, as UTF-8, or NULL if E is not a ** result code for which a text error message is available. ** ^(Memory to hold the error message string is managed internally ** and must not be freed by the application)^. @@ -4170,7 +4191,7 @@ SQLITE_API void sqlite3_free_filename(sqlite3_filename); ** ^If the most recent error references a specific token in the input ** SQL, the sqlite3_error_offset() interface returns the byte offset ** of the start of that token. ^The byte offset returned by -** sqlite3_error_offset() assumes that the input SQL is UTF8. +** sqlite3_error_offset() assumes that the input SQL is UTF-8. ** ^If the most recent error does not reference a specific token in the input ** SQL, then the sqlite3_error_offset() function returns -1. ** @@ -4196,6 +4217,34 @@ SQLITE_API const char *sqlite3_errstr(int); SQLITE_API int sqlite3_error_offset(sqlite3 *db); /* +** CAPI3REF: Set Error Codes And Message +** METHOD: sqlite3 +** +** Set the error code of the database handle passed as the first argument +** to errcode, and the error message to a copy of nul-terminated string +** zErrMsg. If zErrMsg is passed NULL, then the error message is set to +** the default message associated with the supplied error code. Subsequent +** calls to [sqlite3_errcode()] and [sqlite3_errmsg()] and similar will +** return the values set by this routine in place of what was previously +** set by SQLite itself. +** +** This function returns SQLITE_OK if the error code and error message are +** successfully set, SQLITE_NOMEM if an OOM occurs, and SQLITE_MISUSE if +** the database handle is NULL or invalid. +** +** The error code and message set by this routine remains in effect until +** they are changed, either by another call to this routine or until they are +** changed to by SQLite itself to reflect the result of some subsquent +** API call. +** +** This function is intended for use by SQLite extensions or wrappers. The +** idea is that an extension or wrapper can use this routine to set error +** messages and error codes and thus behave more like a core SQLite +** feature from the point of view of an application. +*/ +SQLITE_API int sqlite3_set_errmsg(sqlite3 *db, int errcode, const char *zErrMsg); + +/* ** CAPI3REF: Prepared Statement Object ** KEYWORDS: {prepared statement} {prepared statements} ** @@ -4269,8 +4318,8 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** ** These constants define various performance limits ** that can be lowered at run-time using [sqlite3_limit()]. -** The synopsis of the meanings of the various limits is shown below. -** Additional information is available at [limits | Limits in SQLite]. +** A concise description of these limits follows, and additional information +** is available at [limits | Limits in SQLite]. ** ** <dl> ** [[SQLITE_LIMIT_LENGTH]] ^(<dt>SQLITE_LIMIT_LENGTH</dt> @@ -4335,7 +4384,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); /* ** CAPI3REF: Prepare Flags ** -** These constants define various flags that can be passed into +** These constants define various flags that can be passed into the ** "prepFlags" parameter of the [sqlite3_prepare_v3()] and ** [sqlite3_prepare16_v3()] interfaces. ** @@ -4422,7 +4471,7 @@ SQLITE_API int sqlite3_limit(sqlite3*, int id, int newVal); ** there is a small performance advantage to passing an nByte parameter that ** is the number of bytes in the input string <i>including</i> ** the nul-terminator. -** Note that nByte measure the length of the input in bytes, not +** Note that nByte measures the length of the input in bytes, not ** characters, even for the UTF-16 interfaces. ** ** ^If pzTail is not NULL then *pzTail is made to point to the first byte @@ -4556,7 +4605,7 @@ SQLITE_API int sqlite3_prepare16_v3( ** ** ^The sqlite3_expanded_sql() interface returns NULL if insufficient memory ** is available to hold the result, or if the result would exceed the -** the maximum string length determined by the [SQLITE_LIMIT_LENGTH]. +** maximum string length determined by the [SQLITE_LIMIT_LENGTH]. ** ** ^The [SQLITE_TRACE_SIZE_LIMIT] compile-time option limits the size of ** bound parameter expansions. ^The [SQLITE_OMIT_TRACE] compile-time @@ -4744,7 +4793,7 @@ typedef struct sqlite3_value sqlite3_value; ** ** The context in which an SQL function executes is stored in an ** sqlite3_context object. ^A pointer to an sqlite3_context object -** is always first parameter to [application-defined SQL functions]. +** is always the first parameter to [application-defined SQL functions]. ** The application-defined SQL function implementation will pass this ** pointer through into calls to [sqlite3_result_int | sqlite3_result()], ** [sqlite3_aggregate_context()], [sqlite3_user_data()], @@ -4868,9 +4917,11 @@ typedef struct sqlite3_context sqlite3_context; ** associated with the pointer P of type T. ^D is either a NULL pointer or ** a pointer to a destructor function for P. ^SQLite will invoke the ** destructor D with a single argument of P when it is finished using -** P. The T parameter should be a static string, preferably a string -** literal. The sqlite3_bind_pointer() routine is part of the -** [pointer passing interface] added for SQLite 3.20.0. +** P, even if the call to sqlite3_bind_pointer() fails. Due to a +** historical design quirk, results are undefined if D is +** SQLITE_TRANSIENT. The T parameter should be a static string, +** preferably a string literal. The sqlite3_bind_pointer() routine is +** part of the [pointer passing interface] added for SQLite 3.20.0. ** ** ^If any of the sqlite3_bind_*() routines are called with a NULL pointer ** for the [prepared statement] or with a prepared statement for which @@ -5481,7 +5532,7 @@ SQLITE_API int sqlite3_column_type(sqlite3_stmt*, int iCol); ** ** ^The sqlite3_finalize() function is called to delete a [prepared statement]. ** ^If the most recent evaluation of the statement encountered no errors -** or if the statement is never been evaluated, then sqlite3_finalize() returns +** or if the statement has never been evaluated, then sqlite3_finalize() returns ** SQLITE_OK. ^If the most recent evaluation of statement S failed, then ** sqlite3_finalize(S) returns the appropriate [error code] or ** [extended error code]. @@ -5713,7 +5764,7 @@ SQLITE_API int sqlite3_create_window_function( /* ** CAPI3REF: Text Encodings ** -** These constant define integer codes that represent the various +** These constants define integer codes that represent the various ** text encodings supported by SQLite. */ #define SQLITE_UTF8 1 /* IMP: R-37514-35566 */ @@ -5805,7 +5856,7 @@ SQLITE_API int sqlite3_create_window_function( ** result. ** Every function that invokes [sqlite3_result_subtype()] should have this ** property. If it does not, then the call to [sqlite3_result_subtype()] -** might become a no-op if the function is used as term in an +** might become a no-op if the function is used as a term in an ** [expression index]. On the other hand, SQL functions that never invoke ** [sqlite3_result_subtype()] should avoid setting this property, as the ** purpose of this property is to disable certain optimizations that are @@ -5932,7 +5983,7 @@ SQLITE_API SQLITE_DEPRECATED int sqlite3_memory_alarm(void(*)(void*,sqlite3_int6 ** sqlite3_value_nochange(X) interface returns true if and only if ** the column corresponding to X is unchanged by the UPDATE operation ** that the xUpdate method call was invoked to implement and if -** and the prior [xColumn] method call that was invoked to extracted +** the prior [xColumn] method call that was invoked to extract ** the value for that column returned without setting a result (probably ** because it queried [sqlite3_vtab_nochange()] and found that the column ** was unchanging). ^Within an [xUpdate] method, any value for which @@ -6205,6 +6256,7 @@ SQLITE_API void sqlite3_set_auxdata(sqlite3_context*, int N, void*, void (*)(voi ** or a NULL pointer if there were no prior calls to ** sqlite3_set_clientdata() with the same values of D and N. ** Names are compared using strcmp() and are thus case sensitive. +** It returns 0 on success and SQLITE_NOMEM on allocation failure. ** ** If P and X are both non-NULL, then the destructor X is invoked with ** argument P on the first of the following occurrences: @@ -8881,9 +8933,18 @@ SQLITE_API int sqlite3_status64( ** ^The sqlite3_db_status() routine returns SQLITE_OK on success and a ** non-zero [error code] on failure. ** +** ^The sqlite3_db_status64(D,O,C,H,R) routine works exactly the same +** way as sqlite3_db_status(D,O,C,H,R) routine except that the C and H +** parameters are pointer to 64-bit integers (type: sqlite3_int64) instead +** of pointers to 32-bit integers, which allows larger status values +** to be returned. If a status value exceeds 2,147,483,647 then +** sqlite3_db_status() will truncate the value whereas sqlite3_db_status64() +** will return the full value. +** ** See also: [sqlite3_status()] and [sqlite3_stmt_status()]. */ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int resetFlg); +SQLITE_API int sqlite3_db_status64(sqlite3*,int,sqlite3_int64*,sqlite3_int64*,int); /* ** CAPI3REF: Status Parameters for database connections @@ -8980,6 +9041,10 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** If an IO or other error occurs while writing a page to disk, the effect ** on subsequent SQLITE_DBSTATUS_CACHE_WRITE requests is undefined.)^ ^The ** highwater mark associated with SQLITE_DBSTATUS_CACHE_WRITE is always 0. +** <p> +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_CACHE_WRITE) and SQLITE_DBSTATUS_TEMPBUF_SPILL. +** Resetting one will reduce the other.)^ ** </dd> ** ** [[SQLITE_DBSTATUS_CACHE_SPILL]] ^(<dt>SQLITE_DBSTATUS_CACHE_SPILL</dt> @@ -8995,6 +9060,18 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** <dd>This parameter returns zero for the current value if and only if ** all foreign key constraints (deferred or immediate) have been ** resolved.)^ ^The highwater mark is always 0. +** +** [[SQLITE_DBSTATUS_TEMPBUF_SPILL] ^(<dt>SQLITE_DBSTATUS_TEMPBUF_SPILL</dt> +** <dd>^(This parameter returns the number of bytes written to temporary +** files on disk that could have been kept in memory had sufficient memory +** been available. This value includes writes to intermediate tables that +** are part of complex queries, external sorts that spill to disk, and +** writes to TEMP tables.)^ +** ^The highwater mark is always 0. +** <p> +** ^(There is overlap between the quantities measured by this parameter +** (SQLITE_DBSTATUS_TEMPBUF_SPILL) and SQLITE_DBSTATUS_CACHE_WRITE. +** Resetting one will reduce the other.)^ ** </dd> ** </dl> */ @@ -9011,7 +9088,8 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r #define SQLITE_DBSTATUS_DEFERRED_FKS 10 #define SQLITE_DBSTATUS_CACHE_USED_SHARED 11 #define SQLITE_DBSTATUS_CACHE_SPILL 12 -#define SQLITE_DBSTATUS_MAX 12 /* Largest defined DBSTATUS */ +#define SQLITE_DBSTATUS_TEMPBUF_SPILL 13 +#define SQLITE_DBSTATUS_MAX 13 /* Largest defined DBSTATUS */ /* @@ -9776,7 +9854,7 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** is the number of pages currently in the write-ahead log file, ** including those that were just committed. ** -** The callback function should normally return [SQLITE_OK]. ^If an error +** ^The callback function should normally return [SQLITE_OK]. ^If an error ** code is returned, that error will propagate back up through the ** SQLite code base to cause the statement that provoked the callback ** to report an error, though the commit will have still occurred. If the @@ -9784,13 +9862,26 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback -** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^The return value is -** a copy of the third parameter from the previous call, if any, or 0. -** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the -** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will -** overwrite any prior [sqlite3_wal_hook()] settings. +** ^A single database handle may have at most a single write-ahead log +** callback registered at one time. ^Calling [sqlite3_wal_hook()] +** replaces the default behavior or previously registered write-ahead +** log callback. +** +** ^The return value is a copy of the third parameter from the +** previous call, if any, or 0. +** +** ^The [sqlite3_wal_autocheckpoint()] interface and the +** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and +** will overwrite any prior [sqlite3_wal_hook()] settings. +** +** ^If a write-ahead log callback is set using this function then +** [sqlite3_wal_checkpoint_v2()] or [PRAGMA wal_checkpoint] +** should be invoked periodically to keep the write-ahead log file +** from growing without bound. +** +** ^Passing a NULL pointer for the callback disables automatic +** checkpointing entirely. To re-enable the default behavior, call +** sqlite3_wal_autocheckpoint(db,1000) or use [PRAGMA wal_checkpoint]. */ SQLITE_API void *sqlite3_wal_hook( sqlite3*, @@ -9807,7 +9898,7 @@ SQLITE_API void *sqlite3_wal_hook( ** to automatically [checkpoint] ** after committing a transaction if there are N or ** more frames in the [write-ahead log] file. ^Passing zero or -** a negative value as the nFrame parameter disables automatic +** a negative value as the N parameter disables automatic ** checkpoints entirely. ** ** ^The callback registered by this function replaces any existing callback @@ -9823,9 +9914,10 @@ SQLITE_API void *sqlite3_wal_hook( ** ** ^Every new [database connection] defaults to having the auto-checkpoint ** enabled with a threshold of 1000 or [SQLITE_DEFAULT_WAL_AUTOCHECKPOINT] -** pages. The use of this interface -** is only necessary if the default setting is found to be suboptimal -** for a particular application. +** pages. +** +** ^The use of this interface is only necessary if the default setting +** is found to be suboptimal for a particular application. */ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); @@ -9890,6 +9982,11 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** ^This mode works the same way as SQLITE_CHECKPOINT_RESTART with the ** addition that it also truncates the log file to zero bytes just prior ** to a successful return. +** +** <dt>SQLITE_CHECKPOINT_NOOP<dd> +** ^This mode always checkpoints zero frames. The only reason to invoke +** a NOOP checkpoint is to access the values returned by +** sqlite3_wal_checkpoint_v2() via output parameters *pnLog and *pnCkpt. ** </dl> ** ** ^If pnLog is not NULL, then *pnLog is set to the total number of frames in @@ -9960,6 +10057,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( ** See the [sqlite3_wal_checkpoint_v2()] documentation for details on the ** meaning of each of these checkpoint modes. */ +#define SQLITE_CHECKPOINT_NOOP -1 /* Do no work at all */ #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ #define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ @@ -10787,7 +10885,7 @@ typedef struct sqlite3_snapshot { ** The [sqlite3_snapshot_get()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( +SQLITE_API int sqlite3_snapshot_get( sqlite3 *db, const char *zSchema, sqlite3_snapshot **ppSnapshot @@ -10836,7 +10934,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** The [sqlite3_snapshot_open()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( +SQLITE_API int sqlite3_snapshot_open( sqlite3 *db, const char *zSchema, sqlite3_snapshot *pSnapshot @@ -10853,7 +10951,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_open( ** The [sqlite3_snapshot_free()] interface is only available when the ** [SQLITE_ENABLE_SNAPSHOT] compile-time option is used. */ -SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); +SQLITE_API void sqlite3_snapshot_free(sqlite3_snapshot*); /* ** CAPI3REF: Compare the ages of two snapshot handles. @@ -10880,7 +10978,7 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( +SQLITE_API int sqlite3_snapshot_cmp( sqlite3_snapshot *p1, sqlite3_snapshot *p2 ); @@ -10908,7 +11006,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_cmp( ** This interface is only available if SQLite is compiled with the ** [SQLITE_ENABLE_SNAPSHOT] option. */ -SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); +SQLITE_API int sqlite3_snapshot_recover(sqlite3 *db, const char *zDb); /* ** CAPI3REF: Serialize a database @@ -10982,12 +11080,13 @@ SQLITE_API unsigned char *sqlite3_serialize( ** ** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then -** reopen S as an in-memory database based on the serialization contained -** in P. The serialized database P is N bytes in size. M is the size of -** the buffer P, which might be larger than N. If M is larger than N, and -** the SQLITE_DESERIALIZE_READONLY bit is not set in F, then SQLite is -** permitted to add content to the in-memory database as long as the total -** size does not exceed M bytes. +** reopen S as an in-memory database based on the serialization +** contained in P. If S is a NULL pointer, the main database is +** used. The serialized database P is N bytes in size. M is the size +** of the buffer P, which might be larger than N. If M is larger than +** N, and the SQLITE_DESERIALIZE_READONLY bit is not set in F, then +** SQLite is permitted to add content to the in-memory database as +** long as the total size does not exceed M bytes. ** ** If the SQLITE_DESERIALIZE_FREEONCLOSE bit is set in F, then SQLite will ** invoke sqlite3_free() on the serialization buffer when the database @@ -11055,6 +11154,54 @@ SQLITE_API int sqlite3_deserialize( #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */ /* +** CAPI3REF: Bind array values to the CARRAY table-valued function +** +** The sqlite3_carray_bind(S,I,P,N,F,X) interface binds an array value to +** one of the first argument of the [carray() table-valued function]. The +** S parameter is a pointer to the [prepared statement] that uses the carray() +** functions. I is the parameter index to be bound. P is a pointer to the +** array to be bound, and N is the number of eements in the array. The +** F argument is one of constants [SQLITE_CARRAY_INT32], [SQLITE_CARRAY_INT64], +** [SQLITE_CARRAY_DOUBLE], [SQLITE_CARRAY_TEXT], or [SQLITE_CARRAY_BLOB] to +** indicate the datatype of the array being bound. The X argument is not a +** NULL pointer, then SQLite will invoke the function X on the P parameter +** after it has finished using P, even if the call to +** sqlite3_carray_bind() fails. The special-case finalizer +** SQLITE_TRANSIENT has no effect here. +*/ +SQLITE_API int sqlite3_carray_bind( + sqlite3_stmt *pStmt, /* Statement to be bound */ + int i, /* Parameter index */ + void *aData, /* Pointer to array data */ + int nData, /* Number of data elements */ + int mFlags, /* CARRAY flags */ + void (*xDel)(void*) /* Destructor for aData */ +); + +/* +** CAPI3REF: Datatypes for the CARRAY table-valued function +** +** The fifth argument to the [sqlite3_carray_bind()] interface musts be +** one of the following constants, to specify the datatype of the array +** that is being bound into the [carray table-valued function]. +*/ +#define SQLITE_CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define SQLITE_CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define SQLITE_CARRAY_DOUBLE 2 /* Data is doubles */ +#define SQLITE_CARRAY_TEXT 3 /* Data is char* */ +#define SQLITE_CARRAY_BLOB 4 /* Data is struct iovec */ + +/* +** Versions of the above #defines that omit the initial SQLITE_, for +** legacy compatibility. +*/ +#define CARRAY_INT32 0 /* Data is 32-bit signed integers */ +#define CARRAY_INT64 1 /* Data is 64-bit signed integers */ +#define CARRAY_DOUBLE 2 /* Data is doubles */ +#define CARRAY_TEXT 3 /* Data is char* */ +#define CARRAY_BLOB 4 /* Data is struct iovec */ + +/* ** Undo the hack that converts floating point types to integer for ** builds on processors without floating point support. */ @@ -12313,14 +12460,32 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** update the "main" database attached to handle db with the changes found in ** the changeset passed via the second and third arguments. ** +** All changes made by these functions are enclosed in a savepoint transaction. +** If any other error (aside from a constraint failure when attempting to +** write to the target database) occurs, then the savepoint transaction is +** rolled back, restoring the target database to its original state, and an +** SQLite error code returned. Additionally, starting with version 3.51.0, +** an error code and error message that may be accessed using the +** [sqlite3_errcode()] and [sqlite3_errmsg()] APIs are left in the database +** handle. +** ** The fourth argument (xFilter) passed to these functions is the "filter -** callback". If it is not NULL, then for each table affected by at least one -** change in the changeset, the filter callback is invoked with -** the table name as the second argument, and a copy of the context pointer -** passed as the sixth argument as the first. If the "filter callback" -** returns zero, then no attempt is made to apply any changes to the table. -** Otherwise, if the return value is non-zero or the xFilter argument to -** is NULL, all changes related to the table are attempted. +** callback". This may be passed NULL, in which case all changes in the +** changeset are applied to the database. For sqlite3changeset_apply() and +** sqlite3_changeset_apply_v2(), if it is not NULL, then it is invoked once +** for each table affected by at least one change in the changeset. In this +** case the table name is passed as the second argument, and a copy of +** the context pointer passed as the sixth argument to apply() or apply_v2() +** as the first. If the "filter callback" returns zero, then no attempt is +** made to apply any changes to the table. Otherwise, if the return value is +** non-zero, all changes related to the table are attempted. +** +** For sqlite3_changeset_apply_v3(), the xFilter callback is invoked once +** per change. The second argument in this case is an sqlite3_changeset_iter +** that may be queried using the usual APIs for the details of the current +** change. If the "filter callback" returns zero in this case, then no attempt +** is made to apply the current change. If it returns non-zero, the change +** is applied. ** ** For each table that is not excluded by the filter callback, this function ** tests that the target database contains a compatible table. A table is @@ -12341,11 +12506,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** one such warning is issued for each table in the changeset. ** ** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for -** each type of change is below. +** to modify the table contents according to each UPDATE, INSERT or DELETE +** change that is not excluded by a filter callback. If a change cannot be +** applied cleanly, the conflict handler function passed as the fifth argument +** to sqlite3changeset_apply() may be invoked. A description of exactly when +** the conflict handler is invoked for each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results ** of passing anything other than a valid function pointer as the xConflict @@ -12441,12 +12606,6 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** This can be used to further customize the application's conflict ** resolution strategy. ** -** All changes made by these functions are enclosed in a savepoint transaction. -** If any other error (aside from a constraint failure when attempting to -** write to the target database) occurs, then the savepoint transaction is -** rolled back, restoring the target database to its original state, and an -** SQLite error code returned. -** ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() ** may set (*ppRebase) to point to a "rebase" that may be used with the @@ -12496,6 +12655,23 @@ SQLITE_API int sqlite3changeset_apply_v2( void **ppRebase, int *pnRebase, /* OUT: Rebase data */ int flags /* SESSION_CHANGESETAPPLY_* flags */ ); +SQLITE_API int sqlite3changeset_apply_v3( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int nChangeset, /* Size of changeset in bytes */ + void *pChangeset, /* Changeset blob */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p /* Handle describing change */ + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, /* OUT: Rebase data */ + int flags /* SESSION_CHANGESETAPPLY_* flags */ +); /* ** CAPI3REF: Flags for sqlite3changeset_apply_v2 @@ -12915,6 +13091,23 @@ SQLITE_API int sqlite3changeset_apply_v2_strm( void **ppRebase, int *pnRebase, int flags ); +SQLITE_API int sqlite3changeset_apply_v3_strm( + sqlite3 *db, /* Apply change to "main" db of this handle */ + int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */ + void *pIn, /* First arg for xInput */ + int(*xFilter)( + void *pCtx, /* Copy of sixth arg to _apply() */ + sqlite3_changeset_iter *p + ), + int(*xConflict)( + void *pCtx, /* Copy of sixth arg to _apply() */ + int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */ + sqlite3_changeset_iter *p /* Handle describing change and conflict */ + ), + void *pCtx, /* First argument passed to xConflict */ + void **ppRebase, int *pnRebase, + int flags +); SQLITE_API int sqlite3changeset_concat_strm( int (*xInputA)(void *pIn, void *pData, int *pnData), void *pInA, diff --git a/src/3rdparty/sqlite/update_sqlite.sh b/src/3rdparty/sqlite/update_sqlite.sh index b214cffc096..4b8e1869d8c 100755 --- a/src/3rdparty/sqlite/update_sqlite.sh +++ b/src/3rdparty/sqlite/update_sqlite.sh @@ -7,8 +7,8 @@ # sqlite.c and sqlite.h and updates qt_attribution.json version_maj=3 -version_min=50 -version_patch=4 +version_min=51 +version_patch=0 year=2025 version=${version_maj}.${version_min}.${version_patch} diff --git a/src/3rdparty/wayland/protocols/color-management/REUSE.toml b/src/3rdparty/wayland/protocols/color-management/REUSE.toml index c7b978663b1..a2803d886f5 100644 --- a/src/3rdparty/wayland/protocols/color-management/REUSE.toml +++ b/src/3rdparty/wayland/protocols/color-management/REUSE.toml @@ -1,11 +1,12 @@ version = 1 [[annotations]] -path = "xx-color-management-v4.xml" +path = "color-management-v1.xml" precedence = "closest" SPDX-FileCopyrightText = ["Copyright 2019 Sebastian Wick", "Copyright 2019 Erwin Burema", "Copyright 2020 AMD", "Copyright 2020-2024 Collabora, Ltd.", - "Copyright 2024 Xaver Hugl"] + "Copyright 2024 Xaver Hugl", + "Copyright 2022-2025 Red Hat, Inc."] SPDX-License-Identifier = "MIT" diff --git a/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml b/src/3rdparty/wayland/protocols/color-management/color-management-v1.xml index eab84dfd992..4c1bc759c39 100644 --- a/src/3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml +++ b/src/3rdparty/wayland/protocols/color-management/color-management-v1.xml @@ -1,11 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> -<protocol name="xx_color_management_v4"> +<protocol name="color_management_v1"> <copyright> Copyright 2019 Sebastian Wick Copyright 2019 Erwin Burema Copyright 2020 AMD Copyright 2020-2024 Collabora, Ltd. Copyright 2024 Xaver Hugl + Copyright 2022-2025 Red Hat, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), @@ -63,20 +64,27 @@ color encoding terminology where possible. The glossary in the color-and-hdr repository shall be the authority on the definition of terms in this protocol. + + Warning! The protocol described in this file is currently in the testing + phase. Backward compatible changes may be added together with the + corresponding interface version bump. Backward incompatible changes can + only be done by creating a new major version of the extension. </description> - <interface name="xx_color_manager_v4" version="1"> + <interface name="wp_color_manager_v1" version="1"> <description summary="color manager singleton"> - A global interface used for getting color management extensions for - wl_surface and wl_output objects, and for creating client defined image - description objects. The extension interfaces allow + A singleton global interface used for getting color management extensions + for wl_surface and wl_output objects, and for creating client defined + image description objects. The extension interfaces allow getting the image description of outputs and setting the image description of surfaces. + + Compositors should never remove this global. </description> <request name="destroy" type="destructor"> <description summary="destroy the color manager"> - Destroy the xx_color_manager_v4 object. This does not affect any other + Destroy the wp_color_manager_v1 object. This does not affect any other objects in any way. </description> </request> @@ -116,9 +124,9 @@ <description summary="compositor supported features"/> <entry name="icc_v2_v4" value="0" - summary="new_icc_creator request"/> + summary="create_icc_creator request"/> <entry name="parametric" value="1" - summary="new_parametric_creator request"/> + summary="create_parametric_creator request"/> <entry name="set_primaries" value="2" summary="parametric set_primaries request"/> <entry name="set_tf_power" value="3" @@ -140,6 +148,8 @@ is supported as well. </description> </entry> + <entry name="windows_scrgb" value="7" + summary="create_windows_scrgb request"/> </enum> <enum name="primaries"> @@ -148,10 +158,12 @@ is the authority, when it comes to the exact values of primaries and authoritative specifications, where an equivalent code point exists. + A value of 0 is invalid and will never be present in the list of enums. + Descriptions do list the specifications for convenience. </description> - <entry name="srgb" value="0"> + <entry name="srgb" value="1"> <description summary="Color primaries for the sRGB color space as defined by the BT.709 standard"> Color primaries as defined by - Rec. ITU-R BT.709-6 @@ -164,7 +176,7 @@ Equivalent to H.273 ColourPrimaries code point 1. </description> </entry> - <entry name="pal_m" value="1"> + <entry name="pal_m" value="2"> <description summary="Color primaries for PAL-M as defined by the BT.470 standard"> Color primaries as defined by - Rec. ITU-R BT.470-6 System M (historical) @@ -175,7 +187,7 @@ Equivalent to H.273 ColourPrimaries code point 4. </description> </entry> - <entry name="pal" value="2"> + <entry name="pal" value="3"> <description summary="Color primaries for PAL as defined by the BT.601 standard"> Color primaries as defined by - Rec. ITU-R BT.470-6 System B, G (historical) @@ -185,7 +197,7 @@ Equivalent to H.273 ColourPrimaries code point 5. </description> </entry> - <entry name="ntsc" value="3"> + <entry name="ntsc" value="4"> <description summary="Color primaries for NTSC as defined by the BT.601 standard"> Color primaries as defined by - Rec. ITU-R BT.601-7 525 @@ -196,13 +208,13 @@ Equivalent to H.273 ColourPrimaries code point 6 and 7. </description> </entry> - <entry name="generic_film" value="4"> + <entry name="generic_film" value="5"> <description summary="Generic film with colour filters using Illuminant C"> Color primaries as defined by H.273 for generic film. Equivalent to H.273 ColourPrimaries code point 8. </description> </entry> - <entry name="bt2020" value="5"> + <entry name="bt2020" value="6"> <description summary="Color primaries as defined by the BT.2020 and BT.2100 standard"> Color primaries as defined by - Rec. ITU-R BT.2020-2 @@ -210,7 +222,7 @@ Equivalent to H.273 ColourPrimaries code point 9. </description> </entry> - <entry name="cie1931_xyz" value="6"> + <entry name="cie1931_xyz" value="7"> <description summary="Color primaries of the full CIE 1931 XYZ color space"> Color primaries as defined as the maximum of the CIE 1931 XYZ color space by @@ -219,21 +231,21 @@ Equivalent to H.273 ColourPrimaries code point 10. </description> </entry> - <entry name="dci_p3" value="7"> + <entry name="dci_p3" value="8"> <description summary="Color primaries of the DCI P3 color space as defined by the SMPTE RP 431 standard"> Color primaries as defined by Digital Cinema System and published in SMPTE RP 431-2 (2011). Equivalent to H.273 ColourPrimaries code point 11. </description> </entry> - <entry name="display_p3" value="8"> + <entry name="display_p3" value="9"> <description summary="Color primaries of Display P3 variant of the DCI-P3 color space as defined by the SMPTE EG 432 standard"> Color primaries as defined by Digital Cinema System and published in SMPTE EG 432-1 (2010). Equivalent to H.273 ColourPrimaries code point 12. </description> </entry> - <entry name="adobe_rgb" value="9"> + <entry name="adobe_rgb" value="10"> <description summary="Color primaries of the Adobe RGB color space as defined by the ISO 12640 standard"> Color primaries as defined by Adobe as "Adobe RGB" and later published by ISO 12640-4 (2011). @@ -243,23 +255,32 @@ <enum name="transfer_function"> <description summary="named transfer functions"> - Named transfer functions used to encode well-known transfer + Named transfer functions used to represent well-known transfer characteristics. H.273 is the authority, when it comes to the exact formulas and authoritative specifications, where an equivalent code point exists. + A value of 0 is invalid and will never be present in the list of enums. + Descriptions do list the specifications for convenience. </description> - <entry name="bt709" value="0"> - <description summary="BT.709 transfer function"> - Transfer characteristics as defined by + <entry name="bt1886" value="1"> + <description summary="BT.1886 display transfer characteristic"> + Rec. ITU-R BT.1886 is the display transfer characteristic assumed by + - Rec. ITU-R BT.601-7 525 and 625 - Rec. ITU-R BT.709-6 - - Rec. ITU-R BT.1361-0 conventional colour gamut system (historical) - Equivalent to H.273 TransferCharacteristics code point 1, 6, 14, 15. + - Rec. ITU-R BT.2020-2 + These recommendations are referred to by H.273 TransferCharacteristics + code points 1, 6, 14, and 15, which are all equivalent. + + This TF implies these default luminances from Rec. ITU-R BT.2035: + - primary color volume minimum: 0.01 cd/m² + - primary color volume maximum: 100 cd/m² + - reference white: 100 cd/m² </description> </entry> - <entry name="gamma22" value="1"> + <entry name="gamma22" value="2"> <description summary="Assumed display gamma 2.2 transfer function"> Transfer characteristics as defined by - Rec. ITU-R BT.470-6 System M (historical) @@ -269,60 +290,62 @@ of Federal Regulations 73.682 (a) (20) - Rec. ITU-R BT.1700-0 625 PAL and 625 SECAM Equivalent to H.273 TransferCharacteristics code point 4. + + Note: an sRGB display (IEC 61966-2-1) uses this transfer function. </description> </entry> - <entry name="gamma28" value="2"> + <entry name="gamma28" value="3"> <description summary="Assumed display gamma 2.8 transfer function"> Transfer characteristics as defined by - Rec. ITU-R BT.470-6 System B, G (historical) Equivalent to H.273 TransferCharacteristics code point 5. </description> </entry> - <entry name="st240" value="3"> + <entry name="st240" value="4"> <description summary="SMPTE ST 240 transfer function"> Transfer characteristics as defined by - SMPTE ST 240 (1999) Equivalent to H.273 TransferCharacteristics code point 7. </description> </entry> - <entry name="linear" value="4"> - <description summary="linear transfer function"> - Linear transfer characteristics. - Equivalent to H.273 TransferCharacteristics code point 8. + <entry name="ext_linear" value="5"> + <description summary="extended linear transfer function"> + Linear transfer function defined over all real numbers. + Normalised electrical values are equal the normalised optical values. + + The differences to H.273 TransferCharacteristics code point 8 are + the definition over all real numbers. </description> </entry> - <entry name="log_100" value="5"> + <entry name="log_100" value="6"> <description summary="logarithmic 100:1 transfer function"> Logarithmic transfer characteristic (100:1 range). Equivalent to H.273 TransferCharacteristics code point 9. </description> </entry> - <entry name="log_316" value="6"> + <entry name="log_316" value="7"> <description summary="logarithmic (100*Sqrt(10) : 1) transfer function"> Logarithmic transfer characteristic (100 * Sqrt(10) : 1 range). Equivalent to H.273 TransferCharacteristics code point 10. </description> </entry> - <entry name="xvycc" value="7"> + <entry name="xvycc" value="8"> <description summary="IEC 61966-2-4 transfer function"> Transfer characteristics as defined by - IEC 61966-2-4 Equivalent to H.273 TransferCharacteristics code point 11. </description> </entry> - <entry name="bt1361" value="8"> - <description summary="BT.1361 extended transfer function"> - Transfer characteristics as defined by - - Rec. ITU-R BT.1361-0 extended colour gamut system (historical) - Equivalent to H.273 TransferCharacteristics code point 12. - </description> - </entry> <entry name="srgb" value="9"> <description summary="sRGB piece-wise transfer function"> Transfer characteristics as defined by - IEC 61966-2-1 sRGB Equivalent to H.273 TransferCharacteristics code point 13 with MatrixCoefficients set to 0. + + Note: This is not appropriate for describing sRGB material. + sRGB material is intended to be viewed on an sRGB display, and + that is described by gamma22. </description> </entry> <entry name="ext_srgb" value="10"> @@ -344,6 +367,12 @@ - primary color volume minimum: 0.005 cd/m² - primary color volume maximum: 10000 cd/m² - reference white: 203 cd/m² + + The difference between the primary color volume minimum and maximum + must be approximately 10000 cd/m² as that is the swing of the EOTF + defined by ST 2084 and BT.2100. The default value for the + reference white is a protocol addition: it is suggested by + Report ITU-R BT.2408-7 and is not part of ST 2084 or BT.2100. </description> </entry> <entry name="st428" value="12"> @@ -364,87 +393,146 @@ - primary color volume minimum: 0.005 cd/m² - primary color volume maximum: 1000 cd/m² - reference white: 203 cd/m² - Note: HLG is a scene referred signal. All absolute luminance values - used here for HLG assume a 1000 cd/m² display. + + HLG is a relative display-referred signal with a specified + non-linear mapping to the display peak luminance (the HLG OOTF). + All absolute luminance values used here for HLG assume a 1000 cd/m² + peak display. + + The default value for the reference white is a protocol addition: + it is suggested by Report ITU-R BT.2408-7 and is not part of + ARIB STD-B67 or BT.2100. </description> </entry> </enum> <request name="get_output"> <description summary="create a color management interface for a wl_output"> - This creates a new xx_color_management_output_v4 object for the + This creates a new wp_color_management_output_v1 object for the given wl_output. - See the xx_color_management_output_v4 interface for more details. + See the wp_color_management_output_v1 interface for more details. </description> - <arg name="id" type="new_id" interface="xx_color_management_output_v4"/> + <arg name="id" type="new_id" interface="wp_color_management_output_v1"/> <arg name="output" type="object" interface="wl_output"/> </request> <request name="get_surface"> <description summary="create a color management interface for a wl_surface"> - If a xx_color_management_surface_v4 object already exists for the given + If a wp_color_management_surface_v1 object already exists for the given wl_surface, the protocol error surface_exists is raised. - This creates a new color xx_color_management_surface_v4 object for the + This creates a new color wp_color_management_surface_v1 object for the given wl_surface. - See the xx_color_management_surface_v4 interface for more details. + See the wp_color_management_surface_v1 interface for more details. </description> - <arg name="id" type="new_id" interface="xx_color_management_surface_v4"/> + <arg name="id" type="new_id" interface="wp_color_management_surface_v1"/> <arg name="surface" type="object" interface="wl_surface"/> </request> - <request name="get_feedback_surface"> + <request name="get_surface_feedback"> <description summary="create a color management feedback interface"> - This creates a new color xx_color_management_feedback_surface_v4 object + This creates a new color wp_color_management_surface_feedback_v1 object for the given wl_surface. - See the xx_color_management_feedback_surface_v4 interface for more + See the wp_color_management_surface_feedback_v1 interface for more details. </description> <arg name="id" type="new_id" - interface="xx_color_management_feedback_surface_v4"/> + interface="wp_color_management_surface_feedback_v1"/> <arg name="surface" type="object" interface="wl_surface"/> </request> - <request name="new_icc_creator"> + <request name="create_icc_creator"> <description summary="make a new ICC-based image description creator object"> Makes a new ICC-based image description creator object with all properties initially unset. The client can then use the object's interface to define all the required properties for an image description - and finally create a xx_image_description_v4 object. + and finally create a wp_image_description_v1 object. This request can be used when the compositor advertises - xx_color_manager_v4.feature.icc_v2_v4. + wp_color_manager_v1.feature.icc_v2_v4. Otherwise this request raises the protocol error unsupported_feature. </description> <arg name="obj" - type="new_id" interface="xx_image_description_creator_icc_v4" + type="new_id" interface="wp_image_description_creator_icc_v1" summary="the new creator object"/> </request> - <request name="new_parametric_creator"> + <request name="create_parametric_creator"> <description summary="make a new parametric image description creator object"> Makes a new parametric image description creator object with all properties initially unset. The client can then use the object's interface to define all the required properties for an image description - and finally create a xx_image_description_v4 object. + and finally create a wp_image_description_v1 object. This request can be used when the compositor advertises - xx_color_manager_v4.feature.parametric. + wp_color_manager_v1.feature.parametric. Otherwise this request raises the protocol error unsupported_feature. </description> <arg name="obj" - type="new_id" interface="xx_image_description_creator_params_v4" + type="new_id" interface="wp_image_description_creator_params_v1" summary="the new creator object"/> </request> + <request name="create_windows_scrgb"> + <description summary="create Windows-scRGB image description object"> + This creates a pre-defined image description for the so-called + Windows-scRGB stimulus encoding. This comes from the Windows 10 handling + of its own definition of an scRGB color space for an HDR screen + driven in BT.2100/PQ signalling mode. + + Windows-scRGB uses sRGB (BT.709) color primaries and white point. + The transfer characteristic is extended linear. + + The nominal color channel value range is extended, meaning it includes + negative and greater than 1.0 values. Negative values are used to + escape the sRGB color gamut boundaries. To make use of the extended + range, the client needs to use a pixel format that can represent those + values, e.g. floating-point 16 bits per channel. + + Nominal color value R=G=B=0.0 corresponds to BT.2100/PQ system + 0 cd/m², and R=G=B=1.0 corresponds to BT.2100/PQ system 80 cd/m². + The maximum is R=G=B=125.0 corresponding to 10k cd/m². + + Windows-scRGB is displayed by Windows 10 by converting it to + BT.2100/PQ, maintaining the CIE 1931 chromaticity and mapping the + luminance as above. No adjustment is made to the signal to account + for the viewing conditions. + + The reference white level of Windows-scRGB is unknown. If a + reference white level must be assumed for compositor processing, it + should be R=G=B=2.5375 corresponding to 203 cd/m² of Report ITU-R + BT.2408-7. + + The target color volume of Windows-scRGB is unknown. The color gamut + may be anything between sRGB and BT.2100. + + Note: EGL_EXT_gl_colorspace_scrgb_linear definition differs from + Windows-scRGB by using R=G=B=1.0 as the reference white level, while + Windows-scRGB reference white level is unknown or varies. However, + it seems probable that Windows implements both + EGL_EXT_gl_colorspace_scrgb_linear and Vulkan + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT as Windows-scRGB. + + This request can be used when the compositor advertises + wp_color_manager_v1.feature.windows_scrgb. + Otherwise this request raises the protocol error unsupported_feature. + + The resulting image description object does not allow get_information + request. The wp_image_description_v1.ready event shall be sent. + </description> + + <arg name="image_description" + type="new_id" interface="wp_image_description_v1"/> + </request> + <event name="supported_intent"> <description summary="supported rendering intent"> When this object is created, it shall immediately send this event once @@ -486,22 +574,29 @@ <arg name="primaries" type="uint" enum="primaries" summary="Named color primaries"/> </event> + + <event name="done"> + <description summary="all features have been sent"> + This event is sent when all supported rendering intents, features, + transfer functions and named primaries have been sent. + </description> + </event> </interface> - <interface name="xx_color_management_output_v4" version="1"> + <interface name="wp_color_management_output_v1" version="1"> <description summary="output color properties"> - A xx_color_management_output_v4 describes the color properties of an + A wp_color_management_output_v1 describes the color properties of an output. - The xx_color_management_output_v4 is associated with the wl_output global + The wp_color_management_output_v1 is associated with the wl_output global underlying the wl_output object. Therefore the client destroying the wl_output object has no impact, but the compositor removing the output - global makes the xx_color_management_output_v4 object inert. + global makes the wp_color_management_output_v1 object inert. </description> <request name="destroy" type="destructor"> <description summary="destroy the color management output"> - Destroy the color xx_color_management_output_v4 object. This does not + Destroy the color wp_color_management_output_v1 object. This does not affect any remaining protocol objects. </description> </request> @@ -520,12 +615,12 @@ <request name="get_image_description"> <description summary="get the image description of the output"> - This creates a new xx_image_description_v4 object for the current image + This creates a new wp_image_description_v1 object for the current image description of the output. There always is exactly one image description active for an output so the client should destroy the image description created by earlier invocations of this request. This request is usually sent as a reaction to the image_description_changed event or when - creating a xx_color_management_output_v4 object. + creating a wp_color_management_output_v1 object. The image description of an output represents the color encoding the output expects. There might be performance and power advantages, as well @@ -535,41 +630,41 @@ of, then the color reproduction on those outputs might be considerably worse. - The created xx_image_description_v4 object preserves the image + The created wp_image_description_v1 object preserves the image description of the output from the time the object was created. The resulting image description object allows get_information request. If this protocol object is inert, the resulting image description object - shall immediately deliver the xx_image_description_v4.failed event with + shall immediately deliver the wp_image_description_v1.failed event with the no_output cause. If the interface version is inadequate for the output's image description, meaning that the client does not support all the events needed to deliver the crucial information, the resulting image description object shall immediately deliver the - xx_image_description_v4.failed event with the low_version cause. + wp_image_description_v1.failed event with the low_version cause. Otherwise the object shall immediately deliver the ready event. </description> <arg name="image_description" - type="new_id" interface="xx_image_description_v4"/> + type="new_id" interface="wp_image_description_v1"/> </request> </interface> - <interface name="xx_color_management_surface_v4" version="1"> + <interface name="wp_color_management_surface_v1" version="1"> <description summary="color management extension to a surface"> - A xx_color_management_surface_v4 allows the client to set the color + A wp_color_management_surface_v1 allows the client to set the color space and HDR properties of a surface. - If the wl_surface associated with the xx_color_management_surface_v4 is - destroyed, the xx_color_management_surface_v4 object becomes inert. + If the wl_surface associated with the wp_color_management_surface_v1 is + destroyed, the wp_color_management_surface_v1 object becomes inert. </description> <request name="destroy" type="destructor"> <description summary="destroy the color management interface for a surface"> - Destroy the xx_color_management_surface_v4 object and do the same as + Destroy the wp_color_management_surface_v1 object and do the same as unset_image_description. </description> </request> @@ -580,10 +675,14 @@ summary="unsupported rendering intent"/> <entry name="image_description" value="1" summary="invalid image description"/> + <entry name="inert" value="2" + summary="forbidden request on inert object"/> </enum> <request name="set_image_description"> <description summary="set the surface image description"> + If this protocol object is inert, the protocol error inert is raised. + Set the image description of the underlying surface. The image description and rendering intent are double-buffered state, see wl_surface.commit. @@ -593,36 +692,47 @@ description. Compositors might convert images to match their own or any other image descriptions. - Image description whose creation gracefully failed (received - xx_image_description_v4.failed) are forbidden in this request, and in - such case the protocol error image_description is raised. + Image descriptions which are not ready (see wp_image_description_v1) + are forbidden in this request, and in such case the protocol error + image_description is raised. - All image descriptions whose creation succeeded (received - xx_image_description_v4.ready) are allowed and must always be accepted - by the compositor. + All image descriptions which are ready (see wp_image_description_v1) + are allowed and must always be accepted by the compositor. A rendering intent provides the client's preference on how content colors should be mapped to each output. The render_intent value must be one advertised by the compositor with - xx_color_manager_v4.render_intent event, otherwise the protocol error + wp_color_manager_v1.render_intent event, otherwise the protocol error render_intent is raised. + When an image description is set on a surface, the Transfer + Characteristics of the image description defines the valid range of + the nominal (real-valued) color channel values. The processing of + out-of-range color channel values is undefined, but compositors are + recommended to clamp the values to the valid range when possible. + By default, a surface does not have an associated image description nor a rendering intent. The handling of color on such surfaces is compositor implementation defined. Compositors should handle such - surfaces as sRGB but may handle them differently if they have specific + surfaces as sRGB, but may handle them differently if they have specific requirements. + + Setting the image description has copy semantics; after this request, + the image description can be immediately destroyed without affecting + the pending state of the surface. </description> <arg name="image_description" - type="object" interface="xx_image_description_v4"/> + type="object" interface="wp_image_description_v1"/> <arg name="render_intent" - type="uint" enum="xx_color_manager_v4.render_intent" + type="uint" enum="wp_color_manager_v1.render_intent" summary="rendering intent"/> </request> <request name="unset_image_description"> <description summary="remove the surface image description"> + If this protocol object is inert, the protocol error inert is raised. + This request removes any image description from the surface. See set_image_description for how a compositor handles a surface without an image description. This is double-buffered state, see @@ -631,18 +741,18 @@ </request> </interface> - <interface name="xx_color_management_feedback_surface_v4" version="1"> + <interface name="wp_color_management_surface_feedback_v1" version="1"> <description summary="color management extension to a surface"> - A xx_color_management_feedback_surface_v4 allows the client to get the - preferred color description of a surface. + A wp_color_management_surface_feedback_v1 allows the client to get the + preferred image description of a surface. If the wl_surface associated with this object is destroyed, the - xx_color_management_feedback_surface_v4 object becomes inert. + wp_color_management_surface_feedback_v1 object becomes inert. </description> <request name="destroy" type="destructor"> <description summary="destroy the color management interface for a surface"> - Destroy the xx_color_management_feedback_surface_v4 object. + Destroy the wp_color_management_surface_feedback_v1 object. </description> </request> @@ -650,6 +760,8 @@ <description summary="protocol errors"/> <entry name="inert" value="0" summary="forbidden request on inert object"/> + <entry name="unsupported_feature" value="1" + summary="attempted to use an unsupported feature"/> </enum> <event name="preferred_changed"> @@ -659,17 +771,20 @@ client for its wl_surface contents. This event is sent whenever the compositor changes the wl_surface's preferred image description. - This event is merely a notification. When the client wants to know - what the preferred image description is, it shall use the get_preferred - request. + This event sends the identity of the new preferred state as the argument, + so clients who are aware of the image description already can reuse it. + Otherwise, if the client client wants to know what the preferred image + description is, it shall use the get_preferred request. The preferred image description is not automatically used for anything. It is only a hint, and clients may set any valid image description with - set_image_description but there might be performance and color accuracy + set_image_description, but there might be performance and color accuracy improvements by providing the wl_surface contents in the preferred image description. Therefore clients that can, should render according to the preferred image description </description> + + <arg name="identity" type="uint" summary="image description id number"/> </event> <request name="get_preferred"> @@ -682,36 +797,56 @@ reproduction, if the image description of a content update matches the preferred image description. - This creates a new xx_image_description_v4 object for the currently + This creates a new wp_image_description_v1 object for the currently preferred image description for the wl_surface. The client should stop using and destroy the image descriptions created by earlier invocations of this request for the associated wl_surface. This request is usually sent as a reaction to the preferred_changed - event or when creating a xx_color_management_feedback_surface_v4 object + event or when creating a wp_color_management_surface_feedback_v1 object if the client is capable of adapting to image descriptions. - The created xx_image_description_v4 object preserves the preferred image + The created wp_image_description_v1 object preserves the preferred image description of the wl_surface from the time the object was created. The resulting image description object allows get_information request. + If the image description is parametric, the client should set it on its + wl_surface only if the image description is an exact match with the + client content. Particularly if everything else matches, but the target + color volume is greater than what the client needs, the client should + create its own parameric image description with its exact parameters. + If the interface version is inadequate for the preferred image description, meaning that the client does not support all the events needed to deliver the crucial information, the resulting image description object shall immediately deliver the - xx_image_description_v4.failed event with the low_version cause, + wp_image_description_v1.failed event with the low_version cause, otherwise the object shall immediately deliver the ready event. </description> <arg name="image_description" - type="new_id" interface="xx_image_description_v4"/> + type="new_id" interface="wp_image_description_v1"/> + </request> + + <request name="get_preferred_parametric"> + <description summary="get the preferred image description"> + The same description as for get_preferred applies, except the returned + image description is guaranteed to be parametric. This is meant for + clients that can only deal with parametric image descriptions. + + If the compositor doesn't support parametric image descriptions, the + unsupported_feature error is emitted. + </description> + + <arg name="image_description" + type="new_id" interface="wp_image_description_v1"/> </request> </interface> - <interface name="xx_image_description_creator_icc_v4" version="1"> + <interface name="wp_image_description_creator_icc_v1" version="1"> <description summary="holder of image description ICC information"> This type of object is used for collecting all the information required - to create a xx_image_description_v4 object from an ICC file. A complete + to create a wp_image_description_v1 object from an ICC file. A complete set of required parameters consists of these properties: - ICC file @@ -753,19 +888,19 @@ If the particular combination of the information is not supported by the compositor, the resulting image description object shall - immediately deliver the xx_image_description_v4.failed event with the + immediately deliver the wp_image_description_v1.failed event with the 'unsupported' cause. If a valid image description was created from the - information, the xx_image_description_v4.ready event will eventually + information, the wp_image_description_v1.ready event will eventually be sent instead. - This request destroys the xx_image_description_creator_icc_v4 object. + This request destroys the wp_image_description_creator_icc_v1 object. The resulting image description object does not allow get_information request. </description> <arg name="image_description" - type="new_id" interface="xx_image_description_v4"/> + type="new_id" interface="wp_image_description_v1"/> </request> <request name="set_icc_file"> @@ -774,23 +909,23 @@ description. The data shall be found through the given fd at the given offset, having - the given length. The fd must seekable and readable. Violating these + the given length. The fd must be seekable and readable. Violating these requirements raises the bad_fd protocol error. If reading the data fails due to an error independent of the client, the - compositor shall send the xx_image_description_v4.failed event on the - created xx_image_description_v4 with the 'operating_system' cause. + compositor shall send the wp_image_description_v1.failed event on the + created wp_image_description_v1 with the 'operating_system' cause. - The maximum size of the ICC profile is 4 MB. If length is greater than + The maximum size of the ICC profile is 32 MB. If length is greater than that or zero, the protocol error bad_size is raised. If offset + length exceeds the file size, the protocol error out_of_file is raised. A compositor may read the file at any time starting from this request and only until whichever happens first: - - If create request was issued, the xx_image_description_v4 object + - If create request was issued, the wp_image_description_v1 object delivers either failed or ready event; or - if create request was not issued, this - xx_image_description_creator_icc_v4 object is destroyed. + wp_image_description_creator_icc_v1 object is destroyed. A compositor shall not modify the contents of the file, and the fd may be sealed for writes and size changes. The client must ensure to its @@ -800,9 +935,9 @@ The data must represent a valid ICC profile. The ICC profile version must be 2 or 4, it must be a 3 channel profile and the class must be Display or ColorSpace. Violating these requirements will not result in a - protocol error but will eventually send the - xx_image_description_v4.failed event on the created - xx_image_description_v4 with the 'unsupported' cause. + protocol error, but will eventually send the + wp_image_description_v1.failed event on the created + wp_image_description_v1 with the 'unsupported' cause. See the International Color Consortium specification ICC.1:2022 for more details about ICC profiles. @@ -820,10 +955,10 @@ </request> </interface> - <interface name="xx_image_description_creator_params_v4" version="1"> + <interface name="wp_image_description_creator_params_v1" version="1"> <description summary="holder of image description parameters"> This type of object is used for collecting all the parameters required - to create a xx_image_description_v4 object. A complete set of required + to create a wp_image_description_v1 object. A complete set of required parameters consists of these properties: - transfer characteristic function (tf) - chromaticities of primaries and white point (primary color volume) @@ -834,6 +969,9 @@ - reference white luminance level - mastering display primaries and white point (target color volume) - mastering luminance range + + The following properties are optional and will be ignored + if not explicitly set: - maximum content light level - maximum frame-average light level @@ -853,20 +991,16 @@ <entry name="incomplete_set" value="0" summary="incomplete parameter set"/> - <entry name="inconsistent_set" value="1" - summary="invalid combination of parameters"/> - <entry name="already_set" value="2" + <entry name="already_set" value="1" summary="property already set"/> - <entry name="unsupported_feature" value="3" + <entry name="unsupported_feature" value="2" summary="request not supported"/> - <entry name="invalid_tf" value="4" + <entry name="invalid_tf" value="3" summary="invalid transfer characteristic"/> - <entry name="invalid_primaries" value="5" - summary="invalid primaries or white point"/> - <entry name="invalid_luminance" value="6" + <entry name="invalid_primaries_named" value="4" + summary="invalid primaries named"/> + <entry name="invalid_luminance" value="5" summary="invalid luminance value or range"/> - <entry name="invalid_mastering" value="7" - summary="invalid mastering information"/> </enum> <request name="create" type="destructor"> @@ -878,17 +1012,23 @@ complete, the protocol error incomplete_set is raised. For the definition of a complete set, see the description of this interface. - Also, the combination of the parameter set is verified. If the set is - not consistent, the protocol error inconsistent_set is raised. + The protocol error invalid_luminance is raised if any of the following + requirements is not met: + - When max_cll is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When max_fall is set, it must be greater than min L and less or equal + to max L of the mastering luminance range. + - When both max_cll and max_fall are set, max_fall must be less or equal + to max_cll. If the particular combination of the parameter set is not supported by the compositor, the resulting image description object shall - immediately deliver the xx_image_description_v4.failed event with the + immediately deliver the wp_image_description_v1.failed event with the 'unsupported' cause. If a valid image description was created from the - parameter set, the xx_image_description_v4.ready event will eventually + parameter set, the wp_image_description_v1.ready event will eventually be sent instead. - This request destroys the xx_image_description_creator_params_v4 + This request destroys the wp_image_description_creator_params_v1 object. The resulting image description object does not allow get_information @@ -896,7 +1036,7 @@ </description> <arg name="image_description" - type="new_id" interface="xx_image_description_v4"/> + type="new_id" interface="wp_image_description_v1"/> </request> <request name="set_tf_named"> @@ -908,22 +1048,24 @@ content should be encoded and decoded according to the industry standard practices for the transfer characteristic. - Only names advertised with xx_color_manager_v4 event supported_tf_named + Only names advertised with wp_color_manager_v1 event supported_tf_named are allowed. Other values shall raise the protocol error invalid_tf. If transfer characteristic has already been set on this object, the protocol error already_set is raised. </description> - <arg name="tf" type="uint" enum="xx_color_manager_v4.transfer_function" + <arg name="tf" type="uint" enum="wp_color_manager_v1.transfer_function" summary="named transfer function"/> </request> <request name="set_tf_power"> <description summary="transfer characteristic as a power curve"> Sets the color component transfer characteristic to a power curve with - the given exponent. This curve represents the conversion from electrical - to optical pixel or color values. + the given exponent. Negative values are handled by mirroring the + positive half of the curve through the origin. The valid domain and + range of the curve are all finite real numbers. This curve represents + the conversion from electrical to optical color channel values. When the resulting image description is attached to an image, the content should be encoded with the inverse of the power curve. @@ -938,7 +1080,7 @@ protocol error already_set is raised. This request can be used when the compositor advertises - xx_color_manager_v4.feature.set_tf_power. Otherwise this request raises + wp_color_manager_v1.feature.set_tf_power. Otherwise this request raises the protocol error unsupported_feature. </description> @@ -951,15 +1093,15 @@ This describes the primary color volume which is the basis for color value encoding. - Only names advertised with xx_color_manager_v4 event + Only names advertised with wp_color_manager_v1 event supported_primaries_named are allowed. Other values shall raise the - protocol error invalid_primaries. + protocol error invalid_primaries_named. If primaries have already been set on this object, the protocol error already_set is raised. </description> - <arg name="primaries" type="uint" enum="xx_color_manager_v4.primaries" + <arg name="primaries" type="uint" enum="wp_color_manager_v1.primaries" summary="named primaries"/> </request> @@ -969,33 +1111,36 @@ coordinates. This describes the primary color volume which is the basis for color value encoding. - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. If primaries have already been set on this object, the protocol error already_set is raised. This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_primaries. Otherwise this request raises + wp_color_manager_v1.feature.set_primaries. Otherwise this request raises the protocol error unsupported_feature. </description> - <arg name="r_x" type="int" summary="Red x * 10000"/> - <arg name="r_y" type="int" summary="Red y * 10000"/> - <arg name="g_x" type="int" summary="Green x * 10000"/> - <arg name="g_y" type="int" summary="Green y * 10000"/> - <arg name="b_x" type="int" summary="Blue x * 10000"/> - <arg name="b_y" type="int" summary="Blue y * 10000"/> - <arg name="w_x" type="int" summary="White x * 10000"/> - <arg name="w_y" type="int" summary="White y * 10000"/> + <arg name="r_x" type="int" summary="Red x * 1M"/> + <arg name="r_y" type="int" summary="Red y * 1M"/> + <arg name="g_x" type="int" summary="Green x * 1M"/> + <arg name="g_y" type="int" summary="Green y * 1M"/> + <arg name="b_x" type="int" summary="Blue x * 1M"/> + <arg name="b_y" type="int" summary="Blue y * 1M"/> + <arg name="w_x" type="int" summary="White x * 1M"/> + <arg name="w_y" type="int" summary="White y * 1M"/> </request> <request name="set_luminances"> <description summary="primary color volume luminance range and reference white"> Sets the primary color volume luminance range and the reference white - luminance level. + luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. - The default luminances are + The default luminances from + https://www.color.org/chardata/rgb/srgb.xalter are - primary color volume minimum: 0.2 cd/m² - primary color volume maximum: 80 cd/m² - reference white: 80 cd/m² @@ -1004,6 +1149,8 @@ luminances. The default luminances get overwritten when this request is used. + With transfer_function.st2084_pq the given 'max_lum' value is ignored, + and 'max_lum' is taken as 'min_lum' + 10000 cd/m². 'min_lum' and 'max_lum' specify the minimum and maximum luminances of the primary color volume as reproduced by the targeted display. @@ -1018,9 +1165,12 @@ description should produce the same output level, even though the 'reference_lum' on both image representations can be different. - If 'max_lum' is less than the 'reference_lum', or 'reference_lum' is - less than or equal to 'min_lum', the protocol error invalid_luminance is - raised. + 'reference_lum' may be higher than 'max_lum'. In that case reaching + the reference white output level in image content requires the + 'extended_target_volume' feature support. + + If 'max_lum' or 'reference_lum' are less than or equal to 'min_lum', + the protocol error invalid_luminance is raised. The minimum luminance is multiplied by 10000 to get the argument 'min_lum' value and carries precision of 4 decimals. The maximum @@ -1031,7 +1181,7 @@ already_set is raised. This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_luminances. Otherwise this request + wp_color_manager_v1.feature.set_luminances. Otherwise this request raises the protocol error unsupported_feature. </description> @@ -1049,10 +1199,11 @@ using CIE 1931 xy chromaticity coordinates. This is compatible with the SMPTE ST 2086 definition of HDR static metadata. - The mastering display primaries define the target color volume. + The mastering display primaries and mastering display luminances define + the target color volume. If mastering display primaries are not explicitly set, the target color - volume is assumed to be equal to the primary color volume. + volume is assumed to have the same primaries as the primary color volume. The target color volume is defined by all tristimulus values between 0.0 and 1.0 (inclusive) of the color space defined by the given mastering @@ -1071,50 +1222,72 @@ has to be chosen (e.g. floating point to exceed the primary color volume, or abusing limited quantization range as with xvYCC). - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. If mastering display primaries have already been set on this object, the protocol error already_set is raised. This request can be used if the compositor advertises - xx_color_manager_v4.feature.set_mastering_display_primaries. Otherwise + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise this request raises the protocol error unsupported_feature. The advertisement implies support only for target color volumes fully contained within the primary color volume. If a compositor additionally supports target color volume exceeding the primary color volume, it must advertise - xx_color_manager_v4.feature.extended_target_volume. If a client uses + wp_color_manager_v1.feature.extended_target_volume. If a client uses target color volume exceeding the primary color volume and the compositor does not support it, the result is implementation defined. Compositors are recommended to detect this case and fail the image description gracefully, but it may as well result in color artifacts. </description> - <arg name="r_x" type="int" summary="Red x * 10000"/> - <arg name="r_y" type="int" summary="Red y * 10000"/> - <arg name="g_x" type="int" summary="Green x * 10000"/> - <arg name="g_y" type="int" summary="Green y * 10000"/> - <arg name="b_x" type="int" summary="Blue x * 10000"/> - <arg name="b_y" type="int" summary="Blue y * 10000"/> - <arg name="w_x" type="int" summary="White x * 10000"/> - <arg name="w_y" type="int" summary="White y * 10000"/> + <arg name="r_x" type="int" summary="Red x * 1M"/> + <arg name="r_y" type="int" summary="Red y * 1M"/> + <arg name="g_x" type="int" summary="Green x * 1M"/> + <arg name="g_y" type="int" summary="Green y * 1M"/> + <arg name="b_x" type="int" summary="Blue x * 1M"/> + <arg name="b_y" type="int" summary="Blue y * 1M"/> + <arg name="w_x" type="int" summary="White x * 1M"/> + <arg name="w_y" type="int" summary="White y * 1M"/> </request> <request name="set_mastering_luminance"> <description summary="display mastering luminance range"> Sets the luminance range that was used during the content mastering - process as the minimum and maximum absolute luminance L. This is + process as the minimum and maximum absolute luminance L. These values + include the minimum display emission and ambient flare luminances, + assumed to be optically additive and have the chromaticity of the + primary color volume white point. This should be compatible with the SMPTE ST 2086 definition of HDR static metadata. - The mastering luminance range is undefined by default. + The mastering display primaries and mastering display luminances define + the target color volume. + + If mastering luminances are not explicitly set, the target color volume + is assumed to have the same min and max luminances as the primary color + volume. If max L is less than or equal to min L, the protocol error invalid_luminance is raised. Min L value is multiplied by 10000 to get the argument min_lum value and carry precision of 4 decimals. Max L value is unscaled for max_lum. + + This request can be used if the compositor advertises + wp_color_manager_v1.feature.set_mastering_display_primaries. Otherwise + this request raises the protocol error unsupported_feature. The + advertisement implies support only for target color volumes fully + contained within the primary color volume. + + If a compositor additionally supports target color volume exceeding the + primary color volume, it must advertise + wp_color_manager_v1.feature.extended_target_volume. If a client uses + target color volume exceeding the primary color volume and the + compositor does not support it, the result is implementation defined. + Compositors are recommended to detect this case and fail the image + description gracefully, but it may as well result in color artifacts. </description> <arg name="min_lum" type="uint" summary="min L (cd/m²) * 10000"/> @@ -1125,11 +1298,6 @@ <description summary="maximum content light level"> Sets the maximum content light level (max_cll) as defined by CTA-861-H. - This can only be set when set_tf_cicp is used to set the transfer - characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. - Otherwise, 'create' request shall raise inconsistent_set protocol - error. - max_cll is undefined by default. </description> @@ -1141,10 +1309,6 @@ Sets the maximum frame-average light level (max_fall) as defined by CTA-861-H. - This can only be set when set_tf_cicp is used to set the transfer - characteristic to Rec. ITU-R BT.2100-2 perceptual quantization system. - Otherwise, 'create' request shall raise inconsistent_set protocol error. - max_fall is undefined by default. </description> @@ -1152,15 +1316,15 @@ </request> </interface> - <interface name="xx_image_description_v4" version="1"> + <interface name="wp_image_description_v1" version="1"> <description summary="Colorimetric image description"> An image description carries information about the color encoding used on a surface when attached to a wl_surface via - xx_color_management_surface_v4.set_image_description. A compositor can use + wp_color_management_surface_v1.set_image_description. A compositor can use this information to decode pixel values into colorimetrically meaningful quantities. - Note, that the xx_image_description_v4 object is not ready to be used + Note, that the wp_image_description_v1 object is not ready to be used immediately after creation. The object eventually delivers either the 'ready' or the 'failed' event, specified in all requests creating it. The object is deemed "ready" after receiving the 'ready' event. @@ -1171,7 +1335,7 @@ interfaces shall raise protocol errors defined there. Once created and regardless of how it was created, a - xx_image_description_v4 object always refers to one fixed image + wp_image_description_v1 object always refers to one fixed image description. It cannot change after creation. </description> @@ -1179,8 +1343,8 @@ <description summary="destroy the image description"> Destroy this object. It is safe to destroy an object which is not ready. - Destroying a xx_image_description_v4 object has no side-effects, not - even if a xx_color_management_surface_v4.set_image_description has not + Destroying a wp_image_description_v1 object has no side-effects, not + even if a wp_color_management_surface_v1.set_image_description has not yet been followed by a wl_surface.commit. </description> </request> @@ -1209,7 +1373,7 @@ <event name="failed"> <description summary="graceful error on creating the image description"> - If creating a xx_image_description_v4 object fails for a reason that is + If creating a wp_image_description_v1 object fails for a reason that is not defined as a protocol error, this event is sent. The requests that create image description objects define whether and @@ -1217,7 +1381,7 @@ This event cannot be triggered after the image description was successfully formed. - Once this event has been sent, the xx_image_description_v4 object will + Once this event has been sent, the wp_image_description_v1 object will never become ready and it can only be destroyed. </description> @@ -1229,11 +1393,11 @@ <event name="ready"> <description summary="indication that the object is ready to be used"> - Once this event has been sent, the xx_image_description_v4 object is + Once this event has been sent, the wp_image_description_v1 object is deemed "ready". Ready objects can be used to send requests and can be used through other interfaces. - Every ready xx_image_description_v4 protocol object refers to an + Every ready wp_image_description_v1 protocol object refers to an underlying image description record in the compositor. Multiple protocol objects may end up referring to the same record. Clients may identify these "copies" by comparing their id numbers: if the numbers from two @@ -1250,7 +1414,8 @@ Image description id number is not a protocol object id. Zero is reserved as an invalid id number. It shall not be possible for a client to refer to an image description by its id number in protocol. The id - numbers might not be portable between Wayland connections. + numbers might not be portable between Wayland connections. A compositor + shall not send an invalid id number. This identity allows clients to de-duplicate image description records and avoid get_information request if they already have the image @@ -1262,7 +1427,7 @@ <request name="get_information"> <description summary="get information about the image description"> - Creates a xx_image_description_info_v4 object which delivers the + Creates a wp_image_description_info_v1 object which delivers the information that makes up the image description. Not all image description protocol objects allow get_information @@ -1272,20 +1437,34 @@ </description> <arg name="information" - type="new_id" interface="xx_image_description_info_v4"/> + type="new_id" interface="wp_image_description_info_v1"/> </request> </interface> - <interface name="xx_image_description_info_v4" version="1"> + <interface name="wp_image_description_info_v1" version="1"> <description summary="Colorimetric image description information"> Sends all matching events describing an image description object exactly once and finally sends the 'done' event. - Once a xx_image_description_info_v4 object has delivered a 'done' event it + This means + - if the image description is parametric, it must send + - primaries + - named_primaries, if applicable + - at least one of tf_power and tf_named, as applicable + - luminances + - target_primaries + - target_luminance + - if the image description is parametric, it may send, if applicable, + - target_max_cll + - target_max_fall + - if the image description contains an ICC profile, it must send the + icc_file event + + Once a wp_image_description_info_v1 object has delivered a 'done' event it is automatically destroyed. - Every xx_image_description_info_v4 created from the same - xx_image_description_v4 shall always return the exact same data. + Every wp_image_description_info_v1 created from the same + wp_image_description_v1 shall always return the exact same data. </description> <event name="done" type="destructor"> @@ -1316,18 +1495,18 @@ Delivers the primary color volume primaries and white point using CIE 1931 xy chromaticity coordinates. - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. </description> - <arg name="r_x" type="int" summary="Red x * 10000"/> - <arg name="r_y" type="int" summary="Red y * 10000"/> - <arg name="g_x" type="int" summary="Green x * 10000"/> - <arg name="g_y" type="int" summary="Green y * 10000"/> - <arg name="b_x" type="int" summary="Blue x * 10000"/> - <arg name="b_y" type="int" summary="Blue y * 10000"/> - <arg name="w_x" type="int" summary="White x * 10000"/> - <arg name="w_y" type="int" summary="White y * 10000"/> + <arg name="r_x" type="int" summary="Red x * 1M"/> + <arg name="r_y" type="int" summary="Red y * 1M"/> + <arg name="g_x" type="int" summary="Green x * 1M"/> + <arg name="g_y" type="int" summary="Green y * 1M"/> + <arg name="b_x" type="int" summary="Blue x * 1M"/> + <arg name="b_y" type="int" summary="Blue y * 1M"/> + <arg name="w_x" type="int" summary="White x * 1M"/> + <arg name="w_y" type="int" summary="White y * 1M"/> </event> <event name="primaries_named"> @@ -1336,7 +1515,7 @@ explicitly enumerated named set. </description> - <arg name="primaries" type="uint" enum="xx_color_manager_v4.primaries" + <arg name="primaries" type="uint" enum="wp_color_manager_v1.primaries" summary="named primaries"/> </event> @@ -1360,14 +1539,16 @@ named function. </description> - <arg name="tf" type="uint" enum="xx_color_manager_v4.transfer_function" + <arg name="tf" type="uint" enum="wp_color_manager_v1.transfer_function" summary="named transfer function"/> </event> <event name="luminances"> <description summary="primary color volume luminance range and reference white"> Delivers the primary color volume luminance range and the reference - white luminance level. + white luminance level. These values include the minimum display emission + and ambient flare luminances, assumed to be optically additive and have + the chromaticity of the primary color volume white point. The minimum luminance is multiplied by 10000 to get the argument 'min_lum' value and carries precision of 4 decimals. The maximum @@ -1393,25 +1574,28 @@ volume is equal to the primary color volume, then this event is not sent. - Each coordinate value is multiplied by 10000 to get the argument value - to carry precision of 4 decimals. + Each coordinate value is multiplied by 1 million to get the argument + value to carry precision of 6 decimals. </description> - <arg name="r_x" type="int" summary="Red x * 10000"/> - <arg name="r_y" type="int" summary="Red y * 10000"/> - <arg name="g_x" type="int" summary="Green x * 10000"/> - <arg name="g_y" type="int" summary="Green y * 10000"/> - <arg name="b_x" type="int" summary="Blue x * 10000"/> - <arg name="b_y" type="int" summary="Blue y * 10000"/> - <arg name="w_x" type="int" summary="White x * 10000"/> - <arg name="w_y" type="int" summary="White y * 10000"/> + <arg name="r_x" type="int" summary="Red x * 1M"/> + <arg name="r_y" type="int" summary="Red y * 1M"/> + <arg name="g_x" type="int" summary="Green x * 1M"/> + <arg name="g_y" type="int" summary="Green y * 1M"/> + <arg name="b_x" type="int" summary="Blue x * 1M"/> + <arg name="b_y" type="int" summary="Blue y * 1M"/> + <arg name="w_x" type="int" summary="White x * 1M"/> + <arg name="w_y" type="int" summary="White y * 1M"/> </event> <event name="target_luminance"> <description summary="target luminance range"> Provides the luminance range that the image description is targeting as - the minimum and maximum absolute luminance L. This is compatible with - the SMPTE ST 2086 definition of HDR static metadata. + the minimum and maximum absolute luminance L. These values include the + minimum display emission and ambient flare luminances, assumed to be + optically additive and have the chromaticity of the primary color + volume white point. This should be compatible with the SMPTE ST 2086 + definition of HDR static metadata. This luminance range is only theoretical and may not correspond to the luminance of light emitted on an actual display. diff --git a/src/3rdparty/wayland/protocols/color-management/qt_attribution.json b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json index 246e9df70fa..1001cc9b90d 100644 --- a/src/3rdparty/wayland/protocols/color-management/qt_attribution.json +++ b/src/3rdparty/wayland/protocols/color-management/qt_attribution.json @@ -4,15 +4,15 @@ "Name": "Wayland Color Management Protocol", "QDocModule": "qtwaylandcompositor", "QtUsage": "Used in the Qt Wayland platform plugin.", - "Files": "xx-color-management-v4.xml", + "Files": "color-management-v1.xml", "Description": "An extension to use different colorspaces from sRGB", "Homepage": "https://wayland.freedesktop.org", - "Version": "experimental v4", - "DownloadLocation": "https://gitlab.freedesktop.org/swick/wayland-protocols/-/blob/708a8b4119d4072820158a115166598733d378f4/staging/color-management/xx-color-management-v4.xml", + "Version": "1", + "DownloadLocation": "https://gitlab.freedesktop.org/wayland/wayland-protocols/-/raw/1.45/staging/color-management/color-management-v1.xml", "LicenseId": "MIT", "License": "MIT License", "LicenseFile": "../MIT_LICENSE.txt", - "Copyright": "Copyright 2019 Sebastian Wick\nCopyright 2019 Erwin Burema\nCopyright 2020 AMD\nCopyright 2020-2024 Collabora, Ltd.\nCopyright 2024 Xaver Hugl" + "Copyright": "Copyright 2019 Sebastian Wick\nCopyright 2019 Erwin Burema\nCopyright 2020 AMD\nCopyright 2020-2024 Collabora, Ltd.\nCopyright 2024 Xaver Hugl\nCopyright 2022-2025 Red Hat, Inc." } ] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index babf5bc31d2..2920c743243 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,7 +71,6 @@ add_subdirectory(tools) if(QT_FEATURE_gui) add_subdirectory(gui) - add_subdirectory(assets) if(QT_FEATURE_opengl) add_subdirectory(opengl) diff --git a/src/assets/CMakeLists.txt b/src/assets/CMakeLists.txt deleted file mode 100644 index ab07d27696e..00000000000 --- a/src/assets/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -if (NOT INTEGRITY AND TARGET Qt6::Network AND TARGET Qt6::Concurrent) - add_subdirectory(downloader) -endif() diff --git a/src/assets/downloader/CMakeLists.txt b/src/assets/downloader/CMakeLists.txt deleted file mode 100644 index 6b0564e72af..00000000000 --- a/src/assets/downloader/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -qt_internal_add_module(ExamplesAssetDownloaderPrivate - CONFIG_MODULE_NAME examples_asset_downloader - STATIC - INTERNAL_MODULE - SOURCES - assetdownloader.cpp assetdownloader.h - tasking/barrier.cpp tasking/barrier.h - tasking/concurrentcall.h - tasking/conditional.cpp tasking/conditional.h - tasking/networkquery.cpp tasking/networkquery.h - tasking/qprocesstask.cpp tasking/qprocesstask.h - tasking/tasking_global.h - tasking/tasktree.cpp tasking/tasktree.h - tasking/tasktreerunner.cpp tasking/tasktreerunner.h - tasking/tcpsocket.cpp tasking/tcpsocket.h - DEFINES - QT_NO_CAST_FROM_ASCII - LIBRARIES - Qt6::CorePrivate - PUBLIC_LIBRARIES - Qt6::Concurrent - Qt6::Core - Qt6::Network - NO_GENERATE_CPP_EXPORTS -) - -if (NOT QT_FEATURE_process) - set_source_files_properties(tasking/qprocesstask.h PROPERTIES SKIP_AUTOMOC TRUE) -endif() diff --git a/src/assets/downloader/assetdownloader.cpp b/src/assets/downloader/assetdownloader.cpp deleted file mode 100644 index 7c2f525a66d..00000000000 --- a/src/assets/downloader/assetdownloader.cpp +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "assetdownloader.h" - -#include "tasking/concurrentcall.h" -#include "tasking/networkquery.h" -#include "tasking/tasktreerunner.h" - -#include <QtCore/private/qzipreader_p.h> - -#include <QtCore/QDir> -#include <QtCore/QFile> -#include <QtCore/QJsonArray> -#include <QtCore/QJsonDocument> -#include <QtCore/QJsonObject> -#include <QtCore/QStandardPaths> -#include <QtCore/QTemporaryDir> -#include <QtCore/QTemporaryFile> - -using namespace Tasking; - -QT_BEGIN_NAMESPACE - -namespace Assets::Downloader { - -struct DownloadableAssets -{ - QUrl remoteUrl; - QList<QUrl> files; -}; - -class AssetDownloaderPrivate -{ -public: - AssetDownloaderPrivate(AssetDownloader *q) : m_q(q) {} - AssetDownloader *m_q = nullptr; - - std::unique_ptr<QNetworkAccessManager> m_manager; - std::unique_ptr<QTemporaryDir> m_temporaryDir; - TaskTreeRunner m_taskTreeRunner; - QString m_lastProgressText; - QDir m_localDownloadDir; - - QString m_jsonFileName; - QString m_zipFileName; - QDir m_preferredLocalDownloadDir = - QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - QUrl m_offlineAssetsFilePath; - QUrl m_downloadBase; - QStringList m_networkErrors; - QStringList m_sslErrors; - - void setLocalDownloadDir(const QDir &dir) - { - if (m_localDownloadDir != dir) { - m_localDownloadDir = dir; - emit m_q->localDownloadDirChanged(QUrl::fromLocalFile(m_localDownloadDir.absolutePath())); - } - } - void setProgress(int progressValue, int progressMaximum, const QString &progressText) - { - m_lastProgressText = progressText; - emit m_q->progressChanged(progressValue, progressMaximum, progressText); - } - void updateProgress(int progressValue, int progressMaximum) - { - setProgress(progressValue, progressMaximum, m_lastProgressText); - } - void clearProgress(const QString &progressText) - { - setProgress(0, 0, progressText); - } - - void setupDownload(NetworkQuery *query, const QString &progressText) - { - query->setNetworkAccessManager(m_manager.get()); - clearProgress(progressText); - QObject::connect(query, &NetworkQuery::started, query, [this, query] { - QNetworkReply *reply = query->reply(); - QObject::connect(reply, &QNetworkReply::downloadProgress, - query, [this](qint64 bytesReceived, qint64 totalBytes) { - updateProgress((totalBytes > 0) ? 100.0 * bytesReceived / totalBytes : 0, 100); - }); - QObject::connect(reply, &QNetworkReply::errorOccurred, query, [this, reply] { - m_networkErrors << reply->errorString(); - }); -#if QT_CONFIG(ssl) - QObject::connect(reply, &QNetworkReply::sslErrors, - query, [this](const QList<QSslError> &sslErrors) { - for (const QSslError &sslError : sslErrors) - m_sslErrors << sslError.errorString(); - }); -#endif - }); - } -}; - -static bool isWritableDir(const QDir &dir) -{ - if (dir.exists()) { - QTemporaryFile file(dir.filePath(QString::fromLatin1("tmp"))); - return file.open(); - } - return false; -} - -static bool sameFileContent(const QFileInfo &first, const QFileInfo &second) -{ - if (first.exists() ^ second.exists()) - return false; - - if (first.size() != second.size()) - return false; - - QFile firstFile(first.absoluteFilePath()); - QFile secondFile(second.absoluteFilePath()); - - if (firstFile.open(QFile::ReadOnly) && secondFile.open(QFile::ReadOnly)) { - char char1; - char char2; - int readBytes1 = 0; - int readBytes2 = 0; - while (!firstFile.atEnd()) { - readBytes1 = firstFile.read(&char1, 1); - readBytes2 = secondFile.read(&char2, 1); - if (readBytes1 != readBytes2 || readBytes1 != 1) - return false; - if (char1 != char2) - return false; - } - return true; - } - - return false; -} - -static bool createDirectory(const QDir &dir) -{ - if (dir.exists()) - return true; - - if (!createDirectory(dir.absoluteFilePath(QString::fromUtf8("..")))) - return false; - - return dir.mkpath(QString::fromUtf8(".")); -} - -static bool canBeALocalBaseDir(const QDir &dir) -{ - if (dir.exists()) - return !dir.isEmpty() || isWritableDir(dir); - return createDirectory(dir) && isWritableDir(dir); -} - -static QDir baseLocalDir(const QDir &preferredLocalDir) -{ - if (canBeALocalBaseDir(preferredLocalDir)) - return preferredLocalDir; - - qWarning().noquote() << "AssetDownloader: Cannot set \"" << preferredLocalDir - << "\" as a local download directory!"; - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); -} - -static QString pathFromUrl(const QUrl &url) -{ - if (url.isLocalFile()) - return url.toLocalFile(); - - if (url.scheme() == u"qrc") - return u":" + url.path(); - - return url.toString(); -} - -static QList<QUrl> filterDownloadableAssets(const QList<QUrl> &assetFiles, const QDir &expectedDir) -{ - QList<QUrl> downloadList; - std::copy_if(assetFiles.begin(), assetFiles.end(), std::back_inserter(downloadList), - [&](const QUrl &assetPath) { - return !QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString())); - }); - return downloadList; -} - -static bool allAssetsPresent(const QList<QUrl> &assetFiles, const QDir &expectedDir) -{ - return std::all_of(assetFiles.begin(), assetFiles.end(), [&](const QUrl &assetPath) { - return QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString())); - }); -} - -AssetDownloader::AssetDownloader(QObject *parent) - : QObject(parent) - , d(new AssetDownloaderPrivate(this)) -{} - -AssetDownloader::~AssetDownloader() = default; - -QUrl AssetDownloader::downloadBase() const -{ - return d->m_downloadBase; -} - -void AssetDownloader::setDownloadBase(const QUrl &downloadBase) -{ - if (d->m_downloadBase != downloadBase) { - d->m_downloadBase = downloadBase; - emit downloadBaseChanged(d->m_downloadBase); - } -} - -QUrl AssetDownloader::preferredLocalDownloadDir() const -{ - return QUrl::fromLocalFile(d->m_preferredLocalDownloadDir.absolutePath()); -} - -void AssetDownloader::setPreferredLocalDownloadDir(const QUrl &localDir) -{ - if (localDir.scheme() == u"qrc") { - qWarning() << "Cannot set a qrc as preferredLocalDownloadDir"; - return; - } - - const QString path = pathFromUrl(localDir); - if (d->m_preferredLocalDownloadDir != path) { - d->m_preferredLocalDownloadDir.setPath(path); - emit preferredLocalDownloadDirChanged(preferredLocalDownloadDir()); - } -} - -QUrl AssetDownloader::offlineAssetsFilePath() const -{ - return d->m_offlineAssetsFilePath; -} - -void AssetDownloader::setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath) -{ - if (d->m_offlineAssetsFilePath != offlineAssetsFilePath) { - d->m_offlineAssetsFilePath = offlineAssetsFilePath; - emit offlineAssetsFilePathChanged(d->m_offlineAssetsFilePath); - } -} - -QString AssetDownloader::jsonFileName() const -{ - return d->m_jsonFileName; -} - -void AssetDownloader::setJsonFileName(const QString &jsonFileName) -{ - if (d->m_jsonFileName != jsonFileName) { - d->m_jsonFileName = jsonFileName; - emit jsonFileNameChanged(d->m_jsonFileName); - } -} - -QString AssetDownloader::zipFileName() const -{ - return d->m_zipFileName; -} - -void AssetDownloader::setZipFileName(const QString &zipFileName) -{ - if (d->m_zipFileName != zipFileName) { - d->m_zipFileName = zipFileName; - emit zipFileNameChanged(d->m_zipFileName); - } -} - -QUrl AssetDownloader::localDownloadDir() const -{ - return QUrl::fromLocalFile(d->m_localDownloadDir.absolutePath()); -} - -QStringList AssetDownloader::networkErrors() const -{ - return d->m_networkErrors; -} - -QStringList AssetDownloader::sslErrors() const -{ - return d->m_sslErrors; -} - -static void precheckLocalFile(const QUrl &url) -{ - if (url.isEmpty()) - return; - QFile file(pathFromUrl(url)); - if (!file.open(QIODevice::ReadOnly)) - qWarning() << "Cannot open local file" << url; -} - -static void readAssetsFileContent(QPromise<DownloadableAssets> &promise, const QByteArray &content) -{ - const QJsonObject json = QJsonDocument::fromJson(content).object(); - const QJsonArray assetsArray = json[u"assets"].toArray(); - DownloadableAssets result; - result.remoteUrl = json[u"url"].toString(); - for (const QJsonValue &asset : assetsArray) { - if (promise.isCanceled()) - return; - result.files.append(asset.toString()); - } - - if (result.files.isEmpty() || result.remoteUrl.isEmpty()) - promise.future().cancel(); - else - promise.addResult(result); -} - -static void unzip(QPromise<void> &promise, const QByteArray &content, const QDir &directory, - const QString &fileName) -{ - const QString zipFilePath = directory.absoluteFilePath(fileName); - QFile zipFile(zipFilePath); - if (!zipFile.open(QIODevice::WriteOnly)) { - promise.future().cancel(); - return; - } - zipFile.write(content); - zipFile.close(); - - if (promise.isCanceled()) - return; - - QZipReader reader(zipFilePath); - const bool extracted = reader.extractAll(directory.absolutePath()); - reader.close(); - if (extracted) - QFile::remove(zipFilePath); - else - promise.future().cancel(); -} - -static void writeAsset(QPromise<void> &promise, const QByteArray &content, const QString &filePath) -{ - const QFileInfo fileInfo(filePath); - QFile file(fileInfo.absoluteFilePath()); - if (!createDirectory(fileInfo.dir()) || !file.open(QFile::WriteOnly)) { - promise.future().cancel(); - return; - } - - if (promise.isCanceled()) - return; - - file.write(content); - file.close(); -} - -static void copyAndCheck(QPromise<void> &promise, const QString &sourcePath, const QString &destPath) -{ - QFile sourceFile(sourcePath); - QFile destFile(destPath); - const QFileInfo sourceFileInfo(sourceFile.fileName()); - const QFileInfo destFileInfo(destFile.fileName()); - - if (destFile.exists() && !destFile.remove()) { - qWarning().noquote() << QString::fromLatin1("Unable to remove file \"%1\".") - .arg(QFileInfo(destFile.fileName()).absoluteFilePath()); - promise.future().cancel(); - return; - } - - if (!createDirectory(destFileInfo.absolutePath())) { - qWarning().noquote() << QString::fromLatin1("Cannot create directory \"%1\".") - .arg(destFileInfo.absolutePath()); - promise.future().cancel(); - return; - } - - if (promise.isCanceled()) - return; - - if (!sourceFile.copy(destFile.fileName()) && !sameFileContent(sourceFileInfo, destFileInfo)) - promise.future().cancel(); -} - -void AssetDownloader::start() -{ - if (d->m_taskTreeRunner.isRunning()) - return; - - struct StorageData - { - QDir tempDir; - QByteArray jsonContent; - DownloadableAssets assets; - QList<QUrl> assetsToDownload; - QByteArray zipContent; - int doneCount = 0; - }; - - const Storage<StorageData> storage; - - const auto onSetup = [this, storage] { - if (!d->m_manager) - d->m_manager = std::make_unique<QNetworkAccessManager>(); - if (!d->m_temporaryDir) - d->m_temporaryDir = std::make_unique<QTemporaryDir>(); - if (!d->m_temporaryDir->isValid()) { - qWarning() << "Cannot create a temporary directory."; - return SetupResult::StopWithError; - } - storage->tempDir = d->m_temporaryDir->path(); - d->setLocalDownloadDir(baseLocalDir(d->m_preferredLocalDownloadDir)); - d->m_networkErrors.clear(); - d->m_sslErrors.clear(); - precheckLocalFile(resolvedUrl(d->m_offlineAssetsFilePath)); - return SetupResult::Continue; - }; - - const auto onJsonDownloadSetup = [this](NetworkQuery &query) { - query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_jsonFileName))); - d->setupDownload(&query, tr("Downloading JSON file...")); - }; - const auto onJsonDownloadDone = [this, storage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) { - storage->jsonContent = query.reply()->readAll(); - return DoneResult::Success; - } - qWarning() << "Cannot download" << d->m_downloadBase.resolved(d->m_jsonFileName) - << query.reply()->errorString(); - if (d->m_offlineAssetsFilePath.isEmpty()) { - qWarning() << "Also there is no local file as a replacement"; - return DoneResult::Error; - } - - QFile file(pathFromUrl(resolvedUrl(d->m_offlineAssetsFilePath))); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "Also failed to open" << d->m_offlineAssetsFilePath; - return DoneResult::Error; - } - - storage->jsonContent = file.readAll(); - return DoneResult::Success; - }; - - const auto onReadAssetsFileSetup = [storage](ConcurrentCall<DownloadableAssets> &async) { - async.setConcurrentCallData(readAssetsFileContent, storage->jsonContent); - }; - const auto onReadAssetsFileDone = [storage](const ConcurrentCall<DownloadableAssets> &async) { - storage->assets = async.result(); - storage->assetsToDownload = storage->assets.files; - }; - - const auto onSkipIfAllAssetsPresent = [this, storage] { - return allAssetsPresent(storage->assets.files, d->m_localDownloadDir) - ? SetupResult::StopWithSuccess : SetupResult::Continue; - }; - - const auto onZipDownloadSetup = [this, storage](NetworkQuery &query) { - if (d->m_zipFileName.isEmpty()) - return SetupResult::StopWithSuccess; - - query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_zipFileName))); - d->setupDownload(&query, tr("Downloading zip file...")); - return SetupResult::Continue; - }; - const auto onZipDownloadDone = [storage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) - storage->zipContent = query.reply()->readAll(); - return DoneResult::Success; // Ignore zip download failure - }; - - const auto onUnzipSetup = [this, storage](ConcurrentCall<void> &async) { - if (storage->zipContent.isEmpty()) - return SetupResult::StopWithSuccess; - - async.setConcurrentCallData(unzip, storage->zipContent, storage->tempDir, d->m_zipFileName); - d->clearProgress(tr("Unzipping...")); - return SetupResult::Continue; - }; - const auto onUnzipDone = [storage](DoneWith result) { - if (result == DoneWith::Success) { - // Avoid downloading assets that are present in unzipped tree - StorageData &storageData = *storage; - storageData.assetsToDownload = - filterDownloadableAssets(storageData.assets.files, storageData.tempDir); - } else { - qWarning() << "ZipFile failed"; - } - return DoneResult::Success; // Ignore unzip failure - }; - - const LoopUntil downloadIterator([storage](int iteration) { - return iteration < storage->assetsToDownload.count(); - }); - - const Storage<QByteArray> assetStorage; - - const auto onAssetsDownloadGroupSetup = [this, storage] { - d->setProgress(0, storage->assetsToDownload.size(), tr("Downloading assets...")); - }; - - const auto onAssetDownloadSetup = [this, storage, downloadIterator](NetworkQuery &query) { - query.setNetworkAccessManager(d->m_manager.get()); - query.setRequest(QNetworkRequest(storage->assets.remoteUrl.resolved( - storage->assetsToDownload.at(downloadIterator.iteration())))); - }; - const auto onAssetDownloadDone = [assetStorage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) - *assetStorage = query.reply()->readAll(); - }; - - const auto onAssetWriteSetup = [storage, downloadIterator, assetStorage]( - ConcurrentCall<void> &async) { - const QString filePath = storage->tempDir.absoluteFilePath( - storage->assetsToDownload.at(downloadIterator.iteration()).toString()); - async.setConcurrentCallData(writeAsset, *assetStorage, filePath); - }; - const auto onAssetWriteDone = [this, storage](DoneWith result) { - if (result != DoneWith::Success) { - qWarning() << "Asset write failed"; - return; - } - StorageData &storageData = *storage; - ++storageData.doneCount; - d->updateProgress(storageData.doneCount, storageData.assetsToDownload.size()); - }; - - const LoopUntil copyIterator([storage](int iteration) { - return iteration < storage->assets.files.count(); - }); - - const auto onAssetsCopyGroupSetup = [this, storage] { - storage->doneCount = 0; - d->setProgress(0, storage->assets.files.size(), tr("Copying assets...")); - }; - - const auto onAssetCopySetup = [this, storage, copyIterator](ConcurrentCall<void> &async) { - const QString fileName = storage->assets.files.at(copyIterator.iteration()).toString(); - const QString sourcePath = storage->tempDir.absoluteFilePath(fileName); - const QString destPath = d->m_localDownloadDir.absoluteFilePath(fileName); - async.setConcurrentCallData(copyAndCheck, sourcePath, destPath); - }; - const auto onAssetCopyDone = [this, storage] { - StorageData &storageData = *storage; - ++storageData.doneCount; - d->updateProgress(storageData.doneCount, storageData.assets.files.size()); - }; - - const auto onAssetsCopyGroupDone = [this, storage](DoneWith result) { - if (result != DoneWith::Success) { - d->setLocalDownloadDir(storage->tempDir); - qWarning() << "Asset copy failed"; - return; - } - d->m_temporaryDir.reset(); - }; - - const Group recipe { - storage, - onGroupSetup(onSetup), - NetworkQueryTask(onJsonDownloadSetup, onJsonDownloadDone), - ConcurrentCallTask<DownloadableAssets>(onReadAssetsFileSetup, onReadAssetsFileDone, CallDoneIf::Success), - Group { - onGroupSetup(onSkipIfAllAssetsPresent), - NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone), - ConcurrentCallTask<void>(onUnzipSetup, onUnzipDone), - For (downloadIterator) >> Do { - parallelIdealThreadCountLimit, - onGroupSetup(onAssetsDownloadGroupSetup), - Group { - assetStorage, - NetworkQueryTask(onAssetDownloadSetup, onAssetDownloadDone), - ConcurrentCallTask<void>(onAssetWriteSetup, onAssetWriteDone) - } - }, - For (copyIterator) >> Do { - parallelIdealThreadCountLimit, - onGroupSetup(onAssetsCopyGroupSetup), - ConcurrentCallTask<void>(onAssetCopySetup, onAssetCopyDone, CallDoneIf::Success), - onGroupDone(onAssetsCopyGroupDone) - } - } - }; - d->m_taskTreeRunner.start(recipe, [this](TaskTree *) { emit started(); }, - [this](DoneWith result) { emit finished(result == DoneWith::Success); }); -} - -QUrl AssetDownloader::resolvedUrl(const QUrl &url) const -{ - return url; -} - -} // namespace Assets::Downloader - -QT_END_NAMESPACE diff --git a/src/assets/downloader/assetdownloader.h b/src/assets/downloader/assetdownloader.h deleted file mode 100644 index 3c9351ceafe..00000000000 --- a/src/assets/downloader/assetdownloader.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef ASSETDOWNLOADER_H -#define ASSETDOWNLOADER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtCore/QObject> -#include <QtCore/QUrl> - -#include <memory> - -QT_BEGIN_NAMESPACE - -namespace Assets::Downloader { - -class AssetDownloaderPrivate; - -class AssetDownloader : public QObject -{ - Q_OBJECT - - Q_PROPERTY( - QUrl downloadBase - READ downloadBase - WRITE setDownloadBase - NOTIFY downloadBaseChanged) - - Q_PROPERTY( - QUrl preferredLocalDownloadDir - READ preferredLocalDownloadDir - WRITE setPreferredLocalDownloadDir - NOTIFY preferredLocalDownloadDirChanged) - - Q_PROPERTY( - QUrl offlineAssetsFilePath - READ offlineAssetsFilePath - WRITE setOfflineAssetsFilePath - NOTIFY offlineAssetsFilePathChanged) - - Q_PROPERTY( - QString jsonFileName - READ jsonFileName - WRITE setJsonFileName - NOTIFY jsonFileNameChanged) - - Q_PROPERTY( - QString zipFileName - READ zipFileName - WRITE setZipFileName - NOTIFY zipFileNameChanged) - - Q_PROPERTY( - QUrl localDownloadDir - READ localDownloadDir - NOTIFY localDownloadDirChanged) - -public: - AssetDownloader(QObject *parent = nullptr); - ~AssetDownloader(); - - QUrl downloadBase() const; - void setDownloadBase(const QUrl &downloadBase); - - QUrl preferredLocalDownloadDir() const; - void setPreferredLocalDownloadDir(const QUrl &localDir); - - QUrl offlineAssetsFilePath() const; - void setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath); - - QString jsonFileName() const; - void setJsonFileName(const QString &jsonFileName); - - QString zipFileName() const; - void setZipFileName(const QString &zipFileName); - - QUrl localDownloadDir() const; - - Q_INVOKABLE QStringList networkErrors() const; - Q_INVOKABLE QStringList sslErrors() const; - -public Q_SLOTS: - void start(); - -protected: - virtual QUrl resolvedUrl(const QUrl &url) const; - -Q_SIGNALS: - void started(); - void finished(bool success); - void progressChanged(int progressValue, int progressMaximum, const QString &progressText); - void localDownloadDirChanged(const QUrl &url); - - void downloadBaseChanged(const QUrl &); - void preferredLocalDownloadDirChanged(const QUrl &url); - void offlineAssetsFilePathChanged(const QUrl &); - void jsonFileNameChanged(const QString &); - void zipFileNameChanged(const QString &); - -private: - std::unique_ptr<AssetDownloaderPrivate> d; -}; - -} // namespace Assets::Downloader - -QT_END_NAMESPACE - -#endif // ASSETDOWNLOADER_H diff --git a/src/assets/downloader/tasking/barrier.cpp b/src/assets/downloader/tasking/barrier.cpp deleted file mode 100644 index c9e5992bc78..00000000000 --- a/src/assets/downloader/tasking/barrier.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "barrier.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__)) -#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0) - -void Barrier::setLimit(int value) -{ - QT_ASSERT(!isRunning(), return); - QT_ASSERT(value > 0, return); - - m_limit = value; -} - -void Barrier::start() -{ - QT_ASSERT(!isRunning(), return); - m_current = 0; - m_result.reset(); -} - -void Barrier::advance() -{ - // Calling advance on finished is OK - QT_ASSERT(isRunning() || m_result, return); - if (!isRunning()) // no-op - return; - ++m_current; - if (m_current == m_limit) - stopWithResult(DoneResult::Success); -} - -void Barrier::stopWithResult(DoneResult result) -{ - // Calling stopWithResult on finished is OK when the same success is passed - QT_ASSERT(isRunning() || (m_result && *m_result == result), return); - if (!isRunning()) // no-op - return; - m_current = -1; - m_result = result; - emit done(result); -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/barrier.h b/src/assets/downloader/tasking/barrier.h deleted file mode 100644 index d489d2722a4..00000000000 --- a/src/assets/downloader/tasking/barrier.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_BARRIER_H -#define TASKING_BARRIER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class TASKING_EXPORT Barrier final : public QObject -{ - Q_OBJECT - -public: - void setLimit(int value); - int limit() const { return m_limit; } - - void start(); - void advance(); // If limit reached, stops with true - void stopWithResult(DoneResult result); // Ignores limit - - bool isRunning() const { return m_current >= 0; } - int current() const { return m_current; } - std::optional<DoneResult> result() const { return m_result; } - -Q_SIGNALS: - void done(DoneResult success); - -private: - std::optional<DoneResult> m_result = {}; - int m_limit = 1; - int m_current = -1; -}; - -using BarrierTask = SimpleCustomTask<Barrier>; - -template <int Limit = 1> -class SharedBarrier -{ -public: - static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); - SharedBarrier() : m_barrier(new Barrier) { - m_barrier->setLimit(Limit); - m_barrier->start(); - } - Barrier *barrier() const { return m_barrier.get(); } - -private: - std::shared_ptr<Barrier> m_barrier; -}; - -template <int Limit = 1> -using MultiBarrier = Storage<SharedBarrier<Limit>>; - -// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. -// Can't have one alias with default type in C++17, getting the following error: -// alias template deduction only available with C++20. -using SingleBarrier = MultiBarrier<1>; - -template <int Limit> -ExecutableItem waitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier) -{ - return BarrierTask([sharedBarrier](Barrier &barrier) { - SharedBarrier<Limit> *activeBarrier = sharedBarrier.activeStorage(); - if (!activeBarrier) { - qWarning("The barrier referenced from WaitForBarrier element " - "is not reachable in the running tree. " - "It is possible that no barrier was added to the tree, " - "or the barrier is not reachable from where it is referenced. " - "The WaitForBarrier task finishes with an error. "); - return SetupResult::StopWithError; - } - Barrier *activeSharedBarrier = activeBarrier->barrier(); - const std::optional<DoneResult> result = activeSharedBarrier->result(); - if (result.has_value()) { - return *result == DoneResult::Success ? SetupResult::StopWithSuccess - : SetupResult::StopWithError; - } - QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); - return SetupResult::Continue; - }); -} - -template <typename Signal> -ExecutableItem signalAwaiter(const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal) -{ - return BarrierTask([sender, signal](Barrier &barrier) { - QObject::connect(sender, signal, &barrier, &Barrier::advance, Qt::SingleShotConnection); - }); -} - -using BarrierKickerGetter = std::function<ExecutableItem(const SingleBarrier &)>; - -class TASKING_EXPORT When final -{ -public: - explicit When(const BarrierKickerGetter &kicker) : m_barrierKicker(kicker) {} - -private: - TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem); - - BarrierKickerGetter m_barrierKicker; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_BARRIER_H diff --git a/src/assets/downloader/tasking/concurrentcall.h b/src/assets/downloader/tasking/concurrentcall.h deleted file mode 100644 index cc701297d13..00000000000 --- a/src/assets/downloader/tasking/concurrentcall.h +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_CONCURRENTCALL_H -#define TASKING_CONCURRENTCALL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasktree.h" - -#include <QtConcurrent/QtConcurrent> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// This class introduces the dependency to Qt::Concurrent, otherwise Tasking namespace -// is independent on Qt::Concurrent. -// Possibly, it could be placed inside Qt::Concurrent library, as a wrapper around -// QtConcurrent::run() call. - -template <typename ResultType> -class ConcurrentCall -{ - Q_DISABLE_COPY_MOVE(ConcurrentCall) - -public: - ConcurrentCall() = default; - template <typename Function, typename ...Args> - void setConcurrentCallData(Function &&function, Args &&...args) - { - wrapConcurrent(std::forward<Function>(function), std::forward<Args>(args)...); - } - void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } - ResultType result() const - { - return m_future.resultCount() ? m_future.result() : ResultType(); - } - QList<ResultType> results() const - { - return m_future.results(); - } - QFuture<ResultType> future() const { return m_future; } - -private: - template <typename Function, typename ...Args> - void wrapConcurrent(Function &&function, Args &&...args) - { - m_startHandler = [this, function = std::forward<Function>(function), args...] { - QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); - return QtConcurrent::run(threadPool, function, args...); - }; - } - - template <typename Function, typename ...Args> - void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args) - { - m_startHandler = [this, wrapper = std::forward<std::reference_wrapper<const Function>>(wrapper), args...] { - QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); - return QtConcurrent::run(threadPool, std::forward<const Function>(wrapper.get()), - args...); - }; - } - - template <typename T> - friend class ConcurrentCallTaskAdapter; - - std::function<QFuture<ResultType>()> m_startHandler; - QThreadPool *m_threadPool = nullptr; - QFuture<ResultType> m_future; -}; - -template <typename ResultType> -class ConcurrentCallTaskAdapter : public TaskAdapter<ConcurrentCall<ResultType>> -{ -public: - ~ConcurrentCallTaskAdapter() { - if (m_watcher) { - m_watcher->cancel(); - m_watcher->waitForFinished(); - } - } - - void start() final { - if (!this->task()->m_startHandler) { - emit this->done(DoneResult::Error); // TODO: Add runtime assert - return; - } - m_watcher.reset(new QFutureWatcher<ResultType>); - this->connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] { - emit this->done(toDoneResult(!m_watcher->isCanceled())); - m_watcher.release()->deleteLater(); - }); - this->task()->m_future = this->task()->m_startHandler(); - m_watcher->setFuture(this->task()->m_future); - } - -private: - std::unique_ptr<QFutureWatcher<ResultType>> m_watcher; -}; - -template <typename T> -using ConcurrentCallTask = CustomTask<ConcurrentCallTaskAdapter<T>>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_CONCURRENTCALL_H diff --git a/src/assets/downloader/tasking/conditional.cpp b/src/assets/downloader/tasking/conditional.cpp deleted file mode 100644 index 24a03fb703e..00000000000 --- a/src/assets/downloader/tasking/conditional.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "conditional.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -static Group conditionRecipe(const Storage<bool> &bodyExecutedStorage, const ConditionData &condition) -{ - const auto onSetup = [bodyExecutedStorage] { - return *bodyExecutedStorage ? SetupResult::StopWithSuccess : SetupResult::Continue; - }; - - const auto onBodyDone = [bodyExecutedStorage] { *bodyExecutedStorage = true; }; - - const Group bodyTask { condition.m_body, onGroupDone(onBodyDone) }; - - return { - onGroupSetup(onSetup), - condition.m_condition ? Group{ !*condition.m_condition || bodyTask } : bodyTask - }; -} - -static ExecutableItem conditionsRecipe(const QList<ConditionData> &conditions) -{ - Storage<bool> bodyExecutedStorage; - - GroupItems recipes; - for (const ConditionData &condition : conditions) - recipes << conditionRecipe(bodyExecutedStorage, condition); - - return Group { bodyExecutedStorage, recipes }; -} - -ThenItem::operator ExecutableItem() const -{ - return conditionsRecipe(m_conditions); -} - -ThenItem::ThenItem(const If &ifItem, const Then &thenItem) - : m_conditions{{ifItem.m_condition, thenItem.m_body}} -{} - -ThenItem::ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem) - : m_conditions(elseIfItem.m_conditions) -{ - m_conditions.append({elseIfItem.m_nextCondition, thenItem.m_body}); -} - -ElseItem::operator ExecutableItem() const -{ - return conditionsRecipe(m_conditions); -} - -ElseItem::ElseItem(const ThenItem &thenItem, const Else &elseItem) - : m_conditions(thenItem.m_conditions) -{ - m_conditions.append({{}, elseItem.m_body}); -} - -ElseIfItem::ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem) - : m_conditions(thenItem.m_conditions) - , m_nextCondition(elseIfItem.m_condition) -{} - -ThenItem operator>>(const If &ifItem, const Then &thenItem) -{ - return {ifItem, thenItem}; -} - -ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem) -{ - return {elseIfItem, thenItem}; -} - -ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem) -{ - return {thenItem, elseIfItem}; -} - -ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem) -{ - return {thenItem, elseItem}; -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/conditional.h b/src/assets/downloader/tasking/conditional.h deleted file mode 100644 index 52fdfd64cb7..00000000000 --- a/src/assets/downloader/tasking/conditional.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_CONDITIONAL_H -#define TASKING_CONDITIONAL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class Then; -class ThenItem; -class ElseItem; -class ElseIfItem; - -class TASKING_EXPORT If -{ -public: - explicit If(const ExecutableItem &condition) : m_condition(condition) {} - - template <typename Handler, - std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true> - explicit If(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {} - -private: - TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem); - - friend class ThenItem; - ExecutableItem m_condition; -}; - -class TASKING_EXPORT ElseIf -{ -public: - explicit ElseIf(const ExecutableItem &condition) : m_condition(condition) {} - - template <typename Handler, - std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true> - explicit ElseIf(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {} - -private: - friend class ElseIfItem; - ExecutableItem m_condition; -}; - -class TASKING_EXPORT Else -{ -public: - explicit Else(const GroupItems &children) : m_body({children}) {} - explicit Else(std::initializer_list<GroupItem> children) : m_body({children}) {} - -private: - friend class ElseItem; - Group m_body; -}; - -class TASKING_EXPORT Then -{ -public: - explicit Then(const GroupItems &children) : m_body({children}) {} - explicit Then(std::initializer_list<GroupItem> children) : m_body({children}) {} - -private: - friend class ThenItem; - Group m_body; -}; - -class ConditionData -{ -public: - std::optional<ExecutableItem> m_condition; - Group m_body; -}; - -class ElseIfItem; - -class TASKING_EXPORT ThenItem -{ -public: - operator ExecutableItem() const; - -private: - ThenItem(const If &ifItem, const Then &thenItem); - ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem); - - TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem); - TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem); - TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem); - TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem); - - friend class ElseItem; - friend class ElseIfItem; - QList<ConditionData> m_conditions; -}; - -class TASKING_EXPORT ElseItem -{ -public: - operator ExecutableItem() const; - -private: - ElseItem(const ThenItem &thenItem, const Else &elseItem); - - TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem); - - QList<ConditionData> m_conditions; -}; - -class TASKING_EXPORT ElseIfItem -{ -private: - ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem); - - TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem); - TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem); - - friend class ThenItem; - QList<ConditionData> m_conditions; - ExecutableItem m_nextCondition; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_CONDITIONAL_H diff --git a/src/assets/downloader/tasking/networkquery.cpp b/src/assets/downloader/tasking/networkquery.cpp deleted file mode 100644 index 3f15fed90e0..00000000000 --- a/src/assets/downloader/tasking/networkquery.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "networkquery.h" - -#include <QtNetwork/QNetworkAccessManager> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -void NetworkQuery::start() -{ - if (m_reply) { - qWarning("The NetworkQuery is already running. Ignoring the call to start()."); - return; - } - if (!m_manager) { - qWarning("Can't start the NetworkQuery without the QNetworkAccessManager. " - "Stopping with an error."); - emit done(DoneResult::Error); - return; - } - switch (m_operation) { - case NetworkOperation::Get: - m_reply.reset(m_manager->get(m_request)); - break; - case NetworkOperation::Put: - m_reply.reset(m_manager->put(m_request, m_writeData)); - break; - case NetworkOperation::Post: - m_reply.reset(m_manager->post(m_request, m_writeData)); - break; - case NetworkOperation::Delete: - m_reply.reset(m_manager->deleteResource(m_request)); - break; - } - - connect(m_reply.get(), &QNetworkReply::downloadProgress, this, &NetworkQuery::downloadProgress); -#if QT_CONFIG(ssl) - connect(m_reply.get(), &QNetworkReply::sslErrors, this, &NetworkQuery::sslErrors); -#endif - connect(m_reply.get(), &QNetworkReply::finished, this, [this] { - disconnect(m_reply.get(), &QNetworkReply::finished, this, nullptr); - emit done(toDoneResult(m_reply->error() == QNetworkReply::NoError)); - m_reply.release()->deleteLater(); - }); - if (m_reply->isRunning()) - emit started(); -} - -NetworkQuery::~NetworkQuery() -{ - if (m_reply) { - disconnect(m_reply.get(), nullptr, this, nullptr); - m_reply->abort(); - } -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/networkquery.h b/src/assets/downloader/tasking/networkquery.h deleted file mode 100644 index 2733fef8352..00000000000 --- a/src/assets/downloader/tasking/networkquery.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_NETWORKQUERY_H -#define TASKING_NETWORKQUERY_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -#include <QtNetwork/QNetworkReply> -#include <QtNetwork/QNetworkRequest> - -#include <memory> - -QT_BEGIN_NAMESPACE -class QNetworkAccessManager; - -namespace Tasking { - -// This class introduces the dependency to Qt::Network, otherwise Tasking namespace -// is independent on Qt::Network. -// Possibly, it could be placed inside Qt::Network library, as a wrapper around QNetworkReply. - -enum class NetworkOperation { Get, Put, Post, Delete }; - -class TASKING_EXPORT NetworkQuery final : public QObject -{ - Q_OBJECT - -public: - ~NetworkQuery(); - void setRequest(const QNetworkRequest &request) { m_request = request; } - void setOperation(NetworkOperation operation) { m_operation = operation; } - void setWriteData(const QByteArray &data) { m_writeData = data; } - void setNetworkAccessManager(QNetworkAccessManager *manager) { m_manager = manager; } - QNetworkReply *reply() const { return m_reply.get(); } - void start(); - -Q_SIGNALS: - void started(); - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); -#if QT_CONFIG(ssl) - void sslErrors(const QList<QSslError> &errors); -#endif - void done(DoneResult result); - -private: - QNetworkRequest m_request; - NetworkOperation m_operation = NetworkOperation::Get; - QByteArray m_writeData; // Used by Put and Post - QNetworkAccessManager *m_manager = nullptr; - std::unique_ptr<QNetworkReply> m_reply; -}; - -using NetworkQueryTask = SimpleCustomTask<NetworkQuery>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_NETWORKQUERY_H diff --git a/src/assets/downloader/tasking/qprocesstask.cpp b/src/assets/downloader/tasking/qprocesstask.cpp deleted file mode 100644 index a4696ebbbfb..00000000000 --- a/src/assets/downloader/tasking/qprocesstask.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qprocesstask.h" - -#include <QtCore/QCoreApplication> -#include <QtCore/QDebug> -#include <QtCore/QElapsedTimer> -#include <QtCore/QMutex> -#include <QtCore/QThread> -#include <QtCore/QTimer> -#include <QtCore/QWaitCondition> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(process) - -namespace Tasking { - -class ProcessReaperPrivate; - -class ProcessReaper final -{ -public: - static void reap(QProcess *process, int timeoutMs = 500); - ProcessReaper(); - ~ProcessReaper(); - -private: - static ProcessReaper *instance(); - - QThread m_thread; - ProcessReaperPrivate *m_private; -}; - -static const int s_timeoutThreshold = 10000; // 10 seconds - -static QString execWithArguments(QProcess *process) -{ - QStringList commandLine; - commandLine.append(process->program()); - commandLine.append(process->arguments()); - return commandLine.join(QChar::Space); -} - -struct ReaperSetup -{ - QProcess *m_process = nullptr; - int m_timeoutMs; -}; - -class Reaper : public QObject -{ - Q_OBJECT - -public: - Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {} - - void reap() - { - m_timer.start(); - connect(m_reaperSetup.m_process, &QProcess::finished, this, &Reaper::handleFinished); - if (emitFinished()) - return; - terminate(); - } - -Q_SIGNALS: - void finished(); - -private: - void terminate() - { - m_reaperSetup.m_process->terminate(); - QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleTerminateTimeout); - } - - void kill() { m_reaperSetup.m_process->kill(); } - - bool emitFinished() - { - if (m_reaperSetup.m_process->state() != QProcess::NotRunning) - return false; - - if (!m_finished) { - const int timeout = m_timer.elapsed(); - if (timeout > s_timeoutThreshold) { - qWarning() << "Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process) - << "in" << (timeout / 1000.0) << "seconds."; - } - - m_finished = true; - emit finished(); - } - return true; - } - - void handleFinished() - { - if (emitFinished()) - return; - qWarning() << "Finished process still running..."; - // In case the process is still running - wait until it has finished - QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleFinished); - } - - void handleTerminateTimeout() - { - if (emitFinished()) - return; - kill(); - } - - bool m_finished = false; - QElapsedTimer m_timer; - const ReaperSetup m_reaperSetup; -}; - -class ProcessReaperPrivate : public QObject -{ - Q_OBJECT - -public: - // Called from non-reaper's thread - void scheduleReap(const ReaperSetup &reaperSetup) - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't schedule reap from the reaper internal thread."; - - QMutexLocker locker(&m_mutex); - m_reaperSetupList.append(reaperSetup); - QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush); - } - - // Called from non-reaper's thread - void waitForFinished() - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't wait for finished from the reaper internal thread."; - - QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush, - Qt::BlockingQueuedConnection); - QMutexLocker locker(&m_mutex); - if (m_reaperList.isEmpty()) - return; - - m_waitCondition.wait(&m_mutex); - } - -private: - // All the private methods are called from the reaper's thread - QList<ReaperSetup> takeReaperSetupList() - { - QMutexLocker locker(&m_mutex); - return std::exchange(m_reaperSetupList, {}); - } - - void flush() - { - while (true) { - const QList<ReaperSetup> reaperSetupList = takeReaperSetupList(); - if (reaperSetupList.isEmpty()) - return; - for (const ReaperSetup &reaperSetup : reaperSetupList) - reap(reaperSetup); - } - } - - void reap(const ReaperSetup &reaperSetup) - { - Reaper *reaper = new Reaper(reaperSetup); - connect(reaper, &Reaper::finished, this, [this, reaper, process = reaperSetup.m_process] { - QMutexLocker locker(&m_mutex); - const bool isRemoved = m_reaperList.removeOne(reaper); - if (!isRemoved) - qWarning() << "Reaper list doesn't contain the finished process."; - - delete reaper; - delete process; - if (m_reaperList.isEmpty()) - m_waitCondition.wakeOne(); - }, Qt::QueuedConnection); - - { - QMutexLocker locker(&m_mutex); - m_reaperList.append(reaper); - } - - reaper->reap(); - } - - QMutex m_mutex; - QWaitCondition m_waitCondition; - QList<ReaperSetup> m_reaperSetupList; - QList<Reaper *> m_reaperList; -}; - -static ProcessReaper *s_instance = nullptr; -static QMutex s_instanceMutex; - -// Call me with s_instanceMutex locked. -ProcessReaper *ProcessReaper::instance() -{ - if (!s_instance) - s_instance = new ProcessReaper; - return s_instance; -} - -ProcessReaper::ProcessReaper() - : m_private(new ProcessReaperPrivate) -{ - m_private->moveToThread(&m_thread); - QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater); - m_thread.start(); - m_thread.moveToThread(qApp->thread()); -} - -ProcessReaper::~ProcessReaper() -{ - if (!QThread::isMainThread()) - qWarning() << "Destructing process reaper from non-main thread."; - - instance()->m_private->waitForFinished(); - m_thread.quit(); - m_thread.wait(); -} - -void ProcessReaper::reap(QProcess *process, int timeoutMs) -{ - if (!process) - return; - - if (QThread::currentThread() != process->thread()) { - qWarning() << "Can't reap process from non-process's thread."; - return; - } - - process->disconnect(); - if (process->state() == QProcess::NotRunning) { - delete process; - return; - } - - // Neither can move object with a parent into a different thread - // nor reaping the process with a parent makes any sense. - process->setParent(nullptr); - - QMutexLocker locker(&s_instanceMutex); - ProcessReaperPrivate *priv = instance()->m_private; - - process->moveToThread(priv->thread()); - ReaperSetup reaperSetup {process, timeoutMs}; - priv->scheduleReap(reaperSetup); -} - -void QProcessDeleter::deleteAll() -{ - QMutexLocker locker(&s_instanceMutex); - delete s_instance; - s_instance = nullptr; -} - -void QProcessDeleter::operator()(QProcess *process) -{ - ProcessReaper::reap(process); -} - -} // namespace Tasking - -#endif // QT_CONFIG(process) - -QT_END_NAMESPACE - -#if QT_CONFIG(process) - -#include "qprocesstask.moc" - -#endif // QT_CONFIG(process) - diff --git a/src/assets/downloader/tasking/qprocesstask.h b/src/assets/downloader/tasking/qprocesstask.h deleted file mode 100644 index 6f2ca4a18e2..00000000000 --- a/src/assets/downloader/tasking/qprocesstask.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_QPROCESSTASK_H -#define TASKING_QPROCESSTASK_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -#include <QtCore/QProcess> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(process) - -namespace Tasking { - -// Deleting a running QProcess may block the caller thread up to 30 seconds and issue warnings. -// To avoid these issues we move the running QProcess into a separate thread -// managed by the internal ProcessReaper, instead of deleting it immediately. -// Inside the ProcessReaper's thread we try to finish the process in a most gentle way: -// we call QProcess::terminate() with 500 ms timeout, and if the process is still running -// after this timeout passed, we call QProcess::kill() and wait for the process to finish. -// All these handlings are done is a separate thread, so the main thread doesn't block at all -// when the QProcessTask is destructed. -// Finally, on application quit, QProcessDeleter::deleteAll() should be called in order -// to synchronize all the processes being still potentially reaped in a separate thread. -// The call to QProcessDeleter::deleteAll() is blocking in case some processes -// are still being reaped. -// This strategy seems most sensible, since when passing the running QProcess into the -// ProcessReaper we don't block immediately, but postpone the possible (not certain) block -// until the end of an application. -// In this way we terminate the running processes in the most safe way and keep the main thread -// responsive. That's a common case when the running application wants to terminate the QProcess -// immediately (e.g. on Cancel button pressed), without keeping and managing the handle -// to the still running QProcess. - -// The implementation of the internal reaper is inspired by the Utils::ProcessReaper taken -// from the QtCreator codebase. - -class TASKING_EXPORT QProcessDeleter -{ -public: - // Blocking, should be called after all QProcessAdapter instances are deleted. - static void deleteAll(); - void operator()(QProcess *process); -}; - -class TASKING_EXPORT QProcessAdapter : public TaskAdapter<QProcess, QProcessDeleter> -{ -private: - void start() final { - connect(task(), &QProcess::finished, this, [this] { - const bool success = task()->exitStatus() == QProcess::NormalExit - && task()->error() == QProcess::UnknownError - && task()->exitCode() == 0; - Q_EMIT done(toDoneResult(success)); - }); - connect(task(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - if (error != QProcess::FailedToStart) - return; - Q_EMIT done(DoneResult::Error); - }); - task()->start(); - } -}; - -using QProcessTask = CustomTask<QProcessAdapter>; - -} // namespace Tasking - -#endif // QT_CONFIG(process) - -QT_END_NAMESPACE - -#endif // TASKING_QPROCESSTASK_H diff --git a/src/assets/downloader/tasking/tasking_global.h b/src/assets/downloader/tasking/tasking_global.h deleted file mode 100644 index 57f0b7fefef..00000000000 --- a/src/assets/downloader/tasking/tasking_global.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_GLOBAL_H -#define TASKING_GLOBAL_H - -#include <QtCore/qglobal.h> - -QT_BEGIN_NAMESPACE - -// #if defined(QT_SHARED) || !defined(QT_STATIC) -// # if defined(TASKING_LIBRARY) -// # define TASKING_EXPORT Q_DECL_EXPORT -// # else -// # define TASKING_EXPORT Q_DECL_IMPORT -// # endif -// #else -// # define TASKING_EXPORT -// #endif - -#define TASKING_EXPORT - -QT_END_NAMESPACE - -#endif // TASKING_GLOBAL_H diff --git a/src/assets/downloader/tasking/tasktree.cpp b/src/assets/downloader/tasking/tasktree.cpp deleted file mode 100644 index 37064a3e714..00000000000 --- a/src/assets/downloader/tasking/tasktree.cpp +++ /dev/null @@ -1,3701 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tasktree.h" - -#include "barrier.h" -#include "conditional.h" - -#include <QtCore/QDebug> -#include <QtCore/QEventLoop> -#include <QtCore/QFutureWatcher> -#include <QtCore/QHash> -#include <QtCore/QMetaEnum> -#include <QtCore/QMutex> -#include <QtCore/QPointer> -#include <QtCore/QPromise> -#include <QtCore/QSet> -#include <QtCore/QTime> -#include <QtCore/QTimer> - -using namespace Qt::StringLiterals; -using namespace std::chrono; - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__)) -#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0) -#define QT_CHECK(cond) if (cond) {} else { QT_STRING(#cond); } do {} while (0) - -class Guard -{ - Q_DISABLE_COPY(Guard) -public: - Guard() = default; - ~Guard() { QT_CHECK(m_lockCount == 0); } - bool isLocked() const { return m_lockCount; } -private: - int m_lockCount = 0; - friend class GuardLocker; -}; - -class GuardLocker -{ - Q_DISABLE_COPY(GuardLocker) -public: - GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } - ~GuardLocker() { --m_guard.m_lockCount; } -private: - Guard &m_guard; -}; - -/*! - \module TaskingSolution - \title Tasking Solution - \ingroup solutions-modules - \brief Contains a general purpose Tasking solution. - - The Tasking solution depends on Qt only, and doesn't depend on any \QC specific code. -*/ - -/*! - \namespace Tasking - \inmodule TaskingSolution - \brief The Tasking namespace encloses all classes and global functions of the Tasking solution. -*/ - -/*! - \class Tasking::TaskInterface - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief TaskInterface is the abstract base class for implementing custom task adapters. - \reentrant - - To implement a custom task adapter, derive your adapter from the - \c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys - the custom task instance and associates the adapter with a given \c Task type. -*/ - -/*! - \fn virtual void TaskInterface::start() - - This method is called by the running TaskTree for starting the \c Task instance. - Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the - associated task. - - Use TaskAdapter::task() to access the associated \c Task instance. - - \sa done(), TaskAdapter::task() -*/ - -/*! - \fn void TaskInterface::done(DoneResult result) - - Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished. - Pass DoneResult::Success as a \a result argument when the task finishes with success; - otherwise, when an error occurs, pass DoneResult::Error. -*/ - -/*! - \class Tasking::TaskAdapter - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template for implementing custom task adapters. - \reentrant - - The TaskAdapter class template is responsible for creating a task of the \c Task type, - starting it, and reporting success or an error when the task is finished. - It also associates the adapter with a given \c Task type. - - Reimplement this class with the actual \c Task type to adapt the task's interface - into the general TaskTree's interface for managing the \c Task instances. - - Each subclass needs to provide a public default constructor, - implement the start() method, and emit the done() signal when the task is finished. - Use task() to access the associated \c Task instance. - - To use your task adapter inside the task tree, create an alias to the - Tasking::CustomTask template passing your task adapter as a template parameter: - \code - // Defines actual worker - class Worker {...}; - - // Adapts Worker's interface to work with task tree - class WorkerTaskAdapter : public TaskAdapter<Worker> {...}; - - // Defines WorkerTask as a new custom task type to be placed inside Group items - using WorkerTask = CustomTask<WorkerTaskAdapter>; - \endcode - - Optionally, you may pass a custom \c Deleter for the associated \c Task - as a second template parameter of your \c TaskAdapter subclass. - When the \c Deleter parameter is omitted, the \c std::default_delete<Task> is used by default. - The custom \c Deleter is useful when the destructor of the running \c Task - may potentially block the caller thread. Instead of blocking, the custom deleter may move - the running task into a separate thread and implement the blocking destruction there. - In this way, the fast destruction (seen from the caller thread) of the running task - with a blocking destructor may be achieved. - - For more information on implementing the custom task adapters, refer to \l {Task Adapters}. - - \sa start(), done(), task() -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> TaskAdapter<Task, Deleter>::TaskAdapter<Task, Deleter>() - - Creates a task adapter for the given \c Task type. - - Internally, it creates an instance of \c Task, which is accessible via the task() method. - The optionally provided \c Deleter is used instead of the \c Task destructor. - When \c Deleter is omitted, the \c std::default_delete<Task> is used by default. - - \sa task() -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() - - Returns the pointer to the associated \c Task instance. -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() const - \overload - - Returns the \c const pointer to the associated \c Task instance. -*/ - -/*! - \class Tasking::Storage - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template for custom data exchange in the running task tree. - \reentrant - - The Storage class template is responsible for dynamically creating and destructing objects - of the custom \c StorageStruct type. The creation and destruction are managed by the - running task tree. If a Storage object is placed inside a \l {Tasking::Group} {Group} element, - the running task tree creates the \c StorageStruct object when the group is started and before - the group's setup handler is called. Later, whenever any handler inside this group is called, - the task tree activates the previously created instance of the \c StorageStruct object. - This includes all tasks' and groups' setup and done handlers inside the group where the - Storage object was placed, also within the nested groups. - When a copy of the Storage object is passed to the handler via the lambda capture, - the handler may access the instance activated by the running task tree via the - \l {Tasking::Storage::operator->()} {operator->()}, - \l {Tasking::Storage::operator*()} {operator*()}, or activeStorage() method. - If two handlers capture the same Storage object, one of them may store a custom data there, - and the other may read it afterwards. - When the group is finished, the previously created instance of the \c StorageStruct - object is destroyed after the group's done handler is called. - - An example of data exchange between tasks: - - \code - const Storage<QString> storage; - - const auto onFirstDone = [storage](const Task &task) { - // Assings QString, taken from the first task result, to the active QString instance - // of the Storage object. - *storage = task.getResultAsString(); - }; - - const auto onSecondSetup = [storage](Task &task) { - // Reads QString from the active QString instance of the Storage object and use it to - // configure the second task before start. - task.configureWithString(*storage); - }; - - const Group root { - // The running task tree creates QString instance when root in entered - storage, - // The done handler of the first task stores the QString in the storage - TaskItem(..., onFirstDone), - // The setup handler of the second task reads the QString from the storage - TaskItem(onSecondSetup, ...) - }; - \endcode - - Since the root group executes its tasks sequentially, the \c onFirstDone handler - is always called before the \c onSecondSetup handler. This means that the QString data, - read from the \c storage inside the \c onSecondSetup handler's body, - has already been set by the \c onFirstDone handler. - You can always rely on it in \l {Tasking::sequential} {sequential} execution mode. - - The Storage internals are shared between all of its copies. That is why the copies of the - Storage object inside the handlers' lambda captures still refer to the same Storage instance. - You may place multiple Storage objects inside one \l {Tasking::Group} {Group} element, - provided that they do not include copies of the same Storage object. - Otherwise, an assert is triggered at runtime that includes an error message. - However, you can place copies of the same Storage object in different - \l {Tasking::Group} {Group} elements of the same recipe. In this case, the running task - tree will create multiple instances of the \c StorageStruct objects (one for each copy) - and storage shadowing will take place. Storage shadowing works in a similar way - to C++ variable shadowing inside the nested blocks of code: - - \code - Storage<QString> storage; - - const Group root { - storage, // Top copy, 1st instance of StorageStruct - onGroupSetup([storage] { ... }), // Top copy is active - Group { - storage, // Nested copy, 2nd instance of StorageStruct, - // shadows Top copy - onGroupSetup([storage] { ... }), // Nested copy is active - }, - Group { - onGroupSetup([storage] { ... }), // Top copy is active - } - }; - \endcode - - The Storage objects may also be used for passing the initial data to the executed task tree, - and for reading the final data out of the task tree before it finishes. - To do this, use \l {TaskTree::onStorageSetup()} {onStorageSetup()} or - \l {TaskTree::onStorageDone()} {onStorageDone()}, respectively. - - \note If you use an unreachable Storage object inside the handler, - because you forgot to place the storage in the recipe, - or placed it, but not in any handler's ancestor group, - you may expect a crash, preceded by the following message: - \e {The referenced storage is not reachable in the running tree. - A nullptr will be returned which might lead to a crash in the calling code. - It is possible that no storage was added to the tree, - or the storage is not reachable from where it is referenced.} -*/ - -/*! - \fn template <typename StorageStruct> Storage<StorageStruct>::Storage<StorageStruct>() - - Creates a storage for the given \c StorageStruct type. - - \note All copies of \c this object are considered to be the same Storage instance. -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct &Storage<StorageStruct>::operator*() const noexcept - - Returns a \e reference to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned reference is valid as long as the group that created this instance - is still running. - - \sa activeStorage(), operator->() -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::operator->() const noexcept - - Returns a \e pointer to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned pointer is valid as long as the group that created this instance - is still running. - - \sa activeStorage(), operator*() -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::activeStorage() const - - Returns a \e pointer to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned pointer is valid as long as the group that created this instance - is still running. - - \sa operator->(), operator*() -*/ - -/*! - \typealias Tasking::GroupItems - - Type alias for QList<GroupItem>. -*/ - -/*! - \class Tasking::GroupItem - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief GroupItem represents the basic element that may be a part of any Group. - \reentrant - - GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}. - It encapsulates the functionality provided by any GroupItem's subclass. - It is a value type and it is safe to copy the GroupItem instance, - even when it is originally created via the subclass' constructor. - - There are four main kinds of GroupItem: - \table - \header - \li GroupItem Kind - \li Brief Description - \row - \li \l CustomTask - \li Defines asynchronous task type and task's start, done, and error handlers. - Aliased with a unique task name, such as, \c ConcurrentCallTask<ResultType> - or \c NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree. - \row - \li \l {Tasking::Group} {Group} - \li A container for other group items. Since the group is of the GroupItem type, - it's possible to nest it inside another group. The group is seen by its parent - as a single asynchronous task. - \row - \li GroupItem containing \l {Tasking::Storage} {Storage} - \li Enables the child tasks of a group to exchange data. When GroupItem containing - \l {Tasking::Storage} {Storage} is placed inside a group, the task tree instantiates - the storage's data object just before the group is entered, - and destroys it just after the group is left. - \row - \li Other group control items - \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or - \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior. - The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()} or - \l {Tasking::onGroupDone()} {onGroupDone()} define custom handlers called when - the group starts or ends execution. - \endtable -*/ - -/*! - \fn template <typename StorageStruct> GroupItem::GroupItem(const Storage<StorageStruct> &storage) - - Constructs a \c GroupItem element holding the \a storage object. - - When the \l {Tasking::Group} {Group} element containing \e this GroupItem is entered - by the running task tree, an instance of the \c StorageStruct is created dynamically. - - When that group is about to be left after its execution, the previously instantiated - \c StorageStruct is deleted. - - The dynamically created instance of \c StorageStruct is accessible from inside any - handler body of the parent \l {Tasking::Group} {Group} element, - including nested groups and its tasks, via the - \l {Tasking::Storage::operator->()} {Storage::operator->()}, - \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method. - - \sa {Tasking::Storage} {Storage} -*/ - -/*! - \fn GroupItem::GroupItem(const GroupItems &items) - - Constructs a \c GroupItem element with a given list of \a items. - - When this \c GroupItem element is parsed by the TaskTree, it is simply replaced with - its \a items. - - This constructor is useful when constructing a \l {Tasking::Group} {Group} element with - lists of \c GroupItem elements: - - \code - static QList<GroupItems> getItems(); - - ... - - const Group root { - parallel, - finishAllAndSuccess, - getItems(), // OK, getItems() list is wrapped into a single GroupItem element - onGroupSetup(...), - onGroupDone(...) - }; - \endcode - - If you want to create a subtree, use \l {Tasking::Group} {Group} instead. - - \note Don't confuse this \c GroupItem with the \l {Tasking::Group} {Group} element, as - \l {Tasking::Group} {Group} keeps its children nested - after being parsed by the task tree, while this \c GroupItem does not. - - \sa {Tasking::Group} {Group} -*/ - -/*! - \fn Tasking::GroupItem(std::initializer_list<GroupItem> items) - \overload - \sa GroupItem(const GroupItems &items) -*/ - -/*! - \class Tasking::Group - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Group represents the basic element for composing declarative recipes describing - how to execute and handle a nested tree of asynchronous tasks. - \reentrant - - Group is a container for other group items. It encloses child tasks into one unit, - which is seen by the group's parent as a single, asynchronous task. - Since Group is of the GroupItem type, it may also be a child of Group. - - Insert child tasks into the group by using aliased custom task names, such as, - \c ConcurrentCallTask<ResultType> or \c NetworkQueryTask: - - \code - const Group group { - NetworkQueryTask(...), - ConcurrentCallTask<int>(...) - }; - \endcode - - The group's behavior may be customized by inserting the items returned by - \l {Tasking::parallelLimit()} {parallelLimit()} or - \l {Tasking::workflowPolicy()} {workflowPolicy()} functions: - - \code - const Group group { - parallel, - continueOnError, - NetworkQueryTask(...), - NetworkQueryTask(...) - }; - \endcode - - The group may contain nested groups: - - \code - const Group group { - finishAllAndSuccess, - NetworkQueryTask(...), - Group { - NetworkQueryTask(...), - Group { - parallel, - NetworkQueryTask(...), - NetworkQueryTask(...), - } - ConcurrentCallTask<QString>(...) - } - }; - \endcode - - The group may dynamically instantiate a custom storage structure, which may be used for - inter-task data exchange: - - \code - struct MyCustomStruct { QByteArray data; }; - - Storage<MyCustomStruct> storage; - - const auto onFirstSetup = [](NetworkQuery &task) { ... }; - const auto onFirstDone = [storage](const NetworkQuery &task) { - // storage-> gives a pointer to MyCustomStruct instance, - // created dynamically by the running task tree. - storage->data = task.reply()->readAll(); - }; - const auto onSecondSetup = [storage](ConcurrentCall<QImage> &task) { - // storage-> gives a pointer to MyCustomStruct. Since the group is sequential, - // the stored MyCustomStruct was already updated inside the onFirstDone handler. - const QByteArray storedData = storage->data; - }; - - const Group group { - // When the group is entered by a running task tree, it creates MyCustomStruct - // instance dynamically. It is later accessible from all handlers via - // the *storage or storage-> operators. - sequential, - storage, - NetworkQueryTask(onFirstSetup, onFirstDone, CallDoneIf::Success), - ConcurrentCallTask<QImage>(onSecondSetup) - }; - \endcode -*/ - -/*! - \fn Group::Group(const GroupItems &children) - - Constructs a group with a given list of \a children. - - This constructor is useful when the child items of the group are not known at compile time, - but later, at runtime: - - \code - const QStringList sourceList = ...; - - GroupItems groupItems { parallel }; - - for (const QString &source : sourceList) { - const NetworkQueryTask task(...); // use source for setup handler - groupItems << task; - } - - const Group group(groupItems); - \endcode -*/ - -/*! - \fn Group::Group(std::initializer_list<GroupItem> children) - - Constructs a group from \c std::initializer_list given by \a children. - - This constructor is useful when all child items of the group are known at compile time: - - \code - const Group group { - finishAllAndSuccess, - NetworkQueryTask(...), - Group { - NetworkQueryTask(...), - Group { - parallel, - NetworkQueryTask(...), - NetworkQueryTask(...), - } - ConcurrentCallTask<QString>(...) - } - }; - \endcode -*/ - -/*! - \class Tasking::Sync - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Synchronously executes a custom handler between other tasks. - \reentrant - - \c Sync is useful when you want to execute an additional handler between other tasks. - \c Sync is seen by its parent \l {Tasking::Group} {Group} as any other task. - Avoid long-running execution of the \c Sync's handler body, since it is executed - synchronously from the caller thread. If that is unavoidable, consider using - \c ConcurrentCallTask instead. -*/ - -/*! - \fn template <typename Handler> Sync::Sync(Handler &&handler) - - Constructs an element that executes a passed \a handler synchronously. - The \c Handler is of the \c std::function<DoneResult()> type. - The DoneResult value, returned by the \a handler, is considered during parent group's - \l {workflowPolicy} {workflow policy} resolution. - Optionally, the shortened form of \c std::function<void()> is also accepted. - In this case, it's assumed that the return value is DoneResult::Success. - - The passed \a handler executes synchronously from the caller thread, so avoid a long-running - execution of the handler body. Otherwise, consider using \c ConcurrentCallTask. - - \note The \c Sync element is not counted as a task when reporting task tree progress, - and is not included in TaskTree::taskCount() or TaskTree::progressMaximum(). -*/ - -/*! - \class Tasking::CustomTask - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template used for declaring custom task items and defining their setup - and done handlers. - \reentrant - - Describes custom task items within task tree recipes. - - Custom task names are aliased with unique names using the \c CustomTask template - with a given TaskAdapter subclass as a template parameter. - For example, \c ConcurrentCallTask<T> is an alias to the \c CustomTask that is defined - to work with \c ConcurrentCall<T> as an associated task class. - The following table contains example custom tasks and their associated task classes: - - \table - \header - \li Aliased Task Name (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li ConcurrentCallTask<ReturnType> - \li ConcurrentCall<ReturnType> - \li Starts an asynchronous task. Runs in a separate thread. - \row - \li NetworkQueryTask - \li NetworkQuery - \li Sends a network query. - \row - \li TaskTreeTask - \li TaskTree - \li Starts a nested task tree. - \row - \li TimeoutTask - \li \c std::chrono::milliseconds - \li Starts a timer. - \row - \li WaitForBarrierTask - \li MultiBarrier<Limit> - \li Starts an asynchronous task waiting for the barrier to pass. - \endtable -*/ - -/*! - \typealias Tasking::CustomTask::Task - - Type alias for the task type associated with the custom task's \c Adapter. -*/ - -/*! - \typealias Tasking::CustomTask::Deleter - - Type alias for the task's type deleter associated with the custom task's \c Adapter. -*/ - -/*! - \typealias Tasking::CustomTask::TaskSetupHandler - - Type alias for \c std::function<SetupResult(Task &)>. - - The \c TaskSetupHandler is an optional argument of a custom task element's constructor. - Any function with the above signature, when passed as a task setup handler, - will be called by the running task tree after the task is created and before it is started. - - Inside the body of the handler, you may configure the task according to your needs. - The additional parameters, including storages, may be passed to the handler - via the lambda capture. - You can decide dynamically whether the task should be started or skipped with - success or an error. - - \note Do not start the task inside the start handler by yourself. Leave it for TaskTree, - otherwise the behavior is undefined. - - The return value of the handler instructs the running task tree on how to proceed - after the handler's invocation is finished. The return value of SetupResult::Continue - instructs the task tree to continue running, that is, to execute the associated \c Task. - The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError - instructs the task tree to skip the task's execution and finish it immediately with - success or an error, respectively. - - When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError, - the task's done handler (if provided) isn't called afterwards. - - The constructor of a custom task accepts also functions in the shortened form of - \c std::function<void(Task &)>, that is, the return value is \c void. - In this case, it's assumed that the return value is SetupResult::Continue. - - \sa CustomTask(), TaskDoneHandler, GroupSetupHandler -*/ - -/*! - \typealias Tasking::CustomTask::TaskDoneHandler - - Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult. - - The \c TaskDoneHandler is an optional argument of a custom task element's constructor. - Any function with the above signature, when passed as a task done handler, - will be called by the running task tree after the task execution finished and before - the final result of the execution is reported back to the parent group. - - Inside the body of the handler you may retrieve the final data from the finished task. - The additional parameters, including storages, may be passed to the handler - via the lambda capture. - It is also possible to decide dynamically whether the task should finish with its return - value, or the final result should be tweaked. - - The DoneWith argument is optional and your done handler may omit it. - When provided, it holds the info about the final result of a task that will be - reported to its parent. - - If you do not plan to read any data from the finished task, - you may omit the \c {const Task &} argument. - - The returned DoneResult value is optional and your handler may return \c void instead. - In this case, the final result of the task will be equal to the value indicated by - the DoneWith argument. When the handler returns the DoneResult value, - the task's final result may be tweaked inside the done handler's body by the returned value. - - For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed, - and the task finishes unconditionally with the passed value of DoneResult. - - \sa CustomTask(), TaskSetupHandler, GroupDoneHandler -*/ - -/*! - \fn template <typename Adapter> template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> CustomTask<Adapter>::CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - - Constructs a \c CustomTask instance and attaches the \a setup and \a done handlers to the task. - When the running task tree is about to start the task, - it instantiates the associated \l Task object, invokes \a setup handler with a \e reference - to the created task, and starts it. When the running task finishes, - the task tree invokes a \a done handler, with a \c const \e reference to the created task. - - The passed \a setup handler is of the \l TaskSetupHandler type. For example: - - \code - static void parseAndLog(const QString &input); - - ... - - const QString input = ...; - - const auto onFirstSetup = [input](ConcurrentCall<void> &task) { - if (input == "Skip") - return SetupResult::StopWithSuccess; // This task won't start, the next one will - if (input == "Error") - return SetupResult::StopWithError; // This task and the next one won't start - task.setConcurrentCallData(parseAndLog, input); - // This task will start, and the next one will start after this one finished with success - return SetupResult::Continue; - }; - - const auto onSecondSetup = [input](ConcurrentCall<void> &task) { - task.setConcurrentCallData(parseAndLog, input); - }; - - const Group group { - ConcurrentCallTask<void>(onFirstSetup), - ConcurrentCallTask<void>(onSecondSetup) - }; - \endcode - - The \a done handler is of the \l TaskDoneHandler type. - By default, the \a done handler is invoked whenever the task finishes. - Pass a non-default value for the \a callDoneIf argument when you want the handler to be called - only on a successful or failed execution. - - \sa TaskSetupHandler, TaskDoneHandler -*/ - -/*! - \enum Tasking::WorkflowPolicy - - This enum describes the possible behavior of the Group element when any group's child task - finishes its execution. It's also used when the running Group is canceled. - - \value StopOnError - Default. Corresponds to the stopOnError global element. - If any child task finishes with an error, the group stops and finishes with an error. - If all child tasks finished with success, the group finishes with success. - If a group is empty, it finishes with success. - \value ContinueOnError - Corresponds to the continueOnError global element. - Similar to stopOnError, but in case any child finishes with an error, - the execution continues until all tasks finish, and the group reports an error - afterwards, even when some other tasks in the group finished with success. - If all child tasks finish successfully, the group finishes with success. - If a group is empty, it finishes with success. - \value StopOnSuccess - Corresponds to the stopOnSuccess global element. - If any child task finishes with success, the group stops and finishes with success. - If all child tasks finished with an error, the group finishes with an error. - If a group is empty, it finishes with an error. - \value ContinueOnSuccess - Corresponds to the continueOnSuccess global element. - Similar to stopOnSuccess, but in case any child finishes successfully, - the execution continues until all tasks finish, and the group reports success - afterwards, even when some other tasks in the group finished with an error. - If all child tasks finish with an error, the group finishes with an error. - If a group is empty, it finishes with an error. - \value StopOnSuccessOrError - Corresponds to the stopOnSuccessOrError global element. - The group starts as many tasks as it can. When any task finishes, - the group stops and reports the task's result. - Useful only in parallel mode. - In sequential mode, only the first task is started, and when finished, - the group finishes too, so the other tasks are always skipped. - If a group is empty, it finishes with an error. - \value FinishAllAndSuccess - Corresponds to the finishAllAndSuccess global element. - The group executes all tasks and ignores their return results. When all - tasks finished, the group finishes with success. - If a group is empty, it finishes with success. - \value FinishAllAndError - Corresponds to the finishAllAndError global element. - The group executes all tasks and ignores their return results. When all - tasks finished, the group finishes with an error. - If a group is empty, it finishes with an error. - - Whenever a child task's result causes the Group to stop, that is, - in case of StopOnError, StopOnSuccess, or StopOnSuccessOrError policies, - the Group cancels the other running child tasks (if any - for example in parallel mode), - and skips executing tasks it has not started yet (for example, in the sequential mode - - those, that are placed after the failed task). Both canceling and skipping child tasks - may happen when parallelLimit() is used. - - The table below summarizes the differences between various workflow policies: - - \table - \header - \li \l WorkflowPolicy - \li Executes all child tasks - \li Result - \li Result when the group is empty - \row - \li StopOnError - \li Stops when any child task finished with an error and reports an error - \li An error when at least one child task failed, success otherwise - \li Success - \row - \li ContinueOnError - \li Yes - \li An error when at least one child task failed, success otherwise - \li Success - \row - \li StopOnSuccess - \li Stops when any child task finished with success and reports success - \li Success when at least one child task succeeded, an error otherwise - \li An error - \row - \li ContinueOnSuccess - \li Yes - \li Success when at least one child task succeeded, an error otherwise - \li An error - \row - \li StopOnSuccessOrError - \li Stops when any child task finished and reports child task's result - \li Success or an error, depending on the finished child task's result - \li An error - \row - \li FinishAllAndSuccess - \li Yes - \li Success - \li Success - \row - \li FinishAllAndError - \li Yes - \li An error - \li An error - \endtable - - If a child of a group is also a group, the child group runs its tasks according to its own - workflow policy. When a parent group stops the running child group because - of parent group's workflow policy, that is, when the StopOnError, StopOnSuccess, - or StopOnSuccessOrError policy was used for the parent, - the child group's result is reported according to the - \b Result column and to the \b {child group's workflow policy} row in the table above. -*/ - -/*! - \variable Tasking::nullItem - - A convenient global group's element indicating a no-op item. - - This is useful in conditional expressions to indicate the absence of an optional element: - - \code - const ExecutableItem task = ...; - const std::optional<ExecutableItem> optionalTask = ...; - - Group group { - task, - optionalTask ? *optionalTask : nullItem - }; - \endcode -*/ - -/*! - \variable Tasking::successItem - - A convenient global executable element containing an empty, successful, synchronous task. - - This is useful in if-statements to indicate that a branch ends with success: - - \code - const ExecutableItem conditionalTask = ...; - - Group group { - stopOnDone, - If (conditionalTask) >> Then { - ... - } >> Else { - successItem - }, - nextTask - }; - \endcode - - In the above example, if the \c conditionalTask finishes with an error, the \c Else branch - is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped - (because of the stopOnDone workflow policy of the \c group) - and the \c group finishes with success. - - \sa errorItem -*/ - -/*! - \variable Tasking::errorItem - - A convenient global executable element containing an empty, erroneous, synchronous task. - - This is useful in if-statements to indicate that a branch ends with an error: - - \code - const ExecutableItem conditionalTask = ...; - - Group group { - stopOnError, - If (conditionalTask) >> Then { - ... - } >> Else { - errorItem - }, - nextTask - }; - \endcode - - In the above example, if the \c conditionalTask finishes with an error, the \c Else branch - is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped - (because of the stopOnError workflow policy of the \c group) - and the \c group finishes with an error. - - \sa successItem -*/ - -/*! - \variable Tasking::sequential - A convenient global group's element describing the sequential execution mode. - - This is the default execution mode of the Group element. - - When a Group has no execution mode, it runs in the sequential mode. - All the direct child tasks of a group are started in a chain, so that when one task finishes, - the next one starts. This enables you to pass the results from the previous task - as input to the next task before it starts. This mode guarantees that the next task - is started only after the previous task finishes. - - \sa parallel, parallelLimit() -*/ - -/*! - \variable Tasking::parallel - A convenient global group's element describing the parallel execution mode. - - All the direct child tasks of a group are started after the group is started, - without waiting for the previous child tasks to finish. - In this mode, all child tasks run simultaneously. - - \sa sequential, parallelLimit() -*/ - -/*! - \variable Tasking::parallelIdealThreadCountLimit - A convenient global group's element describing the parallel execution mode with a limited - number of tasks running simultanously. The limit is equal to the ideal number of threads - excluding the calling thread. - - This is a shortcut to: - \code - parallelLimit(qMax(QThread::idealThreadCount() - 1, 1)) - \endcode - - \sa parallel, parallelLimit() -*/ - -/*! - \variable Tasking::stopOnError - A convenient global group's element describing the StopOnError workflow policy. - - This is the default workflow policy of the Group element. -*/ - -/*! - \variable Tasking::continueOnError - A convenient global group's element describing the ContinueOnError workflow policy. -*/ - -/*! - \variable Tasking::stopOnSuccess - A convenient global group's element describing the StopOnSuccess workflow policy. -*/ - -/*! - \variable Tasking::continueOnSuccess - A convenient global group's element describing the ContinueOnSuccess workflow policy. -*/ - -/*! - \variable Tasking::stopOnSuccessOrError - A convenient global group's element describing the StopOnSuccessOrError workflow policy. -*/ - -/*! - \variable Tasking::finishAllAndSuccess - A convenient global group's element describing the FinishAllAndSuccess workflow policy. -*/ - -/*! - \variable Tasking::finishAllAndError - A convenient global group's element describing the FinishAllAndError workflow policy. -*/ - -/*! - \enum Tasking::SetupResult - - This enum is optionally returned from the group's or task's setup handler function. - It instructs the running task tree on how to proceed after the setup handler's execution - finished. - \value Continue - Default. The group's or task's execution continues normally. - When a group's or task's setup handler returns void, it's assumed that - it returned Continue. - \value StopWithSuccess - The group's or task's execution stops immediately with success. - When returned from the group's setup handler, all child tasks are skipped, - and the group's onGroupDone() handler is invoked with DoneWith::Success. - The group reports success to its parent. The group's workflow policy is ignored. - When returned from the task's setup handler, the task isn't started, - its done handler isn't invoked, and the task reports success to its parent. - \value StopWithError - The group's or task's execution stops immediately with an error. - When returned from the group's setup handler, all child tasks are skipped, - and the group's onGroupDone() handler is invoked with DoneWith::Error. - The group reports an error to its parent. The group's workflow policy is ignored. - When returned from the task's setup handler, the task isn't started, - its error handler isn't invoked, and the task reports an error to its parent. -*/ - -/*! - \enum Tasking::DoneResult - - This enum is optionally returned from the group's or task's done handler function. - When the done handler doesn't return any value, that is, its return type is \c void, - its final return value is automatically deduced by the running task tree and reported - to its parent group. - - When the done handler returns the DoneResult, you can tweak the final return value - inside the handler. - - When the DoneResult is returned by the group's done handler, the group's workflow policy - is ignored. - - This enum is also used inside the TaskInterface::done() signal and it indicates whether - the task finished with success or an error. - - \value Success - The group's or task's execution ends with success. - \value Error - The group's or task's execution ends with an error. -*/ - -/*! - \enum Tasking::DoneWith - - This enum is an optional argument for the group's or task's done handler. - It indicates whether the group or task finished with success or an error, or it was canceled. - - It is also used as an argument inside the TaskTree::done() signal, - indicating the final result of the TaskTree execution. - - \value Success - The group's or task's execution ended with success. - \value Error - The group's or task's execution ended with an error. - \value Cancel - The group's or task's execution was canceled. This happens when the user calls - TaskTree::cancel() for the running task tree or when the group's workflow policy - results in canceling some of its running children. - Tweaking the done handler's final result by returning Tasking::DoneResult from - the handler is no-op when the group's or task's execution was canceled. -*/ - -/*! - \enum Tasking::CallDoneIf - - This enum is an optional argument for the \l onGroupDone() element or custom task's constructor. - It instructs the task tree on when the group's or task's done handler should be invoked. - - \value SuccessOrError - The done handler is always invoked. - \value Success - The done handler is invoked only after successful execution, - that is, when DoneWith::Success. - \value Error - The done handler is invoked only after failed execution, - that is, when DoneWith::Error or when DoneWith::Cancel. -*/ - -/*! - \typealias Tasking::GroupItem::GroupSetupHandler - - Type alias for \c std::function<SetupResult()>. - - The \c GroupSetupHandler is an argument of the onGroupSetup() element. - Any function with the above signature, when passed as a group setup handler, - will be called by the running task tree when the group execution starts. - - The return value of the handler instructs the running group on how to proceed - after the handler's invocation is finished. The default return value of SetupResult::Continue - instructs the group to continue running, that is, to start executing its child tasks. - The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError - instructs the group to skip the child tasks' execution and finish immediately with - success or an error, respectively. - - When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError, - the group's done handler (if provided) is called synchronously immediately afterwards. - - \note Even if the group setup handler returns StopWithSuccess or StopWithError, - the group's done handler is invoked. This behavior differs from that of task done handler - and might change in the future. - - The onGroupSetup() element accepts also functions in the shortened form of - \c std::function<void()>, that is, the return value is \c void. - In this case, it's assumed that the return value is SetupResult::Continue. - - \sa onGroupSetup(), GroupDoneHandler, CustomTask::TaskSetupHandler -*/ - -/*! - \typealias Tasking::GroupItem::GroupDoneHandler - - Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult. - - The \c GroupDoneHandler is an argument of the onGroupDone() element. - Any function with the above signature, when passed as a group done handler, - will be called by the running task tree when the group execution ends. - - The DoneWith argument is optional and your done handler may omit it. - When provided, it holds the info about the final result of a group that will be - reported to its parent. - - The returned DoneResult value is optional and your handler may return \c void instead. - In this case, the final result of the group will be equal to the value indicated by - the DoneWith argument. When the handler returns the DoneResult value, - the group's final result may be tweaked inside the done handler's body by the returned value. - - For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed, - and the group finishes unconditionally with the passed value of DoneResult, - ignoring the group's workflow policy. - - \sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler -*/ - -/*! - \fn template <typename Handler> GroupItem onGroupSetup(Handler &&handler) - - Constructs a group's element holding the group setup handler. - The \a handler is invoked whenever the group starts. - - The passed \a handler is either of the \c std::function<SetupResult()> or the - \c std::function<void()> type. For more information on a possible handler type, refer to - \l {GroupItem::GroupSetupHandler}. - - When the \a handler is invoked, none of the group's child tasks are running yet. - - If a group contains the Storage elements, the \a handler is invoked - after the storages are constructed, so that the \a handler may already - perform some initial modifications to the active storages. - - \sa GroupItem::GroupSetupHandler, onGroupDone() -*/ - -/*! - \fn template <typename Handler> GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - - Constructs a group's element holding the group done handler. - By default, the \a handler is invoked whenever the group finishes. - Pass a non-default value for the \a callDoneIf argument when you want the handler to be called - only on a successful or failed execution. - Depending on the group's workflow policy, this handler may also be called - when the running group is canceled (e.g. when stopOnError element was used). - - The passed \a handler is of the \c std::function<DoneResult(DoneWith)> type. - Optionally, each of the return DoneResult type or the argument DoneWith type may be omitted - (that is, its return type may be \c void). For more information on a possible handler type, - refer to \l {GroupItem::GroupDoneHandler}. - - When the \a handler is invoked, all of the group's child tasks are already finished. - - If a group contains the Storage elements, the \a handler is invoked - before the storages are destructed, so that the \a handler may still - perform a last read of the active storages' data. - - \sa GroupItem::GroupDoneHandler, onGroupSetup() -*/ - -/*! - Constructs a group's element describing the \l{Execution Mode}{execution mode}. - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. - - For convenience, when appropriate, the \l sequential or \l parallel global elements - may be used instead. - - The \a limit defines the maximum number of direct child tasks running in parallel: - - \list - \li When \a limit equals to 0, there is no limit, and all direct child tasks are started - together, in the oder in which they appear in a group. This means the fully parallel - execution, and the \l parallel element may be used instead. - - \li When \a limit equals to 1, it means that only one child task may run at the time. - This means the sequential execution, and the \l sequential element may be used instead. - In this case, child tasks run in chain, so the next child task starts after - the previous child task has finished. - - \li When other positive number is passed as \a limit, the group's child tasks run - in parallel, but with a limited number of tasks running simultanously. - The \e limit defines the maximum number of tasks running in parallel in a group. - When the group is started, the first batch of tasks is started - (the number of tasks in a batch equals to the passed \a limit, at most), - while the others are kept waiting. When any running task finishes, - the group starts the next remaining one, so that the \e limit of simultaneously - running tasks inside a group isn't exceeded. This repeats on every child task's - finish until all child tasks are started. This enables you to limit the maximum - number of tasks that run simultaneously, for example if running too many processes might - block the machine for a long time. - \endlist - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group, the child group runs its tasks according - to its own execution mode. - - \sa sequential, parallel -*/ - -GroupItem parallelLimit(int limit) -{ - struct ParallelLimit : GroupItem { - ParallelLimit(int limit) : GroupItem({{}, limit}) {} - }; - return ParallelLimit(limit); -} - -/*! - Constructs a group's \l {Workflow Policy} {workflow policy} element for a given \a policy. - - For convenience, global elements may be used instead. - - \sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError, - finishAllAndSuccess, finishAllAndError, WorkflowPolicy -*/ -GroupItem workflowPolicy(WorkflowPolicy policy) -{ - struct WorkflowPolicyItem : GroupItem { - WorkflowPolicyItem(WorkflowPolicy policy) : GroupItem({{}, {}, policy}) {} - }; - return WorkflowPolicyItem(policy); -} - -const GroupItem sequential = parallelLimit(1); -const GroupItem parallel = parallelLimit(0); -const GroupItem parallelIdealThreadCountLimit = parallelLimit(qMax(QThread::idealThreadCount() - 1, 1)); - -const GroupItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError); -const GroupItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError); -const GroupItem stopOnSuccess = workflowPolicy(WorkflowPolicy::StopOnSuccess); -const GroupItem continueOnSuccess = workflowPolicy(WorkflowPolicy::ContinueOnSuccess); -const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSuccessOrError); -const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess); -const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); - -// Keep below the above in order to avoid static initialization fiasco. -const GroupItem nullItem = Group {}; -const ExecutableItem successItem = Group { finishAllAndSuccess }; -const ExecutableItem errorItem = Group { finishAllAndError }; - -Group operator>>(const For &forItem, const Do &doItem) -{ - return {forItem.m_loop, doItem.m_children}; -} - -Group operator>>(const When &whenItem, const Do &doItem) -{ - const SingleBarrier barrier; - - return { - barrier, - parallel, - whenItem.m_barrierKicker(barrier), - Group { - waitForBarrierTask(barrier), - doItem.m_children - } - }; -} - -// Please note the thread_local keyword below guarantees a separate instance per thread. -// The s_activeTaskTrees is currently used internally only and is not exposed in the public API. -// It serves for withLog() implementation now. Add a note here when a new usage is introduced. -static thread_local QList<TaskTree *> s_activeTaskTrees = {}; - -static TaskTree *activeTaskTree() -{ - QT_ASSERT(s_activeTaskTrees.size(), return nullptr); - return s_activeTaskTrees.back(); -} - -DoneResult toDoneResult(bool success) -{ - return success ? DoneResult::Success : DoneResult::Error; -} - -static SetupResult toSetupResult(bool success) -{ - return success ? SetupResult::StopWithSuccess : SetupResult::StopWithError; -} - -static DoneResult toDoneResult(DoneWith doneWith) -{ - return doneWith == DoneWith::Success ? DoneResult::Success : DoneResult::Error; -} - -static DoneWith toDoneWith(DoneResult result) -{ - return result == DoneResult::Success ? DoneWith::Success : DoneWith::Error; -} - -class LoopThreadData -{ - Q_DISABLE_COPY_MOVE(LoopThreadData) - -public: - LoopThreadData() = default; - void pushIteration(int index) - { - m_activeLoopStack.push_back(index); - } - void popIteration() - { - QT_ASSERT(m_activeLoopStack.size(), return); - m_activeLoopStack.pop_back(); - } - int iteration() const - { - QT_ASSERT(m_activeLoopStack.size(), qWarning( - "The referenced loop is not reachable in the running tree. " - "A -1 will be returned which might lead to a crash in the calling code. " - "It is possible that no loop was added to the tree, " - "or the loop is not reachable from where it is referenced."); return -1); - return m_activeLoopStack.last(); - } - -private: - QList<int> m_activeLoopStack; -}; - -class LoopData -{ -public: - LoopThreadData &threadData() { - QMutexLocker lock(&m_threadDataMutex); - return m_threadDataMap.try_emplace(QThread::currentThread()).first->second; - } - - const std::optional<int> m_loopCount = {}; - const Loop::ValueGetter m_valueGetter = {}; - const Loop::Condition m_condition = {}; - QMutex m_threadDataMutex = {}; - // Use std::map on purpose, so that it doesn't invalidate references on modifications. - // Don't optimize it by using std::unordered_map. - std::map<QThread *, LoopThreadData> m_threadDataMap = {}; -}; - -Loop::Loop() - : m_loopData(new LoopData) -{} - -Loop::Loop(int count, const ValueGetter &valueGetter) - : m_loopData(new LoopData{count, valueGetter}) -{} - -Loop::Loop(const Condition &condition) - : m_loopData(new LoopData{{}, {}, condition}) -{} - -int Loop::iteration() const -{ - return m_loopData->threadData().iteration(); -} - -const void *Loop::valuePtr() const -{ - return m_loopData->m_valueGetter(iteration()); -} - -using StoragePtr = void *; - -static constexpr QLatin1StringView s_activeStorageWarning = - "The referenced storage is not reachable in the running tree. " - "A nullptr will be returned which might lead to a crash in the calling code. " - "It is possible that no storage was added to the tree, " - "or the storage is not reachable from where it is referenced."_L1; - -class StorageThreadData -{ - Q_DISABLE_COPY_MOVE(StorageThreadData) - -public: - StorageThreadData() = default; - void pushStorage(StoragePtr storagePtr) - { - m_activeStorageStack.push_back({storagePtr, activeTaskTree()}); - } - void popStorage() - { - QT_ASSERT(m_activeStorageStack.size(), return); - m_activeStorageStack.pop_back(); - } - StoragePtr activeStorage() const - { - QT_ASSERT(m_activeStorageStack.size(), - qWarning().noquote() << s_activeStorageWarning; return nullptr); - const QPair<StoragePtr, TaskTree *> &top = m_activeStorageStack.last(); - QT_ASSERT(top.second == activeTaskTree(), - qWarning().noquote() << s_activeStorageWarning; return nullptr); - return top.first; - } - -private: - QList<QPair<StoragePtr, TaskTree *>> m_activeStorageStack; -}; - -class StorageData -{ -public: - StorageThreadData &threadData() { - QMutexLocker lock(&m_threadDataMutex); - return m_threadDataMap.try_emplace(QThread::currentThread()).first->second; - } - - const StorageBase::StorageConstructor m_constructor = {}; - const StorageBase::StorageDestructor m_destructor = {}; - QMutex m_threadDataMutex = {}; - // Use std::map on purpose, so that it doesn't invalidate references on modifications. - // Don't optimize it by using std::unordered_map. - std::map<QThread *, StorageThreadData> m_threadDataMap = {}; -}; - -StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor) - : m_storageData(new StorageData{ctor, dtor}) -{} - -void *StorageBase::activeStorageVoid() const -{ - return m_storageData->threadData().activeStorage(); -} - -void GroupItem::addChildren(const GroupItems &children) -{ - QT_ASSERT(m_type == Type::Group || m_type == Type::List, - qWarning("Only Group or List may have children, skipping..."); return); - if (m_type == Type::List) { - m_children.append(children); - return; - } - for (const GroupItem &child : children) { - switch (child.m_type) { - case Type::List: - addChildren(child.m_children); - break; - case Type::Group: - m_children.append(child); - break; - case Type::GroupData: - if (child.m_groupData.m_groupHandler.m_setupHandler) { - QT_ASSERT(!m_groupData.m_groupHandler.m_setupHandler, - qWarning("Group setup handler redefinition, overriding...")); - m_groupData.m_groupHandler.m_setupHandler - = child.m_groupData.m_groupHandler.m_setupHandler; - } - if (child.m_groupData.m_groupHandler.m_doneHandler) { - QT_ASSERT(!m_groupData.m_groupHandler.m_doneHandler, - qWarning("Group done handler redefinition, overriding...")); - m_groupData.m_groupHandler.m_doneHandler - = child.m_groupData.m_groupHandler.m_doneHandler; - m_groupData.m_groupHandler.m_callDoneIf - = child.m_groupData.m_groupHandler.m_callDoneIf; - } - if (child.m_groupData.m_parallelLimit) { - QT_ASSERT(!m_groupData.m_parallelLimit, - qWarning("Group execution mode redefinition, overriding...")); - m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit; - } - if (child.m_groupData.m_workflowPolicy) { - QT_ASSERT(!m_groupData.m_workflowPolicy, - qWarning("Group workflow policy redefinition, overriding...")); - m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy; - } - if (child.m_groupData.m_loop) { - QT_ASSERT(!m_groupData.m_loop, - qWarning("Group loop redefinition, overriding...")); - m_groupData.m_loop = child.m_groupData.m_loop; - } - break; - case Type::TaskHandler: - QT_ASSERT(child.m_taskHandler.m_createHandler, - qWarning("Task create handler can't be null, skipping..."); return); - m_children.append(child); - break; - case Type::Storage: - // Check for duplicates, as can't have the same storage twice on the same level. - for (const StorageBase &storage : child.m_storageList) { - if (m_storageList.contains(storage)) { - QT_ASSERT(false, qWarning("Can't add the same storage into one Group twice, " - "skipping...")); - continue; - } - m_storageList.append(storage); - } - break; - } - } -} - -/*! - \class Tasking::ExecutableItem - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Base class for executable task items. - \reentrant - - \c ExecutableItem provides an additional interface for items containing executable tasks. - Use withTimeout() to attach a timeout to a task. - Use withLog() to include debugging information about the task startup and the execution result. -*/ - -/*! - Attaches \c TimeoutTask to a copy of \c this ExecutableItem, elapsing after \a timeout - in milliseconds, with an optionally provided timeout \a handler, and returns the coupled item. - - When the ExecutableItem finishes before \a timeout passes, the returned item finishes - immediately with the task's result. Otherwise, \a handler is invoked (if provided), - the task is canceled, and the returned item finishes with an error. -*/ -Group ExecutableItem::withTimeout(milliseconds timeout, - const std::function<void()> &handler) const -{ - const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; }; - return Group { - parallel, - stopOnSuccessOrError, - Group { - finishAllAndError, - handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success) - : TimeoutTask(onSetup) - }, - *this - }; -} - -static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); } - -static QString logHeader(const QString &logName) -{ - return QString::fromLatin1("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName); -}; - -/*! - Attaches a custom debug printout to a copy of \c this ExecutableItem, - issued on task startup and after the task is finished, and returns the coupled item. - - The debug printout includes a timestamp of the event (start or finish) - and \a logName to identify the specific task in the debug log. - - The finish printout contains the additional information whether the execution was - synchronous or asynchronous, its result (the value described by the DoneWith enum), - and the total execution time in milliseconds. -*/ -Group ExecutableItem::withLog(const QString &logName) const -{ - struct LogStorage - { - time_point<system_clock, nanoseconds> start; - int asyncCount = 0; - }; - const Storage<LogStorage> storage; - return Group { - storage, - onGroupSetup([storage, logName] { - storage->start = system_clock::now(); - storage->asyncCount = activeTaskTree()->asyncCount(); - qDebug().noquote().nospace() << logHeader(logName) << " started."; - }), - *this, - onGroupDone([storage, logName](DoneWith result) { - const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start); - const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount; - QT_CHECK(asyncCountDiff >= 0); - const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>(); - const QString syncType = asyncCountDiff ? QString::fromLatin1("asynchronously") - : QString::fromLatin1("synchronously"); - qDebug().noquote().nospace() << logHeader(logName) << " finished " << syncType - << " with " << doneWithEnum.valueToKey(int(result)) - << " within " << elapsed.count() << "ms."; - }) - }; -} - -/*! - \fn Group ExecutableItem::operator!(const ExecutableItem &item) - - Returns a Group with the DoneResult of \a item negated. - - If \a item reports DoneResult::Success, the returned item reports DoneResult::Error. - If \a item reports DoneResult::Error, the returned item reports DoneResult::Success. - - The returned item is equivalent to: - \code - Group { - item, - onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); }) - } - \endcode - - \sa operator&&(), operator||() -*/ -Group operator!(const ExecutableItem &item) -{ - return { - item, - onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); }) - }; -} - -/*! - \fn Group ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second) - - Returns a Group with \a first and \a second tasks merged with conjunction. - - Both \a first and \a second tasks execute in sequence. - If both tasks report DoneResult::Success, the returned item reports DoneResult::Success. - Otherwise, the returned item reports DoneResult::Error. - - The returned item is - \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}: - if the \a first task reports DoneResult::Error, the \a second task is skipped, - and the returned item reports DoneResult::Error immediately. - - The returned item is equivalent to: - \code - Group { stopOnError, first, second } - \endcode - - \note Parallel execution of conjunction in a short-circuit manner can be achieved with the - following code: \c {Group { parallel, stopOnError, first, second }}. In this case: - if the \e {first finished} task reports DoneResult::Error, - the \e other task is canceled, and the group reports DoneResult::Error immediately. - - \sa operator||(), operator!() -*/ -Group operator&&(const ExecutableItem &first, const ExecutableItem &second) -{ - return { stopOnError, first, second }; -} - -/*! - \fn Group ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second) - - Returns a Group with \a first and \a second tasks merged with disjunction. - - Both \a first and \a second tasks execute in sequence. - If both tasks report DoneResult::Error, the returned item reports DoneResult::Error. - Otherwise, the returned item reports DoneResult::Success. - - The returned item is - \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}: - if the \a first task reports DoneResult::Success, the \a second task is skipped, - and the returned item reports DoneResult::Success immediately. - - The returned item is equivalent to: - \code - Group { stopOnSuccess, first, second } - \endcode - - \note Parallel execution of disjunction in a short-circuit manner can be achieved with the - following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case: - if the \e {first finished} task reports DoneResult::Success, - the \e other task is canceled, and the group reports DoneResult::Success immediately. - - \sa operator&&(), operator!() -*/ -Group operator||(const ExecutableItem &first, const ExecutableItem &second) -{ - return { stopOnSuccess, first, second }; -} - -/*! - \fn Group ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result) - \overload ExecutableItem::operator&&() - - Returns the \a item task if the \a result is DoneResult::Success; otherwise returns - the \a item task with its done result tweaked to DoneResult::Error. - - The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result - into DoneResult::Error unconditionally. -*/ -Group operator&&(const ExecutableItem &item, DoneResult result) -{ - return { result == DoneResult::Success ? stopOnError : finishAllAndError, item }; -} - -/*! - \fn Group ExecutableItem::operator||(const ExecutableItem &item, DoneResult result) - \overload ExecutableItem::operator||() - - Returns the \a item task if the \a result is DoneResult::Error; otherwise returns - the \a item task with its done result tweaked to DoneResult::Success. - - The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result - into DoneResult::Success unconditionally. -*/ -Group operator||(const ExecutableItem &item, DoneResult result) -{ - return { result == DoneResult::Error ? stopOnError : finishAllAndSuccess, item }; -} - -Group ExecutableItem::withCancelImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper, - const GroupItems &postCancelRecipe) const -{ - const Storage<bool> canceledStorage(false); - - const auto onSetup = [connectWrapper, canceledStorage](Barrier &barrier) { - connectWrapper(&barrier, [barrierPtr = &barrier, canceled = canceledStorage.activeStorage()] { - *canceled = true; - barrierPtr->advance(); - }); - }; - - const auto wasCanceled = [canceledStorage] { return *canceledStorage; }; - - return { - continueOnError, - canceledStorage, - Group { - parallel, - stopOnSuccessOrError, - BarrierTask(onSetup) && errorItem, - *this - }, - If (wasCanceled) >> Then { - postCancelRecipe - } - }; -} - -Group ExecutableItem::withAcceptImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const -{ - const auto onSetup = [connectWrapper](Barrier &barrier) { - connectWrapper(&barrier, [barrierPtr = &barrier] { barrierPtr->advance(); }); - }; - return Group { - parallel, - BarrierTask(onSetup), - *this - }; -} - -class TaskTreePrivate; -class TaskNode; -class RuntimeContainer; -class RuntimeIteration; -class RuntimeTask; - -class ExecutionContextActivator -{ -public: - ExecutionContextActivator(RuntimeIteration *iteration) { - activateTaskTree(iteration); - activateContext(iteration); - } - ExecutionContextActivator(RuntimeContainer *container) { - activateTaskTree(container); - activateContext(container); - } - ~ExecutionContextActivator() { - for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order - m_activeStorages[i].m_storageData->threadData().popStorage(); - for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order - m_activeLoops[i].m_loopData->threadData().popIteration(); - QT_ASSERT(s_activeTaskTrees.size(), return); - s_activeTaskTrees.pop_back(); - } - -private: - void activateTaskTree(RuntimeIteration *iteration); - void activateTaskTree(RuntimeContainer *container); - void activateContext(RuntimeIteration *iteration); - void activateContext(RuntimeContainer *container); - QList<Loop> m_activeLoops; - QList<StorageBase> m_activeStorages; -}; - -class ContainerNode -{ - Q_DISABLE_COPY(ContainerNode) - -public: - ContainerNode(ContainerNode &&other) = default; - ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task); - - TaskTreePrivate *const m_taskTreePrivate = nullptr; - - const GroupItem::GroupHandler m_groupHandler; - const int m_parallelLimit = 1; - const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - const std::optional<Loop> m_loop; - const QList<StorageBase> m_storageList; - std::vector<TaskNode> m_children; - const int m_taskCount = 0; -}; - -class TaskNode -{ - Q_DISABLE_COPY(TaskNode) - -public: - TaskNode(TaskNode &&other) = default; - TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task) - : m_taskHandler(task.m_taskHandler) - , m_container(taskTreePrivate, task) - {} - - bool isTask() const { return bool(m_taskHandler.m_createHandler); } - int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; } - - const GroupItem::TaskHandler m_taskHandler; - ContainerNode m_container; -}; - -class TaskTreePrivate -{ - Q_DISABLE_COPY_MOVE(TaskTreePrivate) - -public: - explicit TaskTreePrivate(TaskTree *taskTree); - ~TaskTreePrivate(); - - void start(); - void stop(); - void bumpAsyncCount(); - void advanceProgress(int byValue); - void emitDone(DoneWith result); - void callSetupHandler(const StorageBase &storage, StoragePtr storagePtr) { - callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler); - } - void callDoneHandler(const StorageBase &storage, StoragePtr storagePtr) { - callStorageHandler(storage, storagePtr, &StorageHandler::m_doneHandler); - } - struct StorageHandler { - StorageBase::StorageHandler m_setupHandler = {}; - StorageBase::StorageHandler m_doneHandler = {}; - }; - typedef StorageBase::StorageHandler StorageHandler::*HandlerPtr; // ptr to class member - void callStorageHandler(const StorageBase &storage, StoragePtr storagePtr, HandlerPtr ptr) - { - const auto it = m_storageHandlers.constFind(storage); - if (it == m_storageHandlers.constEnd()) - return; - const StorageHandler storageHandler = *it; - if (storageHandler.*ptr) { - GuardLocker locker(m_guard); - (storageHandler.*ptr)(storagePtr); - } - } - - // Node related methods - - // If returned value != Continue, childDone() needs to be called in parent container (in caller) - // in order to unwind properly. - void startTask(const std::shared_ptr<RuntimeTask> &node); - void stopTask(RuntimeTask *node); - bool invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith); - - // Container related methods - - void continueContainer(RuntimeContainer *container); - void startChildren(RuntimeContainer *container); - void childDone(RuntimeIteration *iteration, bool success); - void stopContainer(RuntimeContainer *container); - bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith); - bool invokeLoopHandler(RuntimeContainer *container); - - template <typename Container, typename Handler, typename ...Args, - typename ReturnType = std::invoke_result_t<Handler, Args...>> - ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args) - { - QT_ASSERT(!m_guard.isLocked(), qWarning("Nested execution of handlers detected." - "This may happen when one task's handler has entered a nested event loop," - "and other task finished during nested event loop's processing, " - "causing stopping (canceling) the task executing the nested event loop. " - "This includes the case when QCoreApplication::processEvents() was called from " - "the handler. It may also happen when the Barrier task is advanced directly " - "from some other task handler. This will lead to a crash. " - "Avoid event processing during handlers' execution. " - "If it can't be avoided, make sure no other tasks are run in parallel when " - "processing events from the handler.")); - ExecutionContextActivator activator(container); - GuardLocker locker(m_guard); - return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...); - } - - static int effectiveLoopCount(const std::optional<Loop> &loop) - { - return loop && loop->m_loopData->m_loopCount ? *loop->m_loopData->m_loopCount : 1; - } - - TaskTree *q = nullptr; - Guard m_guard; - int m_progressValue = 0; - int m_asyncCount = 0; - QSet<StorageBase> m_storages; - QHash<StorageBase, StorageHandler> m_storageHandlers; - std::optional<TaskNode> m_root; - std::shared_ptr<RuntimeTask> m_runtimeRoot; // Keep me last in order to destruct first -}; - -static bool initialSuccessBit(WorkflowPolicy workflowPolicy) -{ - switch (workflowPolicy) { - case WorkflowPolicy::StopOnError: - case WorkflowPolicy::ContinueOnError: - case WorkflowPolicy::FinishAllAndSuccess: - return true; - case WorkflowPolicy::StopOnSuccess: - case WorkflowPolicy::ContinueOnSuccess: - case WorkflowPolicy::StopOnSuccessOrError: - case WorkflowPolicy::FinishAllAndError: - return false; - } - QT_CHECK(false); - return false; -} - -static bool isProgressive(RuntimeContainer *container); - -class RuntimeIteration -{ - Q_DISABLE_COPY(RuntimeIteration) - -public: - RuntimeIteration(int index, RuntimeContainer *container); - ~RuntimeIteration(); - std::optional<Loop> loop() const; - void removeChild(RuntimeTask *node); - - const int m_iterationIndex = 0; - const bool m_isProgressive = true; - RuntimeContainer *m_container = nullptr; - int m_doneCount = 0; - std::vector<std::shared_ptr<RuntimeTask>> m_children = {}; // Owning. -}; - -class RuntimeContainer -{ - Q_DISABLE_COPY(RuntimeContainer) - -public: - RuntimeContainer(const ContainerNode &taskContainer, RuntimeTask *parentTask) - : m_containerNode(taskContainer) - , m_parentTask(parentTask) - , m_storages(createStorages(taskContainer)) - , m_successBit(initialSuccessBit(taskContainer.m_workflowPolicy)) - , m_shouldIterate(taskContainer.m_loop) - {} - - ~RuntimeContainer() - { - for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const StorageBase storage = m_containerNode.m_storageList[i]; - StoragePtr storagePtr = m_storages.value(i); - if (m_callStorageDoneHandlersOnDestruction) - m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr); - storage.m_storageData->m_destructor(storagePtr); - } - } - - static QList<StoragePtr> createStorages(const ContainerNode &container); - bool isStarting() const { return m_startGuard.isLocked(); } - RuntimeIteration *parentIteration() const; - bool updateSuccessBit(bool success); - void deleteFinishedIterations(); - int progressiveLoopCount() const - { - return m_containerNode.m_taskTreePrivate->effectiveLoopCount(m_containerNode.m_loop); - } - - const ContainerNode &m_containerNode; // Not owning. - RuntimeTask *m_parentTask = nullptr; // Not owning. - const QList<StoragePtr> m_storages; // Owning. - - bool m_successBit = true; - bool m_callStorageDoneHandlersOnDestruction = false; - Guard m_startGuard; - - int m_iterationCount = 0; - int m_nextToStart = 0; - int m_runningChildren = 0; - bool m_shouldIterate = true; - std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning. -}; - -class RuntimeTask -{ -public: - ~RuntimeTask() - { - if (m_task) { - // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204. - QObject::disconnect(m_task.get(), &TaskInterface::done, nullptr, nullptr); - } - } - - const TaskNode &m_taskNode; // Not owning. - RuntimeIteration *m_parentIteration = nullptr; // Not owning. - std::optional<RuntimeContainer> m_container = {}; // Owning. - std::unique_ptr<TaskInterface> m_task = {}; // Owning. - SetupResult m_setupResult = SetupResult::Continue; -}; - -RuntimeIteration::~RuntimeIteration() = default; - -TaskTreePrivate::TaskTreePrivate(TaskTree *taskTree) - : q(taskTree) {} - -TaskTreePrivate::~TaskTreePrivate() = default; - -static bool isProgressive(RuntimeContainer *container) -{ - RuntimeIteration *iteration = container->m_parentTask->m_parentIteration; - return iteration ? iteration->m_isProgressive : true; -} - -void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration) -{ - activateTaskTree(iteration->m_container); -} - -void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container) -{ - s_activeTaskTrees.push_back(container->m_containerNode.m_taskTreePrivate->q); -} - -void ExecutionContextActivator::activateContext(RuntimeIteration *iteration) -{ - std::optional<Loop> loop = iteration->loop(); - if (loop) { - loop->m_loopData->threadData().pushIteration(iteration->m_iterationIndex); - m_activeLoops.append(*loop); - } - activateContext(iteration->m_container); -} - -void ExecutionContextActivator::activateContext(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - for (int i = 0; i < containerNode.m_storageList.size(); ++i) { - const StorageBase &storage = containerNode.m_storageList[i]; - if (m_activeStorages.contains(storage)) - continue; // Storage shadowing: The storage is already active, skipping it... - m_activeStorages.append(storage); - storage.m_storageData->threadData().pushStorage(container->m_storages.value(i)); - } - // Go to the parent after activating this storages so that storage shadowing works - // in the direction from child to parent root. - if (container->parentIteration()) - activateContext(container->parentIteration()); -} - -void TaskTreePrivate::start() -{ - QT_ASSERT(m_root, return); - QT_ASSERT(!m_runtimeRoot, return); - m_asyncCount = 0; - m_progressValue = 0; - { - GuardLocker locker(m_guard); - emit q->started(); - emit q->asyncCountChanged(m_asyncCount); - emit q->progressValueChanged(m_progressValue); - } - // TODO: check storage handlers for not existing storages in tree - for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { - QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " - "exist in task tree. Its handlers will never be called.")); - } - m_runtimeRoot.reset(new RuntimeTask{*m_root}); - startTask(m_runtimeRoot); - bumpAsyncCount(); -} - -void TaskTreePrivate::stop() -{ - QT_ASSERT(m_root, return); - if (!m_runtimeRoot) - return; - stopTask(m_runtimeRoot.get()); - m_runtimeRoot.reset(); - emitDone(DoneWith::Cancel); -} - -void TaskTreePrivate::bumpAsyncCount() -{ - if (!m_runtimeRoot) - return; - ++m_asyncCount; - GuardLocker locker(m_guard); - emit q->asyncCountChanged(m_asyncCount); -} - -void TaskTreePrivate::advanceProgress(int byValue) -{ - if (byValue == 0) - return; - QT_CHECK(byValue > 0); - QT_CHECK(m_progressValue + byValue <= m_root->taskCount()); - m_progressValue += byValue; - GuardLocker locker(m_guard); - emit q->progressValueChanged(m_progressValue); -} - -void TaskTreePrivate::emitDone(DoneWith result) -{ - QT_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->done(result); -} - -RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container) - : m_iterationIndex(index) - , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container)) - , m_container(container) -{} - -std::optional<Loop> RuntimeIteration::loop() const -{ - return m_container->m_containerNode.m_loop; -} - -void RuntimeIteration::removeChild(RuntimeTask *task) -{ - const auto it = std::find_if(m_children.cbegin(), m_children.cend(), [task](const auto &ptr) { - return ptr.get() == task; - }); - if (it != m_children.cend()) - m_children.erase(it); -} - -static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate, - const GroupItems &children) -{ - std::vector<TaskNode> result; - result.reserve(children.size()); - for (const GroupItem &child : children) - result.emplace_back(taskTreePrivate, child); - return result; -} - -ContainerNode::ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task) - : m_taskTreePrivate(taskTreePrivate) - , m_groupHandler(task.m_groupData.m_groupHandler) - , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(1)) - , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError)) - , m_loop(task.m_groupData.m_loop) - , m_storageList(task.m_storageList) - , m_children(createChildren(taskTreePrivate, task.m_children)) - , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, - [](int r, const TaskNode &n) { return r + n.taskCount(); }) - * taskTreePrivate->effectiveLoopCount(m_loop)) -{ - for (const StorageBase &storage : m_storageList) - m_taskTreePrivate->m_storages << storage; -} - -QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container) -{ - QList<StoragePtr> storages; - for (const StorageBase &storage : container.m_storageList) { - StoragePtr storagePtr = storage.m_storageData->m_constructor(); - storages.append(storagePtr); - container.m_taskTreePrivate->callSetupHandler(storage, storagePtr); - } - return storages; -} - -RuntimeIteration *RuntimeContainer::parentIteration() const -{ - return m_parentTask->m_parentIteration; -} - -bool RuntimeContainer::updateSuccessBit(bool success) -{ - if (m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess - || m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndError - || m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) { - if (m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) - m_successBit = success; - return m_successBit; - } - - const bool donePolicy = m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccess - || m_containerNode.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess; - m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); - return m_successBit; -} - -void RuntimeContainer::deleteFinishedIterations() -{ - for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) { - if (it->get()->m_doneCount == int(m_containerNode.m_children.size())) - it = m_iterations.erase(it); - else - ++it; - } -} - -void TaskTreePrivate::continueContainer(RuntimeContainer *container) -{ - RuntimeTask *parentTask = container->m_parentTask; - if (parentTask->m_setupResult == SetupResult::Continue) - startChildren(container); - if (parentTask->m_setupResult == SetupResult::Continue) - return; - - const bool bit = container->updateSuccessBit(parentTask->m_setupResult == SetupResult::StopWithSuccess); - RuntimeIteration *parentIteration = container->parentIteration(); - QT_CHECK(parentTask); - const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error); - parentTask->m_setupResult = toSetupResult(result); - if (parentIteration) { - parentIteration->removeChild(parentTask); - if (!parentIteration->m_container->isStarting()) - childDone(parentIteration, result); - } else { - QT_CHECK(m_runtimeRoot.get() == parentTask); - m_runtimeRoot.reset(); - emitDone(result ? DoneWith::Success : DoneWith::Error); - } -} - -void TaskTreePrivate::startChildren(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - const int childCount = int(containerNode.m_children.size()); - - if (container->m_iterationCount == 0) { - if (container->m_shouldIterate && !invokeLoopHandler(container)) { - if (isProgressive(container)) - advanceProgress(containerNode.m_taskCount); - container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit); - return; - } - container->m_iterations.emplace_back( - std::make_unique<RuntimeIteration>(container->m_iterationCount, container)); - ++container->m_iterationCount; - } - - GuardLocker locker(container->m_startGuard); - - while (containerNode.m_parallelLimit == 0 - || container->m_runningChildren < containerNode.m_parallelLimit) { - container->deleteFinishedIterations(); - if (container->m_nextToStart == childCount) { - if (invokeLoopHandler(container)) { - container->m_nextToStart = 0; - container->m_iterations.emplace_back( - std::make_unique<RuntimeIteration>(container->m_iterationCount, container)); - ++container->m_iterationCount; - } else if (container->m_iterations.empty()) { - container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit); - return; - } else { - return; - } - } - if (containerNode.m_children.size() == 0) // Empty loop body. - continue; - - RuntimeIteration *iteration = container->m_iterations.back().get(); - const std::shared_ptr<RuntimeTask> task( - new RuntimeTask{containerNode.m_children.at(container->m_nextToStart), iteration}); - iteration->m_children.emplace_back(task); - ++container->m_runningChildren; - ++container->m_nextToStart; - - startTask(task); - if (task->m_setupResult == SetupResult::Continue) - continue; - - task->m_parentIteration->removeChild(task.get()); - childDone(iteration, task->m_setupResult == SetupResult::StopWithSuccess); - if (container->m_parentTask->m_setupResult != SetupResult::Continue) - return; - } -} - -void TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success) -{ - RuntimeContainer *container = iteration->m_container; - const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy; - const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError - || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success) - || (workflowPolicy == WorkflowPolicy::StopOnError && !success); - ++iteration->m_doneCount; - --container->m_runningChildren; - const bool updatedSuccess = container->updateSuccessBit(success); - container->m_parentTask->m_setupResult = shouldStop ? toSetupResult(updatedSuccess) : SetupResult::Continue; - if (shouldStop) - stopContainer(container); - - if (container->isStarting()) - return; - continueContainer(container); -} - -void TaskTreePrivate::stopContainer(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - for (auto &iteration : container->m_iterations) { - for (auto &child : iteration->m_children) { - ++iteration->m_doneCount; - stopTask(child.get()); - } - - if (iteration->m_isProgressive) { - int skippedTaskCount = 0; - for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i) - skippedTaskCount += containerNode.m_children.at(i).taskCount(); - advanceProgress(skippedTaskCount); - } - } - const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount; - if (skippedIterations > 0) { - advanceProgress(container->m_containerNode.m_taskCount / container->progressiveLoopCount() - * skippedIterations); - } -} - -static bool shouldCall(CallDoneIf callDoneIf, DoneWith result) -{ - if (result == DoneWith::Success) - return callDoneIf != CallDoneIf::Error; - return callDoneIf != CallDoneIf::Success; -} - -bool TaskTreePrivate::invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith) -{ - DoneResult result = toDoneResult(doneWith); - const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler; - if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith)) - result = invokeHandler(container, groupHandler.m_doneHandler, doneWith); - container->m_callStorageDoneHandlersOnDestruction = true; - return result == DoneResult::Success; -} - -bool TaskTreePrivate::invokeLoopHandler(RuntimeContainer *container) -{ - if (container->m_shouldIterate) { - const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get(); - if (loopData->m_loopCount) { - container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount; - } else if (loopData->m_condition) { - container->m_shouldIterate = invokeHandler(container, loopData->m_condition, - container->m_iterationCount); - } - } - return container->m_shouldIterate; -} - -void TaskTreePrivate::startTask(const std::shared_ptr<RuntimeTask> &node) -{ - if (!node->m_taskNode.isTask()) { - const ContainerNode &containerNode = node->m_taskNode.m_container; - node->m_container.emplace(containerNode, node.get()); - RuntimeContainer *container = &*node->m_container; - if (containerNode.m_groupHandler.m_setupHandler) { - container->m_parentTask->m_setupResult = invokeHandler(container, containerNode.m_groupHandler.m_setupHandler); - if (container->m_parentTask->m_setupResult != SetupResult::Continue) { - if (isProgressive(container)) - advanceProgress(containerNode.m_taskCount); - // Non-Continue SetupResult takes precedence over the workflow policy. - container->m_successBit = container->m_parentTask->m_setupResult == SetupResult::StopWithSuccess; - } - } - continueContainer(container); - return; - } - - const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; - node->m_task.reset(handler.m_createHandler()); - node->m_setupResult = handler.m_setupHandler - ? invokeHandler(node->m_parentIteration, handler.m_setupHandler, *node->m_task.get()) - : SetupResult::Continue; - if (node->m_setupResult != SetupResult::Continue) { - if (node->m_parentIteration->m_isProgressive) - advanceProgress(1); - node->m_parentIteration->removeChild(node.get()); - return; - } - QObject::connect(node->m_task.get(), &TaskInterface::done, - q, [this, node](DoneResult doneResult) { - const bool result = invokeTaskDoneHandler(node.get(), toDoneWith(doneResult)); - node->m_setupResult = toSetupResult(result); - QObject::disconnect(node->m_task.get(), &TaskInterface::done, q, nullptr); - node->m_task.release()->deleteLater(); - RuntimeIteration *parentIteration = node->m_parentIteration; - if (parentIteration->m_container->isStarting()) - return; - - parentIteration->removeChild(node.get()); - childDone(parentIteration, result); - bumpAsyncCount(); - }); - node->m_task->start(); -} - -void TaskTreePrivate::stopTask(RuntimeTask *node) -{ - if (!node->m_task) { - if (!node->m_container) - return; - stopContainer(&*node->m_container); - node->m_container->updateSuccessBit(false); - invokeDoneHandler(&*node->m_container, DoneWith::Cancel); - return; - } - - invokeTaskDoneHandler(node, DoneWith::Cancel); - node->m_task.reset(); -} - -bool TaskTreePrivate::invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith) -{ - DoneResult result = toDoneResult(doneWith); - const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; - if (handler.m_doneHandler && shouldCall(handler.m_callDoneIf, doneWith)) { - result = invokeHandler(node->m_parentIteration, - handler.m_doneHandler, *node->m_task.get(), doneWith); - } - if (node->m_parentIteration->m_isProgressive) - advanceProgress(1); - return result == DoneResult::Success; -} - -/*! - \class Tasking::TaskTree - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief The TaskTree class runs an async task tree structure defined in a declarative way. - \reentrant - - Use the Tasking namespace to build extensible, declarative task tree - structures that contain possibly asynchronous tasks, such as QProcess, - NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you - to create a sophisticated mixture of a parallel or sequential flow of tasks - in the form of a tree and to run it any time later. - - \section1 Root Element and Tasks - - The TaskTree has a mandatory Group root element, which may contain - any number of tasks of various types, such as QProcessTask, NetworkQueryTask, - or ConcurrentCallTask<ReturnType>: - - \code - using namespace Tasking; - - const Group root { - QProcessTask(...), - NetworkQueryTask(...), - ConcurrentCallTask<int>(...) - }; - - TaskTree *taskTree = new TaskTree(root); - connect(taskTree, &TaskTree::done, ...); // finish handler - taskTree->start(); - \endcode - - The task tree above has a top level element of the Group type that contains - tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type. - After taskTree->start() is called, the tasks are run in a chain, starting - with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask - task is started. Finally, when the network task finishes successfully, the - ConcurrentCallTask<int> task is started. - - When the last running task finishes with success, the task tree is considered - to have run successfully and the done() signal is emitted with DoneWith::Success. - When a task finishes with an error, the execution of the task tree is stopped - and the remaining tasks are skipped. The task tree finishes with an error and - sends the TaskTree::done() signal with DoneWith::Error. - - \section1 Groups - - The parent of the Group sees it as a single task. Like other tasks, - the group can be started and it can finish with success or an error. - The Group elements can be nested to create a tree structure: - - \code - const Group root { - Group { - parallel, - QProcessTask(...), - ConcurrentCallTask<int>(...) - }, - NetworkQueryTask(...) - }; - \endcode - - The example above differs from the first example in that the root element has - a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a - sibling element of the NetworkQueryTask in the root. The subgroup contains an - additional \e parallel element that instructs its Group to execute its tasks - in parallel. - - So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start - immediately and run in parallel. Since the root group doesn't contain a - \e parallel element, its direct child tasks are run in sequence. Thus, the - NetworkQueryTask starts when the whole subgroup finishes. The group is - considered as finished when all its tasks have finished. The order in which - the tasks finish is not relevant. - - So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the - following scenarios can take place: - - \table - \header - \li Scenario 1 - \li Scenario 2 - \row - \li Root Group starts - \li Root Group starts - \row - \li Sub Group starts - \li Sub Group starts - \row - \li QProcessTask starts - \li QProcessTask starts - \row - \li ConcurrentCallTask<int> starts - \li ConcurrentCallTask<int> starts - \row - \li ... - \li ... - \row - \li \b {QProcessTask finishes} - \li \b {ConcurrentCallTask<int> finishes} - \row - \li ... - \li ... - \row - \li \b {ConcurrentCallTask<int> finishes} - \li \b {QProcessTask finishes} - \row - \li Sub Group finishes - \li Sub Group finishes - \row - \li NetworkQueryTask starts - \li NetworkQueryTask starts - \row - \li ... - \li ... - \row - \li NetworkQueryTask finishes - \li NetworkQueryTask finishes - \row - \li Root Group finishes - \li Root Group finishes - \endtable - - The differences between the scenarios are marked with bold. Three dots mean - that an unspecified amount of time passes between previous and next events - (a task or tasks continue to run). No dots between events - means that they occur synchronously. - - The presented scenarios assume that all tasks run successfully. If a task - fails during execution, the task tree finishes with an error. In particular, - when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed, - the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error, - the NetworkQueryTask is skipped, and the tree finishes with an error. - - \section1 Task Types - - Each task type is associated with its corresponding task class that executes - the task. For example, a QProcessTask inside a task tree is associated with - the QProcess class that executes the process. The associated objects are - automatically created, started, and destructed exclusively by the task tree - at the appropriate time. - - If a root group consists of five sequential QProcessTask tasks, and the task tree - executes the group, it creates an instance of QProcess for the first - QProcessTask and starts it. If the QProcess instance finishes successfully, - the task tree destructs it and creates a new QProcess instance for the - second QProcessTask, and so on. If the first task finishes with an error, the task - tree stops creating QProcess instances, and the root group finishes with an - error. - - The following table shows examples of task types and their corresponding task - classes: - - \table - \header - \li Task Type (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li QProcessTask - \li QProcess - \li Starts process. - \row - \li ConcurrentCallTask<ReturnType> - \li Tasking::ConcurrentCall<ReturnType> - \li Starts asynchronous task, runs in separate thread. - \row - \li TaskTreeTask - \li Tasking::TaskTree - \li Starts nested task tree. - \row - \li NetworkQueryTask - \li NetworkQuery - \li Starts network download. - \endtable - - \section1 Task Handlers - - Use Task handlers to set up a task for execution and to enable reading - the output data from the task when it finishes with success or an error. - - \section2 Task's Start Handler - - When a corresponding task class object is created and before it's started, - the task tree invokes an optionally user-provided setup handler. The setup - handler should always take a \e reference to the associated task class object: - - \code - const auto onSetup = [](QProcess &process) { - process.setProgram("sleep"); - process.setArguments({"3"}); - }; - const Group root { - QProcessTask(onSetup) - }; - \endcode - - You can modify the passed QProcess in the setup handler, so that the task - tree can start the process according to your configuration. - You should not call \c {process.start();} in the setup handler, - as the task tree calls it when needed. The setup handler is optional. When used, - it must be the first argument of the task's constructor. - - Optionally, the setup handler may return a SetupResult. The returned - SetupResult influences the further start behavior of a given task. The - possible values are: - - \table - \header - \li SetupResult Value - \li Brief Description - \row - \li Continue - \li The task will be started normally. This is the default behavior when the - setup handler doesn't return SetupResult (that is, its return type is - void). - \row - \li StopWithSuccess - \li The task won't be started and it will report success to its parent. - \row - \li StopWithError - \li The task won't be started and it will report an error to its parent. - \endtable - - This is useful for running a task only when a condition is met and the data - needed to evaluate this condition is not known until previously started tasks - finish. In this way, the setup handler dynamically decides whether to start the - corresponding task normally or skip it and report success or an error. - For more information about inter-task data exchange, see \l Storage. - - \section2 Task's Done Handler - - When a running task finishes, the task tree invokes an optionally provided done handler. - The handler should take a \c const \e reference to the associated task class object: - - \code - const auto onSetup = [](QProcess &process) { - process.setProgram("sleep"); - process.setArguments({"3"}); - }; - const auto onDone = [](const QProcess &process, DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "Success" << process.cleanedStdOut(); - else - qDebug() << "Failure" << process.cleanedStdErr(); - }; - const Group root { - QProcessTask(onSetup, onDone) - }; - \endcode - - The done handler may collect output data from QProcess, and store it - for further processing or perform additional actions. - - \note If the task setup handler returns StopWithSuccess or StopWithError, - the done handler is not invoked. - - \section1 Group Handlers - - Similarly to task handlers, group handlers enable you to set up a group to - execute and to apply more actions when the whole group finishes with - success or an error. - - \section2 Group's Start Handler - - The task tree invokes the group start handler before it starts the child - tasks. The group handler doesn't take any arguments: - - \code - const auto onSetup = [] { - qDebug() << "Entering the group"; - }; - const Group root { - onGroupSetup(onSetup), - QProcessTask(...) - }; - \endcode - - The group setup handler is optional. To define a group setup handler, add an - onGroupSetup() element to a group. The argument of onGroupSetup() is a user - handler. If you add more than one onGroupSetup() element to a group, an assert - is triggered at runtime that includes an error message. - - Like the task's start handler, the group start handler may return SetupResult. - The returned SetupResult value affects the start behavior of the - whole group. If you do not specify a group start handler or its return type - is void, the default group's action is SetupResult::Continue, so that all - tasks are started normally. Otherwise, when the start handler returns - SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not - started (they are skipped) and the group itself reports success or failure, - depending on the returned value, respectively. - - \code - const Group root { - onGroupSetup([] { qDebug() << "Root setup"; }), - Group { - onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }), - QProcessTask(...) // Process 1 - }, - Group { - onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }), - QProcessTask(...) // Process 2 - }, - Group { - onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }), - QProcessTask(...) // Process 3 - }, - QProcessTask(...) // Process 4 - }; - \endcode - - In the above example, all subgroups of a root group define their setup handlers. - The following scenario assumes that all started processes finish with success: - - \table - \header - \li Scenario - \li Comment - \row - \li Root Group starts - \li Doesn't return SetupResult, so its tasks are executed. - \row - \li Group 1 starts - \li Returns Continue, so its tasks are executed. - \row - \li Process 1 starts - \li - \row - \li ... - \li ... - \row - \li Process 1 finishes (success) - \li - \row - \li Group 1 finishes (success) - \li - \row - \li Group 2 starts - \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports - success. - \row - \li Group 2 finishes (success) - \li - \row - \li Group 3 starts - \li Returns StopWithError, so Process 3 is skipped and Group 3 reports - an error. - \row - \li Group 3 finishes (error) - \li - \row - \li Root Group finishes (error) - \li Group 3, which is a direct child of the root group, finished with an - error, so the root group stops executing, skips Process 4, which has - not started yet, and reports an error. - \endtable - - \section2 Groups's Done Handler - - A Group's done handler is executed after the successful or failed execution of its tasks. - The final value reported by the group depends on its \l {Workflow Policy}. - The handler can apply other necessary actions. - The done handler is defined inside the onGroupDone() element of a group. - It may take the optional DoneWith argument, indicating the successful or failed execution: - - \code - const Group root { - onGroupSetup([] { qDebug() << "Root setup"; }), - QProcessTask(...), - onGroupDone([](DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "Root finished with success"; - else - qDebug() << "Root finished with an error"; - }) - }; - \endcode - - The group done handler is optional. If you add more than one onGroupDone() to a group, - an assert is triggered at runtime that includes an error message. - - \note Even if the group setup handler returns StopWithSuccess or StopWithError, - the group's done handler is invoked. This behavior differs from that of task done handler - and might change in the future. - - \section1 Other Group Elements - - A group can contain other elements that describe the processing flow, such as - the execution mode or workflow policy. It can also contain storage elements - that are responsible for collecting and sharing custom common data gathered - during group execution. - - \section2 Execution Mode - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. The most common execution modes are \l sequential and - \l parallel. It's also possible to specify the limit of tasks running - in parallel by using the parallelLimit() function. - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group, the child group runs its tasks - according to its own execution mode. - - \section2 Workflow Policy - - The workflow policy element in a Group specifies how the group should behave - when any of its \e direct child's tasks finish. For a detailed description of possible - policies, refer to WorkflowPolicy. - - If a child of a group is also a group, the child group runs its tasks - according to its own workflow policy. - - \section2 Storage - - Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks. - Especially, in the sequential execution mode, when a task needs data from another, - already finished task, before it can start. For example, a task tree that copies data by reading - it from a source and writing it to a destination might look as follows: - - \code - static QByteArray load(const QString &fileName) { ... } - static void save(const QString &fileName, const QByteArray &array) { ... } - - static Group copyRecipe(const QString &source, const QString &destination) - { - struct CopyStorage { // [1] custom inter-task struct - QByteArray content; // [2] custom inter-task data - }; - - // [3] instance of custom inter-task struct manageable by task tree - const Storage<CopyStorage> storage; - - const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) { - async.setConcurrentCallData(&load, source); - }; - // [4] runtime: task tree activates the instance from [7] before invoking handler - const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) { - storage->content = async.result(); // [5] loader stores the result in storage - }; - - // [4] runtime: task tree activates the instance from [7] before invoking handler - const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) { - const QByteArray content = storage->content; // [6] saver takes data from storage - async.setConcurrentCallData(&save, destination, content); - }; - const auto onSaverDone = [](const ConcurrentCall<void> &async) { - qDebug() << "Save done successfully"; - }; - - const Group root { - // [7] runtime: task tree creates an instance of CopyStorage when root is entered - storage, - ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success), - ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success) - }; - return root; - } - - const QString source = ...; - const QString destination = ...; - TaskTree taskTree(copyRecipe(source, destination)); - connect(&taskTree, &TaskTree::done, - &taskTree, [](DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "The copying finished successfully."; - }); - tasktree.start(); - \endcode - - In the example above, the inter-task data consists of a QByteArray content - variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader - finishes successfully, it stores the data in a \c CopyStorage::content - variable [5]. The saver then uses the variable to configure the saving task [6]. - - To enable a task tree to manage the \c CopyStorage struct, an instance of - \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is - inserted as the group's child item [7], an instance of the \c CopyStorage struct is - created dynamically when the task tree enters this group. When the task - tree leaves this group, the existing instance of the \c CopyStorage struct is - destructed as it's no longer needed. - - If several task trees holding a copy of the common - \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously - (including the case when the task trees are run in different threads), - each task tree contains its own copy of the \c CopyStorage struct. - - You can access \c CopyStorage from any handler in the group with a storage object. - This includes all handlers of all descendant tasks of the group with - a storage object. To access the custom struct in a handler, pass the - copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler - (for example, in a lambda capture) [4]. - - When the task tree invokes a handler in a subtree containing the storage [7], - the task tree activates its own \c CopyStorage instance inside the - \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct - may be accessed only from within the handler body. To access the currently active - \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the - \l {Tasking::Storage::operator->()} {Storage::operator->()}, - \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method. - - The following list summarizes how to employ a Storage object into the task - tree: - \list 1 - \li Define the custom structure \c MyStorage with custom data [1], [2] - \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3] - \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4] - \li Access the \c MyStorage instance in handlers [5], [6] - \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7] - \endlist - - \section1 TaskTree class - - TaskTree executes the tree structure of asynchronous tasks according to the - recipe described by the Group root element. - - As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask - element into another Group element. - - TaskTree reports progress of completed tasks when running. The progress value - is increased when a task finishes or is skipped or canceled. - When TaskTree is finished and the TaskTree::done() signal is emitted, - the current value of the progress equals the maximum progress value. - Maximum progress equals the total number of asynchronous tasks in a tree. - A nested TaskTree is counted as a single task, and its child tasks are not - counted in the top level tree. Groups themselves are not counted as tasks, - but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous, - so they are not counted as tasks. - - To set additional initial data for the running tree, modify the storage - instances in a tree when it creates them by installing a storage setup - handler: - - \code - Storage<CopyStorage> storage; - const Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto initStorage = [](CopyStorage &storage) { - storage.content = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree creates a \c CopyStorage instance, and before any - handler inside a tree is called, the task tree calls the initStorage handler, - to enable setting up initial data of the storage, unique to this particular - run of taskTree. - - Similarly, to collect some additional result data from the running tree, - read it from storage instances in the tree when they are about to be - destroyed. To do this, install a storage done handler: - - \code - Storage<CopyStorage> storage; - const Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto collectStorage = [](const CopyStorage &storage) { - qDebug() << "final content" << storage.content; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to destroy a \c CopyStorage instance, the - task tree calls the collectStorage handler, to enable reading the final data - from the storage, unique to this particular run of taskTree. - - \section1 Task Adapters - - To extend a TaskTree with a new task type, implement a simple adapter class - derived from the TaskAdapter class template. The following class is an - adapter for a single shot timer, which may be considered as a new asynchronous task: - - \code - class TimerTaskAdapter : public TaskAdapter<QTimer> - { - public: - TimerTaskAdapter() { - task()->setSingleShot(true); - task()->setInterval(1000); - connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); }); - } - private: - void start() final { task()->start(); } - }; - - using TimerTask = CustomTask<TimerTaskAdapter>; - \endcode - - You must derive the custom adapter from the TaskAdapter class template - instantiated with a template parameter of the class implementing a running - task. The code above uses QTimer to run the task. This class appears - later as an argument to the task's handlers. The instance of this class - parameter automatically becomes a member of the TaskAdapter template, and is - accessible through the TaskAdapter::task() method. The constructor - of \c TimerTaskAdapter initially configures the QTimer object and connects - to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter - emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that - the task finished successfully. If it emits TaskInterface::done(DoneResult::Error), - the task finished with an error. - The TaskAdapter::start() method starts the timer. - - To make QTimer accessible inside TaskTree under the \c TimerTask name, - define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>. - \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter. - - The new task type is now registered, and you can use it in TaskTree: - - \code - const auto onSetup = [](QTimer &task) { task.setInterval(2000); }; - const auto onDone = [] { qDebug() << "timer triggered"; }; - const Group root { - TimerTask(onSetup, onDone) - }; - \endcode - - When a task tree containing the root from the above example is started, it - prints a debug message within two seconds and then finishes successfully. - - \note The class implementing the running task should have a default constructor, - and objects of this class should be freely destructible. It should be allowed - to destroy a running object, preferably without waiting for the running task - to finish (that is, safe non-blocking destructor of a running task). - To achieve a non-blocking destruction of a task that has a blocking destructor, - consider using the optional \c Deleter template parameter of the TaskAdapter. -*/ - -/*! - Constructs an empty task tree. Use setRecipe() to pass a declarative description - on how the task tree should execute the tasks and how it should handle the finished tasks. - - Starting an empty task tree is no-op and the relevant warning message is issued. - - \sa setRecipe(), start() -*/ -TaskTree::TaskTree() - : d(new TaskTreePrivate(this)) -{} - -/*! - \overload - - Constructs a task tree with a given \a recipe. After the task tree is started, - it executes the tasks contained inside the \a recipe and - handles finished tasks according to the passed description. - - \sa setRecipe(), start() -*/ -TaskTree::TaskTree(const Group &recipe) : TaskTree() -{ - setRecipe(recipe); -} - -/*! - Destroys the task tree. - - When the task tree is running while being destructed, it cancels all the running tasks - immediately. In this case, no handlers are called, not even the groups' and - tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any - signals from the destructor, not even done() or progressValueChanged() signals. - This behavior may always be relied on. - It is completely safe to destruct the running task tree. - - It's a usual pattern to destruct the running task tree. - It's guaranteed that the destruction will run quickly, without having to wait for - the currently running tasks to finish, provided that the used tasks implement - their destructors in a non-blocking way. - - \note Do not call the destructor directly from any of the running task's handlers - or task tree's signals. In these cases, use \l deleteLater() instead. - - \sa cancel() -*/ -TaskTree::~TaskTree() -{ - QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " - "one of its handlers will lead to a crash!")); - // TODO: delete storages explicitly here? - delete d; -} - -/*! - Sets a given \a recipe for the task tree. After the task tree is started, - it executes the tasks contained inside the \a recipe and - handles finished tasks according to the passed description. - - \note When called for a running task tree, the call is ignored. - - \sa TaskTree(const Tasking::Group &recipe), start() -*/ -void TaskTree::setRecipe(const Group &recipe) -{ - QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the" - "TaskTree handlers, ignoring..."); return); - // TODO: Should we clear the m_storageHandlers, too? - d->m_storages.clear(); - d->m_root.emplace(d, recipe); -} - -/*! - Starts the task tree. - - Use setRecipe() or the constructor to set the declarative description according to which - the task tree will execute the contained tasks and handle finished tasks. - - When the task tree is empty, that is, constructed with a default constructor, - a call to \c start() is no-op and the relevant warning message is issued. - - Otherwise, when the task tree is already running, a call to \e start() is ignored and the - relevant warning message is issued. - - Otherwise, the task tree is started. - - The started task tree may finish synchronously, - for example when the main group's start handler returns SetupResult::StopWithError. - For this reason, the connection to the done signal should be established before calling - \c start(). Use isRunning() in order to detect whether the task tree is still running - after a call to \c start(). - - The task tree implementation relies on the running event loop. - Make sure you have a QEventLoop or QCoreApplication or one of its - subclasses running (or about to be run) when calling this method. - - \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel() -*/ -void TaskTree::start() -{ - QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" - "TaskTree handlers, ignoring..."); return); - d->start(); -} - -/*! - \fn void TaskTree::started() - - This signal is emitted when the task tree is started. The emission of this signal is - followed synchronously by the progressValueChanged() signal with an initial \c 0 value. - - \sa start(), done() -*/ - -/*! - \fn void TaskTree::done(DoneWith result) - - This signal is emitted when the task tree finished, passing the final \a result - of the execution. The task tree neither calls any handler, - nor emits any signal anymore after this signal was emitted. - - \note Do not delete the task tree directly from this signal's handler. - Use deleteLater() instead. - - \sa started() -*/ - -/*! - Cancels the execution of the running task tree. - - Cancels all the running tasks immediately. - All running tasks finish with an error, invoking their error handlers. - All running groups dispatch their handlers according to their workflow policies, - invoking their done handlers. The storages' onStorageDone() handlers are invoked, too. - The progressValueChanged() signals are also being sent. - This behavior may always be relied on. - - The \c cancel() function is executed synchronously, so that after a call to \c cancel() - all running tasks are finished and the tree is already canceled. - It's guaranteed that \c cancel() will run quickly, without any blocking wait for - the currently running tasks to finish, provided the used tasks implement their destructors - in a non-blocking way. - - When the task tree is empty, that is, constructed with a default constructor, - a call to \c cancel() is no-op and the relevant warning message is issued. - - Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored. - - \note Do not call this function directly from any of the running task's handlers - or task tree's signals. - - \sa ~TaskTree() -*/ -void TaskTree::cancel() -{ - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the" - "TaskTree handlers, ignoring..."); return); - d->stop(); -} - -/*! - Returns \c true if the task tree is currently running; otherwise returns \c false. - - \sa start(), cancel() -*/ -bool TaskTree::isRunning() const -{ - return bool(d->m_runtimeRoot); -} - -/*! - Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree. - - Returns DoneWith::Success if the task tree finished successfully; - otherwise returns DoneWith::Error. - - \note Avoid using this method from the main thread. Use asynchronous start() instead. - This method is to be used in non-main threads or in auto tests. - - \sa start() -*/ -DoneWith TaskTree::runBlocking() -{ - QPromise<void> dummy; - dummy.start(); - return runBlocking(dummy.future()); -} - -/*! - \overload runBlocking() - - The passed \a future is used for listening to the cancel event. - When the task tree is canceled, this method cancels the passed \a future. -*/ -DoneWith TaskTree::runBlocking(const QFuture<void> &future) -{ - if (future.isCanceled()) - return DoneWith::Cancel; - - DoneWith doneWith = DoneWith::Cancel; - QEventLoop loop; - connect(this, &TaskTree::done, &loop, [&loop, &doneWith](DoneWith result) { - doneWith = result; - // Otherwise, the tasks from inside the running tree that were deleteLater() - // will be leaked. Refer to the QObject::deleteLater() docs. - QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection); - }); - QFutureWatcher<void> watcher; - connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::cancel); - watcher.setFuture(future); - - QTimer::singleShot(0, this, &TaskTree::start); - - loop.exec(QEventLoop::ExcludeUserInputEvents); - if (doneWith == DoneWith::Cancel) { - auto nonConstFuture = future; - nonConstFuture.cancel(); - } - return doneWith; -} - -/*! - Constructs a temporary task tree using the passed \a recipe and runs it blocking. - - Returns DoneWith::Success if the task tree finished successfully; - otherwise returns DoneWith::Error. - - \note Avoid using this method from the main thread. Use asynchronous start() instead. - This method is to be used in non-main threads or in auto tests. - - \sa start() -*/ -DoneWith TaskTree::runBlocking(const Group &recipe) -{ - QPromise<void> dummy; - dummy.start(); - return TaskTree::runBlocking(recipe, dummy.future()); -} - -/*! - \overload runBlocking(const Group &recipe) - - The passed \a future is used for listening to the cancel event. - When the task tree is canceled, this method cancels the passed \a future. -*/ -DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future) -{ - TaskTree taskTree(recipe); - return taskTree.runBlocking(future); -} - -/*! - Returns the current real count of asynchronous chains of invocations. - - The returned value indicates how many times the control returns to the caller's - event loop while the task tree is running. Initially, this value is 0. - If the execution of the task tree finishes fully synchronously, this value remains 0. - If the task tree contains any asynchronous tasks that are successfully started during - a call to start(), this value is bumped to 1 just before the call to start() finishes. - Later, when any asynchronous task finishes and any possible continuations are started, - this value is bumped again. The bumping continues until the task tree finishes. - When the task tree emits the done() signal, the bumping stops. - The asyncCountChanged() signal is emitted on every bump of this value. - - \sa asyncCountChanged() -*/ -int TaskTree::asyncCount() const -{ - return d->m_asyncCount; -} - -/*! - \fn void TaskTree::asyncCountChanged(int count) - - This signal is emitted when the running task tree is about to return control to the caller's - event loop. When the task tree is started, this signal is emitted with \a count value of 0, - and emitted later on every asyncCount() value bump with an updated \a count value. - Every signal sent (except the initial one with the value of 0) guarantees that the task tree - is still running asynchronously after the emission. - - \sa asyncCount() -*/ - -/*! - Returns the number of asynchronous tasks contained in the stored recipe. - - \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks. - \note Any task or group that was set up using withTimeout() increases the total number of - tasks by \c 1. - - \sa setRecipe(), progressMaximum() -*/ -int TaskTree::taskCount() const -{ - return d->m_root ? d->m_root->taskCount() : 0; -} - -/*! - \fn void TaskTree::progressValueChanged(int value) - - This signal is emitted when the running task tree finished, canceled, or skipped some tasks. - The \a value gives the current total number of finished, canceled or skipped tasks. - When the task tree is started, and after the started() signal was emitted, - this signal is emitted with an initial \a value of \c 0. - When the task tree is about to finish, and before the done() signal is emitted, - this signal is emitted with the final \a value of progressMaximum(). - - \sa progressValue(), progressMaximum() -*/ - -/*! - \fn int TaskTree::progressMaximum() const - - Returns the maximum progressValue(). - - \note Currently, it's the same as taskCount(). This might change in the future. - - \sa progressValue() -*/ - -/*! - Returns the current progress value, which is between the \c 0 and progressMaximum(). - - The returned number indicates how many tasks have been already finished, canceled, or skipped - while the task tree is running. - When the task tree is started, this number is set to \c 0. - When the task tree is finished, this number always equals progressMaximum(). - - \sa progressMaximum(), progressValueChanged() -*/ -int TaskTree::progressValue() const -{ - return d->m_progressValue; -} - -/*! - \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) - - Installs a storage setup \a handler for the \a storage to pass the initial data - dynamically to the running task tree. - - The \c StorageHandler takes a \e reference to the \c StorageStruct instance: - - \code - static void save(const QString &fileName, const QByteArray &array) { ... } - - Storage<QByteArray> storage; - - const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) { - concurrent.setConcurrentCallData(&save, "foo.txt", *storage); - }; - - const Group root { - storage, - ConcurrentCallTask(onSaverSetup) - }; - - TaskTree taskTree(root); - auto initStorage = [](QByteArray &storage){ - storage = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree enters a Group where the \a storage is placed in, - it creates a \c StorageStruct instance, ready to be used inside this group. - Just after the \c StorageStruct instance is created, and before any handler of this group - is called, the task tree invokes the passed \a handler. This enables setting up - initial content for the given storage dynamically. Later, when any group's handler is invoked, - the task tree activates the created and initialized storage, so that it's available inside - any group's handler. - - \sa onStorageDone() -*/ - -/*! - \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) - - Installs a storage done \a handler for the \a storage to retrieve the final data - dynamically from the running task tree. - - The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance: - - \code - static QByteArray load(const QString &fileName) { ... } - - Storage<QByteArray> storage; - - const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) { - concurrent.setConcurrentCallData(&load, "foo.txt"); - }; - const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) { - *storage = concurrent.result(); - }; - - const Group root { - storage, - ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success) - }; - - TaskTree taskTree(root); - auto collectStorage = [](const QByteArray &storage){ - qDebug() << "final content" << storage; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to leave a Group where the \a storage is placed in, - it destructs a \c StorageStruct instance. - Just before the \c StorageStruct instance is destructed, and after all possible handlers from - this group were called, the task tree invokes the passed \a handler. This enables reading - the final content of the given storage dynamically and processing it further outside of - the task tree. - - This handler is called also when the running tree is canceled. However, it's not called - when the running tree is destructed. - - \sa onStorageSetup() -*/ - -void TaskTree::setupStorageHandler(const StorageBase &storage, - const StorageBase::StorageHandler &setupHandler, - const StorageBase::StorageHandler &doneHandler) -{ - auto it = d->m_storageHandlers.find(storage); - if (it == d->m_storageHandlers.end()) { - d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); - return; - } - if (setupHandler) { - QT_ASSERT(!it->m_setupHandler, - qWarning("The storage has its setup handler defined, overriding...")); - it->m_setupHandler = setupHandler; - } - if (doneHandler) { - QT_ASSERT(!it->m_doneHandler, - qWarning("The storage has its done handler defined, overriding...")); - it->m_doneHandler = doneHandler; - } -} - -TaskTreeTaskAdapter::TaskTreeTaskAdapter() -{ - connect(task(), &TaskTree::done, this, - [this](DoneWith result) { emit done(toDoneResult(result)); }); -} - -void TaskTreeTaskAdapter::start() -{ - task()->start(); -} - -using TimeoutCallback = std::function<void()>; - -struct TimerData -{ - system_clock::time_point m_deadline; - QPointer<QObject> m_context; - TimeoutCallback m_callback; -}; - -struct TimerThreadData -{ - Q_DISABLE_COPY_MOVE(TimerThreadData) - - TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20 - QHash<int, TimerData> m_timerIdToTimerData = {}; - QMap<system_clock::time_point, QList<int>> m_deadlineToTimerId = {}; - int m_timerIdCounter = 0; -}; - -// Please note the thread_local keyword below guarantees a separate instance per thread. -static thread_local TimerThreadData s_threadTimerData = {}; - -static void removeTimerId(int timerId) -{ - const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(timerId); - QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(), - qWarning("Removing active timerId failed."); return); - - const system_clock::time_point deadline = it->m_deadline; - s_threadTimerData.m_timerIdToTimerData.erase(it); - - QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline]; - const int removedCount = ids.removeAll(timerId); - QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return); - if (ids.isEmpty()) - s_threadTimerData.m_deadlineToTimerId.remove(deadline); -} - -static void handleTimeout(int timerId) -{ - const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(timerId); - if (itData == s_threadTimerData.m_timerIdToTimerData.cend()) - return; // The timer was already activated. - - const auto deadline = itData->m_deadline; - while (true) { - auto itMap = s_threadTimerData.m_deadlineToTimerId.begin(); - if (itMap == s_threadTimerData.m_deadlineToTimerId.end()) - return; - - if (itMap.key() > deadline) - return; - - std::optional<TimerData> timerData; - QList<int> &idList = *itMap; - if (!idList.isEmpty()) { - const int first = idList.first(); - idList.removeFirst(); - - const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(first); - if (it != s_threadTimerData.m_timerIdToTimerData.cend()) { - timerData = it.value(); - s_threadTimerData.m_timerIdToTimerData.erase(it); - } else { - QT_CHECK(false); - } - } else { - QT_CHECK(false); - } - - if (idList.isEmpty()) - s_threadTimerData.m_deadlineToTimerId.erase(itMap); - if (timerData && timerData->m_context) - timerData->m_callback(); - } -} - -static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback) -{ - const int timerId = ++s_threadTimerData.m_timerIdCounter; - const system_clock::time_point deadline = system_clock::now() + timeout; - QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); }); - s_threadTimerData.m_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback}); - s_threadTimerData.m_deadlineToTimerId[deadline].append(timerId); - return timerId; -} - -TimeoutTaskAdapter::TimeoutTaskAdapter() -{ - *task() = milliseconds::zero(); -} - -TimeoutTaskAdapter::~TimeoutTaskAdapter() -{ - if (m_timerId) - removeTimerId(*m_timerId); -} - -void TimeoutTaskAdapter::start() -{ - m_timerId = scheduleTimeout(*task(), this, [this] { - m_timerId.reset(); - emit done(DoneResult::Success); - }); -} - -ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, DoneResult result) -{ - return TimeoutTask([timeout](std::chrono::milliseconds &t) { t = timeout; }, result); -} - -/*! - \typealias Tasking::TaskTreeTask - - Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task. -*/ - -/*! - \typealias Tasking::TimeoutTask - - Type alias for the CustomTask, to be used inside recipes, associated with the - \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the - timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is, - the TimeoutTask finishes as soon as the control returns to the running event loop. - - Example usage: - - \code - using namespace std::chrono; - using namespace std::chrono_literals; - - const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; } - const auto onDone = [] { qDebug() << "Timed out."; } - - const Group root { - Timeout(onSetup, onDone) - }; - \endcode -*/ - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tasktree.h b/src/assets/downloader/tasking/tasktree.h deleted file mode 100644 index d46bd8eee3e..00000000000 --- a/src/assets/downloader/tasking/tasktree.h +++ /dev/null @@ -1,757 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TASKTREE_H -#define TASKING_TASKTREE_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include <QtCore/QList> -#include <QtCore/QObject> - -#include <memory> - -QT_BEGIN_NAMESPACE -template <class T> -class QFuture; - -namespace Tasking { - -class Do; -class For; -class Group; -class GroupItem; -using GroupItems = QList<GroupItem>; - -Q_NAMESPACE_EXPORT(TASKING_EXPORT) - -// WorkflowPolicy: -// 1. When all children finished with success -> report success, otherwise: -// a) Report error on first error and stop executing other children (including their subtree). -// b) On first error - continue executing all children and report error afterwards. -// 2. When all children finished with error -> report error, otherwise: -// a) Report success on first success and stop executing other children (including their subtree). -// b) On first success - continue executing all children and report success afterwards. -// 3. Stops on first finished child. In sequential mode it will never run other children then the first one. -// Useful only in parallel mode. -// 4. Always run all children, let them finish, ignore their results and report success afterwards. -// 5. Always run all children, let them finish, ignore their results and report error afterwards. - -enum class WorkflowPolicy -{ - StopOnError, // 1a - Reports error on first child error, otherwise success (if all children were success). - ContinueOnError, // 1b - The same, but children execution continues. Reports success when no children. - StopOnSuccess, // 2a - Reports success on first child success, otherwise error (if all children were error). - ContinueOnSuccess, // 2b - The same, but children execution continues. Reports error when no children. - StopOnSuccessOrError, // 3 - Stops on first finished child and report its result. - FinishAllAndSuccess, // 4 - Reports success after all children finished. - FinishAllAndError // 5 - Reports error after all children finished. -}; -Q_ENUM_NS(WorkflowPolicy) - -enum class SetupResult -{ - Continue, - StopWithSuccess, - StopWithError -}; -Q_ENUM_NS(SetupResult) - -enum class DoneResult -{ - Success, - Error -}; -Q_ENUM_NS(DoneResult) - -enum class DoneWith -{ - Success, - Error, - Cancel -}; -Q_ENUM_NS(DoneWith) - -enum class CallDoneIf -{ - SuccessOrError, - Success, - Error -}; -Q_ENUM_NS(CallDoneIf) - -TASKING_EXPORT DoneResult toDoneResult(bool success); - -class LoopData; -class StorageData; -class TaskTreePrivate; - -class TASKING_EXPORT TaskInterface : public QObject -{ - Q_OBJECT - -Q_SIGNALS: - void done(DoneResult result); - -private: - template <typename Task, typename Deleter> friend class TaskAdapter; - friend class TaskTreePrivate; - TaskInterface() = default; -#ifdef Q_QDOC -protected: -#endif - virtual void start() = 0; -}; - -class TASKING_EXPORT Loop -{ -public: - using Condition = std::function<bool(int)>; // Takes iteration, called prior to each iteration. - using ValueGetter = std::function<const void *(int)>; // Takes iteration, returns ptr to ref. - - int iteration() const; - -protected: - Loop(); // LoopForever - Loop(int count, const ValueGetter &valueGetter = {}); // LoopRepeat, LoopList - Loop(const Condition &condition); // LoopUntil - - const void *valuePtr() const; - -private: - friend class ExecutionContextActivator; - friend class TaskTreePrivate; - std::shared_ptr<LoopData> m_loopData; -}; - -class TASKING_EXPORT LoopForever final : public Loop -{ -public: - LoopForever() : Loop() {} -}; - -class TASKING_EXPORT LoopRepeat final : public Loop -{ -public: - LoopRepeat(int count) : Loop(count) {} -}; - -class TASKING_EXPORT LoopUntil final : public Loop -{ -public: - LoopUntil(const Condition &condition) : Loop(condition) {} -}; - -template <typename T> -class LoopList final : public Loop -{ -public: - LoopList(const QList<T> &list) : Loop(list.size(), [list](int i) { return &list.at(i); }) {} - const T *operator->() const { return static_cast<const T *>(valuePtr()); } - const T &operator*() const { return *static_cast<const T *>(valuePtr()); } -}; - -class TASKING_EXPORT StorageBase -{ -private: - using StorageConstructor = std::function<void *(void)>; - using StorageDestructor = std::function<void(void *)>; - using StorageHandler = std::function<void(void *)>; - - StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor); - - void *activeStorageVoid() const; - - friend bool operator==(const StorageBase &first, const StorageBase &second) - { return first.m_storageData == second.m_storageData; } - - friend bool operator!=(const StorageBase &first, const StorageBase &second) - { return first.m_storageData != second.m_storageData; } - - friend size_t qHash(const StorageBase &storage, uint seed = 0) - { return size_t(storage.m_storageData.get()) ^ seed; } - - std::shared_ptr<StorageData> m_storageData; - - template <typename StorageStruct> friend class Storage; - friend class ExecutionContextActivator; - friend class StorageData; - friend class RuntimeContainer; - friend class TaskTree; - friend class TaskTreePrivate; -}; - -template <typename StorageStruct> -class Storage final : public StorageBase -{ -public: - Storage() : StorageBase(Storage::ctor(), Storage::dtor()) {} -#if __cplusplus >= 201803L // C++20: Allow pack expansion in lambda init-capture. - template <typename ...Args> - Storage(const Args &...args) - : StorageBase([...args = args] { return new StorageStruct(args...); }, Storage::dtor()) {} -#else // C++17 - template <typename ...Args> - Storage(const Args &...args) - : StorageBase([argsTuple = std::tuple(args...)] { - return std::apply([](const Args &...arguments) { return new StorageStruct(arguments...); }, argsTuple); - }, Storage::dtor()) {} -#endif - StorageStruct &operator*() const noexcept { return *activeStorage(); } - StorageStruct *operator->() const noexcept { return activeStorage(); } - StorageStruct *activeStorage() const { - return static_cast<StorageStruct *>(activeStorageVoid()); - } - -private: - static StorageConstructor ctor() { return [] { return new StorageStruct(); }; } - static StorageDestructor dtor() { - return [](void *storage) { delete static_cast<StorageStruct *>(storage); }; - } -}; - -class TASKING_EXPORT GroupItem -{ -public: - // Called when group entered, after group's storages are created - using GroupSetupHandler = std::function<SetupResult()>; - // Called when group done, before group's storages are deleted - using GroupDoneHandler = std::function<DoneResult(DoneWith)>; - - template <typename StorageStruct> - GroupItem(const Storage<StorageStruct> &storage) - : m_type(Type::Storage) - , m_storageList{storage} {} - - // TODO: Add tests. - GroupItem(const GroupItems &children) : m_type(Type::List) { addChildren(children); } - GroupItem(std::initializer_list<GroupItem> children) : m_type(Type::List) { addChildren(children); } - -protected: - GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {} - // Internal, provided by CustomTask - using InterfaceCreateHandler = std::function<TaskInterface *(void)>; - // Called prior to task start, just after createHandler - using InterfaceSetupHandler = std::function<SetupResult(TaskInterface &)>; - // Called on task done, just before deleteLater - using InterfaceDoneHandler = std::function<DoneResult(const TaskInterface &, DoneWith)>; - - struct TaskHandler { - InterfaceCreateHandler m_createHandler; - InterfaceSetupHandler m_setupHandler = {}; - InterfaceDoneHandler m_doneHandler = {}; - CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError; - }; - - struct GroupHandler { - GroupSetupHandler m_setupHandler; - GroupDoneHandler m_doneHandler = {}; - CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError; - }; - - struct GroupData { - GroupHandler m_groupHandler = {}; - std::optional<int> m_parallelLimit = {}; - std::optional<WorkflowPolicy> m_workflowPolicy = {}; - std::optional<Loop> m_loop = {}; - }; - - enum class Type { - List, - Group, - GroupData, - Storage, - TaskHandler - }; - - GroupItem() = default; - GroupItem(Type type) : m_type(type) { } - GroupItem(const GroupData &data) - : m_type(Type::GroupData) - , m_groupData(data) {} - GroupItem(const TaskHandler &handler) - : m_type(Type::TaskHandler) - , m_taskHandler(handler) {} - void addChildren(const GroupItems &children); - - static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); } - - // Checks if Function may be invoked with Args and if Function's return type is Result. - template <typename Result, typename Function, typename ...Args, - typename DecayedFunction = std::decay_t<Function>> - static constexpr bool isInvocable() - { - // Note, that std::is_invocable_r_v doesn't check Result type properly. - if constexpr (std::is_invocable_r_v<Result, DecayedFunction, Args...>) - return std::is_same_v<Result, std::invoke_result_t<DecayedFunction, Args...>>; - return false; - } - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - friend class ContainerNode; - friend class TaskNode; - friend class TaskTreePrivate; - friend class ParallelLimitFunctor; - friend class WorkflowPolicyFunctor; - Type m_type = Type::Group; - GroupItems m_children; - GroupData m_groupData; - QList<StorageBase> m_storageList; - TaskHandler m_taskHandler; -}; - -class TASKING_EXPORT ExecutableItem : public GroupItem -{ -public: - Group withTimeout(std::chrono::milliseconds timeout, - const std::function<void()> &handler = {}) const; - Group withLog(const QString &logName) const; - template <typename SenderSignalPairGetter> - Group withCancel(SenderSignalPairGetter &&getter, std::initializer_list<GroupItem> postCancelRecipe = {}) const; - template <typename SenderSignalPairGetter> - Group withAccept(SenderSignalPairGetter &&getter) const; - -protected: - ExecutableItem() = default; - ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {} - -private: - TASKING_EXPORT friend Group operator!(const ExecutableItem &item); - TASKING_EXPORT friend Group operator&&(const ExecutableItem &first, - const ExecutableItem &second); - TASKING_EXPORT friend Group operator||(const ExecutableItem &first, - const ExecutableItem &second); - TASKING_EXPORT friend Group operator&&(const ExecutableItem &item, DoneResult result); - TASKING_EXPORT friend Group operator||(const ExecutableItem &item, DoneResult result); - - Group withCancelImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper, - const GroupItems &postCancelRecipe) const; - Group withAcceptImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const; -}; - -class TASKING_EXPORT Group : public ExecutableItem -{ -public: - Group(const GroupItems &children) { addChildren(children); } - Group(std::initializer_list<GroupItem> children) { addChildren(children); } - - // GroupData related: - template <typename Handler> - static GroupItem onGroupSetup(Handler &&handler) { - return groupHandler({wrapGroupSetup(std::forward<Handler>(handler))}); - } - template <typename Handler> - static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) { - return groupHandler({{}, wrapGroupDone(std::forward<Handler>(handler)), callDoneIf}); - } - -private: - template <typename Handler> - static GroupSetupHandler wrapGroupSetup(Handler &&handler) - { - // R, V stands for: Setup[R]esult, [V]oid - static constexpr bool isR = isInvocable<SetupResult, Handler>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isR || isV, - "Group setup handler needs to take no arguments and has to return void or SetupResult. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)] { - if constexpr (isR) - return std::invoke(handler); - std::invoke(handler); - return SetupResult::Continue; - }; - } - template <typename Handler> - static GroupDoneHandler wrapGroupDone(Handler &&handler) - { - static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>; - // R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith - static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>(); - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isVD = isInvocable<void, Handler, DoneWith>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isDoneResultType || isRD || isR || isBD || isB || isVD || isV, - "Group done handler needs to take (DoneWith) or (void) as an argument and has to " - "return void, bool or DoneResult. Alternatively, it may be of DoneResult type. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](DoneWith result) { - if constexpr (isDoneResultType) - return handler; - if constexpr (isRD) - return std::invoke(handler, result); - if constexpr (isR) - return std::invoke(handler); - if constexpr (isBD) - return toDoneResult(std::invoke(handler, result)); - if constexpr (isB) - return toDoneResult(std::invoke(handler)); - if constexpr (isVD) - std::invoke(handler, result); - else if constexpr (isV) - std::invoke(handler); - return toDoneResult(result == DoneWith::Success); - }; - } -}; - -template <typename SenderSignalPairGetter> -Group ExecutableItem::withCancel(SenderSignalPairGetter &&getter, - std::initializer_list<GroupItem> postCancelRecipe) const -{ - const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) { - const auto senderSignalPair = getter(); - QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] { - trigger(); - }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); - }; - return withCancelImpl(connectWrapper, postCancelRecipe); -} - -template <typename SenderSignalPairGetter> -Group ExecutableItem::withAccept(SenderSignalPairGetter &&getter) const -{ - const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) { - const auto senderSignalPair = getter(); - QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] { - trigger(); - }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); - }; - return withAcceptImpl(connectWrapper); -} - -template <typename Handler> -static GroupItem onGroupSetup(Handler &&handler) -{ - return Group::onGroupSetup(std::forward<Handler>(handler)); -} - -template <typename Handler> -static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) -{ - return Group::onGroupDone(std::forward<Handler>(handler), callDoneIf); -} - -// Default: 1 (sequential). 0 means unlimited (parallel). -TASKING_EXPORT GroupItem parallelLimit(int limit); - -// Default: WorkflowPolicy::StopOnError. -TASKING_EXPORT GroupItem workflowPolicy(WorkflowPolicy policy); - -TASKING_EXPORT extern const GroupItem sequential; -TASKING_EXPORT extern const GroupItem parallel; -TASKING_EXPORT extern const GroupItem parallelIdealThreadCountLimit; - -TASKING_EXPORT extern const GroupItem stopOnError; -TASKING_EXPORT extern const GroupItem continueOnError; -TASKING_EXPORT extern const GroupItem stopOnSuccess; -TASKING_EXPORT extern const GroupItem continueOnSuccess; -TASKING_EXPORT extern const GroupItem stopOnSuccessOrError; -TASKING_EXPORT extern const GroupItem finishAllAndSuccess; -TASKING_EXPORT extern const GroupItem finishAllAndError; - -TASKING_EXPORT extern const GroupItem nullItem; -TASKING_EXPORT extern const ExecutableItem successItem; -TASKING_EXPORT extern const ExecutableItem errorItem; - -class TASKING_EXPORT For final -{ -public: - explicit For(const Loop &loop) : m_loop(loop) {} - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - - Loop m_loop; -}; - -class When; - -class TASKING_EXPORT Do final -{ -public: - explicit Do(const GroupItems &children) : m_children(children) {} - explicit Do(std::initializer_list<GroupItem> children) : m_children(children) {} - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem); - - GroupItem m_children; -}; - -class TASKING_EXPORT Forever final : public ExecutableItem -{ -public: - explicit Forever(const GroupItems &children) - { addChildren({ For (LoopForever()) >> Do { children } } ); } - explicit Forever(std::initializer_list<GroupItem> children) - { addChildren({ For (LoopForever()) >> Do { children } } ); } -}; - -// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() -class TASKING_EXPORT Sync final : public ExecutableItem -{ -public: - template <typename Handler> - Sync(Handler &&handler) { - addChildren({ onGroupDone(wrapHandler(std::forward<Handler>(handler))) }); - } - -private: - template <typename Handler> - static auto wrapHandler(Handler &&handler) { - // R, B, V stands for: Done[R]esult, [B]ool, [V]oid - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isR || isB || isV, - "Sync handler needs to take no arguments and has to return void, bool or DoneResult. " - "The passed handler doesn't fulfill these requirements."); - return handler; - } -}; - -template <typename Task, typename Deleter = std::default_delete<Task>> -class TaskAdapter : public TaskInterface -{ -protected: - TaskAdapter() : m_task(new Task) {} - Task *task() { return m_task.get(); } - const Task *task() const { return m_task.get(); } - -private: - using TaskType = Task; - using DeleterType = Deleter; - template <typename Adapter> friend class CustomTask; - std::unique_ptr<Task, Deleter> m_task; -}; - -template <typename Adapter> -class CustomTask final : public ExecutableItem -{ -public: - using Task = typename Adapter::TaskType; - using Deleter = typename Adapter::DeleterType; - static_assert(std::is_base_of_v<TaskAdapter<Task, Deleter>, Adapter>, - "The Adapter type for the CustomTask<Adapter> needs to be derived from " - "TaskAdapter<Task>."); - using TaskSetupHandler = std::function<SetupResult(Task &)>; - using TaskDoneHandler = std::function<DoneResult(const Task &, DoneWith)>; - - template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> - CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), - CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - : ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)), - wrapDone(std::forward<DoneHandler>(done)), callDoneIf}) - {} - -private: - static Adapter *createAdapter() { return new Adapter; } - - template <typename Handler> - static InterfaceSetupHandler wrapSetup(Handler &&handler) { - if constexpr (std::is_same_v<Handler, TaskSetupHandler>) - return {}; // When user passed {} for the setup handler. - // R, V stands for: Setup[R]esult, [V]oid - static constexpr bool isR = isInvocable<SetupResult, Handler, Task &>(); - static constexpr bool isV = isInvocable<void, Handler, Task &>(); - static_assert(isR || isV, - "Task setup handler needs to take (Task &) as an argument and has to return void or " - "SetupResult. The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](TaskInterface &taskInterface) { - Adapter &adapter = static_cast<Adapter &>(taskInterface); - if constexpr (isR) - return std::invoke(handler, *adapter.task()); - std::invoke(handler, *adapter.task()); - return SetupResult::Continue; - }; - } - - template <typename Handler> - static InterfaceDoneHandler wrapDone(Handler &&handler) { - if constexpr (std::is_same_v<Handler, TaskDoneHandler>) - return {}; // User passed {} for the done handler. - static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>; - // R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith - static constexpr bool isRTD = isInvocable<DoneResult, Handler, const Task &, DoneWith>(); - static constexpr bool isRT = isInvocable<DoneResult, Handler, const Task &>(); - static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>(); - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>(); - static constexpr bool isBT = isInvocable<bool, Handler, const Task &>(); - static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>(); - static constexpr bool isVT = isInvocable<void, Handler, const Task &>(); - static constexpr bool isVD = isInvocable<void, Handler, DoneWith>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isDoneResultType || isRTD || isRT || isRD || isR - || isBTD || isBT || isBD || isB - || isVTD || isVT || isVD || isV, - "Task done handler needs to take (const Task &, DoneWith), (const Task &), " - "(DoneWith) or (void) as arguments and has to return void, bool or DoneResult. " - "Alternatively, it may be of DoneResult type. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](const TaskInterface &taskInterface, DoneWith result) { - if constexpr (isDoneResultType) - return handler; - const Adapter &adapter = static_cast<const Adapter &>(taskInterface); - if constexpr (isRTD) - return std::invoke(handler, *adapter.task(), result); - if constexpr (isRT) - return std::invoke(handler, *adapter.task()); - if constexpr (isRD) - return std::invoke(handler, result); - if constexpr (isR) - return std::invoke(handler); - if constexpr (isBTD) - return toDoneResult(std::invoke(handler, *adapter.task(), result)); - if constexpr (isBT) - return toDoneResult(std::invoke(handler, *adapter.task())); - if constexpr (isBD) - return toDoneResult(std::invoke(handler, result)); - if constexpr (isB) - return toDoneResult(std::invoke(handler)); - if constexpr (isVTD) - std::invoke(handler, *adapter.task(), result); - else if constexpr (isVT) - std::invoke(handler, *adapter.task()); - else if constexpr (isVD) - std::invoke(handler, result); - else if constexpr (isV) - std::invoke(handler); - return toDoneResult(result == DoneWith::Success); - }; - } -}; - -template <typename Task> -class SimpleTaskAdapter final : public TaskAdapter<Task> -{ -public: - SimpleTaskAdapter() { this->connect(this->task(), &Task::done, this, &TaskInterface::done); } - void start() final { this->task()->start(); } -}; - -// A convenient helper, when: -// 1. Task is derived from QObject. -// 2. Task::start() method starts the task. -// 3. Task::done(DoneResult) signal is emitted when the task is finished. -template <typename Task> -using SimpleCustomTask = CustomTask<SimpleTaskAdapter<Task>>; - -class TASKING_EXPORT TaskTree final : public QObject -{ - Q_OBJECT - -public: - TaskTree(); - TaskTree(const Group &recipe); - ~TaskTree(); - - void setRecipe(const Group &recipe); - - void start(); - void cancel(); - bool isRunning() const; - - // Helper methods. They execute a local event loop with ExcludeUserInputEvents. - // The passed future is used for listening to the cancel event. - // Don't use it in main thread. To be used in non-main threads or in auto tests. - DoneWith runBlocking(); - DoneWith runBlocking(const QFuture<void> &future); - static DoneWith runBlocking(const Group &recipe); - static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future); - - int asyncCount() const; - int taskCount() const; - int progressMaximum() const { return taskCount(); } - int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded - - template <typename StorageStruct, typename Handler> - void onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) { - static_assert(std::is_invocable_v<std::decay_t<Handler>, StorageStruct &>, - "Storage setup handler needs to take (Storage &) as an argument. " - "The passed handler doesn't fulfill this requirement."); - setupStorageHandler(storage, - wrapHandler<StorageStruct>(std::forward<Handler>(handler)), {}); - } - template <typename StorageStruct, typename Handler> - void onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) { - static_assert(std::is_invocable_v<std::decay_t<Handler>, const StorageStruct &>, - "Storage done handler needs to take (const Storage &) as an argument. " - "The passed handler doesn't fulfill this requirement."); - setupStorageHandler(storage, {}, - wrapHandler<const StorageStruct>(std::forward<Handler>(handler))); - } - -Q_SIGNALS: - void started(); - void done(DoneWith result); - void asyncCountChanged(int count); - void progressValueChanged(int value); // updated whenever task finished / skipped / stopped - -private: - void setupStorageHandler(const StorageBase &storage, - const StorageBase::StorageHandler &setupHandler, - const StorageBase::StorageHandler &doneHandler); - template <typename StorageStruct, typename Handler> - StorageBase::StorageHandler wrapHandler(Handler &&handler) { - return [handler](void *voidStruct) { - auto *storageStruct = static_cast<StorageStruct *>(voidStruct); - std::invoke(handler, *storageStruct); - }; - } - - TaskTreePrivate *d; -}; - -class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree> -{ -public: - TaskTreeTaskAdapter(); - -private: - void start() final; -}; - -class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds> -{ -public: - TimeoutTaskAdapter(); - ~TimeoutTaskAdapter(); - -private: - void start() final; - std::optional<int> m_timerId; -}; - -using TaskTreeTask = CustomTask<TaskTreeTaskAdapter>; -using TimeoutTask = CustomTask<TimeoutTaskAdapter>; - -TASKING_EXPORT ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, - DoneResult result = DoneResult::Error); - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TASKTREE_H diff --git a/src/assets/downloader/tasking/tasktreerunner.cpp b/src/assets/downloader/tasking/tasktreerunner.cpp deleted file mode 100644 index 6ed642b1bfd..00000000000 --- a/src/assets/downloader/tasking/tasktreerunner.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tasktreerunner.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -TaskTreeRunner::~TaskTreeRunner() = default; - -void TaskTreeRunner::start(const Group &recipe, - const SetupHandler &setupHandler, - const DoneHandler &doneHandler) -{ - m_taskTree.reset(new TaskTree(recipe)); - connect(m_taskTree.get(), &TaskTree::done, this, [this, doneHandler](DoneWith result) { - m_taskTree.release()->deleteLater(); - if (doneHandler) - doneHandler(result); - emit done(result); - }); - if (setupHandler) - setupHandler(m_taskTree.get()); - emit aboutToStart(m_taskTree.get()); - m_taskTree->start(); -} - -void TaskTreeRunner::cancel() -{ - if (m_taskTree) - m_taskTree->cancel(); -} - -void TaskTreeRunner::reset() -{ - m_taskTree.reset(); -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tasktreerunner.h b/src/assets/downloader/tasking/tasktreerunner.h deleted file mode 100644 index f91e7608113..00000000000 --- a/src/assets/downloader/tasking/tasktreerunner.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TASKTREERUNNER_H -#define TASKING_TASKTREERUNNER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" -#include "tasktree.h" - -#include <QtCore/QObject> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class TASKING_EXPORT TaskTreeRunner : public QObject -{ - Q_OBJECT - -public: - using SetupHandler = std::function<void(TaskTree *)>; - using DoneHandler = std::function<void(DoneWith)>; - - ~TaskTreeRunner(); - - bool isRunning() const { return bool(m_taskTree); } - - // When task tree is running it resets the old task tree. - void start(const Group &recipe, - const SetupHandler &setupHandler = {}, - const DoneHandler &doneHandler = {}); - - // When task tree is running it emits done(DoneWith::Cancel) synchronously. - void cancel(); - - // No done() signal is emitted. - void reset(); - -Q_SIGNALS: - void aboutToStart(TaskTree *taskTree); - void done(DoneWith result); - -private: - std::unique_ptr<TaskTree> m_taskTree; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TASKTREERUNNER_H diff --git a/src/assets/downloader/tasking/tcpsocket.cpp b/src/assets/downloader/tasking/tcpsocket.cpp deleted file mode 100644 index 19aab8fc714..00000000000 --- a/src/assets/downloader/tasking/tcpsocket.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tcpsocket.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -void TcpSocket::start() -{ - if (m_socket) { - qWarning("The TcpSocket is already running. Ignoring the call to start()."); - return; - } - if (m_address.isNull()) { - qWarning("Can't start the TcpSocket with invalid address. " - "Stopping with an error."); - m_error = QAbstractSocket::HostNotFoundError; - emit done(DoneResult::Error); - return; - } - - m_socket.reset(new QTcpSocket); - connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, - [this](QAbstractSocket::SocketError error) { - m_error = error; - m_socket->disconnect(); - emit done(DoneResult::Error); - m_socket.release()->deleteLater(); - }); - connect(m_socket.get(), &QAbstractSocket::connected, this, [this] { - if (!m_writeData.isEmpty()) - m_socket->write(m_writeData); - emit started(); - }); - connect(m_socket.get(), &QAbstractSocket::disconnected, this, [this] { - m_socket->disconnect(); - emit done(DoneResult::Success); - m_socket.release()->deleteLater(); - }); - - m_socket->connectToHost(m_address, m_port); -} - -TcpSocket::~TcpSocket() -{ - if (m_socket) { - m_socket->disconnect(); - m_socket->abort(); - } -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tcpsocket.h b/src/assets/downloader/tasking/tcpsocket.h deleted file mode 100644 index ec0069bf130..00000000000 --- a/src/assets/downloader/tasking/tcpsocket.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TCPSOCKET_H -#define TASKING_TCPSOCKET_H - -#include "tasking_global.h" - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasktree.h" - -#include <QtNetwork/QTcpSocket> - -#include <memory> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// This class introduces the dependency to Qt::Network, otherwise Tasking namespace -// is independent on Qt::Network. -// Possibly, it could be placed inside Qt::Network library, as a wrapper around QTcpSocket. - -class TASKING_EXPORT TcpSocket final : public QObject -{ - Q_OBJECT - -public: - ~TcpSocket(); - void setAddress(const QHostAddress &address) { m_address = address; } - void setPort(quint16 port) { m_port = port; } - void setWriteData(const QByteArray &data) { m_writeData = data; } - QTcpSocket *socket() const { return m_socket.get(); } - void start(); - -Q_SIGNALS: - void started(); - void done(DoneResult result); - -private: - QHostAddress m_address; - quint16 m_port = 0; - QByteArray m_writeData; - std::unique_ptr<QTcpSocket> m_socket; - QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError; -}; - -using TcpSocketTask = SimpleCustomTask<TcpSocket>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TCPSOCKET_H diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 1147205b79f..380b571c3cf 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -638,6 +638,7 @@ qt_internal_extend_target(Core CONDITION WIN32 platform/windows/qcomptr_p.h platform/windows/qbstr_p.h platform/windows/qcomvariant_p.h + platform/windows/quniquehandle_types_windows.cpp platform/windows/quniquehandle_types_windows_p.h LIBRARIES advapi32 authz @@ -1028,7 +1029,7 @@ qt_internal_extend_target(Core qt_internal_extend_target(Core CONDITION - QT_FEATURE_timezone AND UNIX + QT_FEATURE_timezone AND UNIX AND NOT VXWORKS AND NOT ANDROID AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_tz.cpp @@ -1037,7 +1038,7 @@ qt_internal_extend_target(Core qt_internal_extend_target(Core CONDITION QT_FEATURE_icu AND QT_FEATURE_timezone - AND NOT UNIX AND NOT QT_FEATURE_timezone_tzdb + AND (VXWORKS OR NOT UNIX) AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_icu.cpp ) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index d951b85c147..edcfba0f6ce 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -1150,7 +1150,7 @@ qt_feature("timezone" PUBLIC SECTION "Utilities" LABEL "QTimeZone" PURPOSE "Provides support for time-zone handling." - CONDITION NOT WASM AND NOT VXWORKS + CONDITION NOT WASM ) qt_feature("timezone_locale" PRIVATE SECTION "Utilities" diff --git a/src/corelib/doc/images/javaiterators1.svg b/src/corelib/doc/images/javaiterators1.svg new file mode 100644 index 00000000000..468dbe5371c --- /dev/null +++ b/src/corelib/doc/images/javaiterators1.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="340" + height="110" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .line-style { stroke: red; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .fill-style { stroke: none; fill: red } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .line-style { stroke: red; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: red } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .line-style { stroke: red; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .fill-style { stroke: none; fill: red } +</style> + +<g transform="translate(10.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">A</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(90.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">B</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(170.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">C</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(250.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">D</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(330.5, 10.5)"> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +</svg> diff --git a/src/corelib/doc/images/javaiterators2.svg b/src/corelib/doc/images/javaiterators2.svg new file mode 100644 index 00000000000..df4c6b352a6 --- /dev/null +++ b/src/corelib/doc/images/javaiterators2.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="340" + height="130" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .line-style { stroke: red; fill: none } + svg .fill-style { stroke: none; fill: red } + svg .np-line-style { stroke: blue; fill: none } + svg .np-fill-style { stroke: none; fill: blue } + svg .text-style { font: 20px arial; fill: black } + svg .small-text-style { font: 12px monospace; fill: black } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .line-style { stroke: red; fill: none } + [data-theme="dark"] svg .fill-style { stroke: none; fill: red } + [data-theme="dark"] svg .np-line-style { stroke: #4080ff; fill: none; stroke-width: 1.5 } + [data-theme="dark"] svg .np-fill-style { stroke: none; fill: #4080ff } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .small-text-style { font: 12px monospace; fill: #f2f2f2 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .line-style { stroke: red; fill: none } + [data-theme="light"] svg .fill-style { stroke: none; fill: red } + [data-theme="light"] svg .np-line-style { stroke: blue; fill: none } + [data-theme="light"] svg .np-fill-style { stroke: none; fill: blue } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .small-text-style { font: 12px monospace; fill: black } +</style> + +<g transform="translate(10.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">A</text> +</g> + +<g transform="translate(90.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">B</text> +<path d="M 0,60 c 0,30 50,40 80,30" class="np-line-style" /> +<path d="M 0,60 l -2,14 l 11,-4 z" class="np-fill-style" /> +<text x="-15" y="110" class="small-text-style">previous()</text> +</g> + +<g transform="translate(170.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">C</text> +<path d="M 0,60 v 50" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +<text x="30" y="110" class="small-text-style">next()</text> +</g> + +<g transform="translate(250.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">D</text> +<path d="M 0,60 c 0,30 -50,40 -80,30" class="np-line-style" /> +<path d="M 0,60 l 2,14 l -11,-4 z" class="np-fill-style" /> +</g> + +</svg> diff --git a/src/corelib/doc/src/java-style-iterators.qdoc b/src/corelib/doc/src/java-style-iterators.qdoc index 856005cb66c..a0e5c53d633 100644 --- a/src/corelib/doc/src/java-style-iterators.qdoc +++ b/src/corelib/doc/src/java-style-iterators.qdoc @@ -42,7 +42,7 @@ diagram below shows the valid iterator positions as red arrows for a list containing four items: - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's a typical loop for iterating through all the elements of a QList<QString> in order: @@ -70,7 +70,7 @@ \l{QListIterator::next()}{next()} and \l{QListIterator::previous()}{previous()} on an iterator: - \image javaiterators2.png + \image javaiterators2.png Iterating to the next and previous items The following table summarizes the QListIterator API: diff --git a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc index 9b3ea6ae660..5dc2e6dad12 100644 --- a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc +++ b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc @@ -172,7 +172,7 @@ The following illustrates this approach. - \badcode + \code void DerivedClass::setValue(int val) { // do something @@ -247,7 +247,7 @@ be called for the current value of the property, register your callback using subscribe() instead. - \section1 Interaction with Q_PROPERTYs + \section1 Interaction with Q_PROPERTY A \l {The Property System}{Q_PROPERTY} that defines \c BINDABLE can be bound and used in binding expressions. You can implement such properties using \l {QProperty}, diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index 05e3a39e589..b0c6c46b125 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -20,6 +20,8 @@ public: private: std::unique_ptr<QRangeModelImplBase, QRangeModelImplBase::Deleter> impl; + friend class QRangeModelImplBase; + mutable QHash<int, QByteArray> m_roleNames; }; @@ -28,6 +30,16 @@ QRangeModel::QRangeModel(QRangeModelImplBase *impl, QObject *parent) { } +QRangeModelImplBase *QRangeModelImplBase::getImplementation(QRangeModel *model) +{ + return model->d_func()->impl.get(); +} + +const QRangeModelImplBase *QRangeModelImplBase::getImplementation(const QRangeModel *model) +{ + return model->d_func()->impl.get(); +} + /*! \class QRangeModel \inmodule QtCore diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index f38dc88c0d9..c2f473542d7 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -842,6 +842,11 @@ namespace QRangeModelDetails { using protocol = wrapped_t<Protocol>; using row = typename range_traits<wrapped_t<Range>>::value_type; + static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>, + protocol_childRows<protocol, row>>; + static constexpr bool is_list = static_size_v<row> == 0 + && (!has_metaobject_v<row> || row_category<row>::isMultiRole); + static constexpr bool is_table = !is_list && !is_tree; static constexpr bool has_newRow = protocol_newRow<protocol>(); static constexpr bool has_deleteRow = protocol_deleteRow<protocol, row>(); @@ -1046,6 +1051,9 @@ public: typename C::MultiData >; + static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model); + static Q_CORE_EXPORT const QRangeModelImplBase *getImplementation(const QRangeModel *model); + private: friend class QRangeModelPrivate; QRangeModel *m_rangeModel; @@ -1147,13 +1155,6 @@ protected: } } - static constexpr bool isMutable() - { - return range_features::is_mutable && row_features::is_mutable - && std::is_reference_v<row_reference> - && Structure::is_mutable_impl; - } - static constexpr int static_row_count = QRangeModelDetails::static_size_v<range_type>; static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>; static constexpr bool rows_are_owning_or_raw_pointers = @@ -1161,9 +1162,6 @@ protected: static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>; static constexpr bool one_dimensional_range = static_column_count == 0; - static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; } - static constexpr bool dynamicColumns() { return static_column_count < 0; } - // A row might be a value (or range of values), or a pointer. // row_ptr is always a pointer, and const_row_ptr is a pointer to const. using row_ptr = wrapped_row_type *; @@ -1205,6 +1203,15 @@ protected: "The range holding a move-only row-type must support insert(pos, start, end)"); public: + static constexpr bool isMutable() + { + return range_features::is_mutable && row_features::is_mutable + && std::is_reference_v<row_reference> + && Structure::is_mutable_impl; + } + static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; } + static constexpr bool dynamicColumns() { return static_column_count < 0; } + explicit QRangeModelImpl(Range &&model, Protocol&& protocol, QRangeModel *itemModel) : Ancestor(itemModel) , ProtocolStorage{std::forward<Protocol>(protocol)} @@ -1236,7 +1243,7 @@ public: if (row == index.row() && column == index.column()) return index; - if (column < 0 || column >= this->itemModel().columnCount()) + if (column < 0 || column >= this->columnCount({})) return {}; if (row == index.row()) @@ -1974,8 +1981,8 @@ public: } if (sourceRow == destRow || sourceRow == destRow - 1 || count <= 0 - || sourceRow < 0 || sourceRow + count - 1 >= this->itemModel().rowCount(sourceParent) - || destRow < 0 || destRow > this->itemModel().rowCount(destParent)) { + || sourceRow < 0 || sourceRow + count - 1 >= this->rowCount(sourceParent) + || destRow < 0 || destRow > this->rowCount(destParent)) { return false; } @@ -1995,11 +2002,14 @@ public: } } - QModelIndex parent(const QModelIndex &child) const { return that().parent(child); } + const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); } + protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); } + + QModelIndex parent(const QModelIndex &child) const { return that().parentImpl(child); } - int rowCount(const QModelIndex &parent) const { return that().rowCount(parent); } + int rowCount(const QModelIndex &parent) const { return that().rowCountImpl(parent); } - int columnCount(const QModelIndex &parent) const { return that().columnCount(parent); } + int columnCount(const QModelIndex &parent) const { return that().columnCountImpl(parent); } void destroy() { delete std::addressof(that()); } @@ -2035,6 +2045,11 @@ public: protected: ~QRangeModelImpl() { + deleteOwnedRows(); + } + + void deleteOwnedRows() + { // We delete row objects if we are not operating on a reference or pointer // to a range, as in that case, the owner of the referenced/pointed to // range also owns the row entries. @@ -2335,9 +2350,6 @@ protected: } - const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); } - protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); } - ModelData m_data; }; @@ -2385,7 +2397,7 @@ protected: return this->createIndex(row, column, QRangeModelDetails::pointerTo(*it)); } - QModelIndex parent(const QModelIndex &child) const + QModelIndex parentImpl(const QModelIndex &child) const { if (!child.isValid()) return {}; @@ -2410,12 +2422,12 @@ protected: return {}; } - int rowCount(const QModelIndex &parent) const + int rowCountImpl(const QModelIndex &parent) const { return Base::size(this->childRange(parent)); } - int columnCount(const QModelIndex &) const + int columnCountImpl(const QModelIndex &) const { // all levels of a tree have to have the same, static, column count if constexpr (Base::one_dimensional_range) @@ -2662,6 +2674,9 @@ class QGenericTableItemModelImpl using Base = QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>; friend class QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>; + static constexpr bool is_mutable_impl = true; + +public: using range_type = typename Base::range_type; using range_features = typename Base::range_features; using row_type = typename Base::row_type; @@ -2669,9 +2684,6 @@ class QGenericTableItemModelImpl using row_traits = typename Base::row_traits; using row_features = typename Base::row_features; - static constexpr bool is_mutable_impl = true; - -public: explicit QGenericTableItemModelImpl(Range &&model, QRangeModel *itemModel) : Base(std::forward<Range>(model), {}, itemModel) {} @@ -2692,19 +2704,19 @@ protected: } } - QModelIndex parent(const QModelIndex &) const + QModelIndex parentImpl(const QModelIndex &) const { return {}; } - int rowCount(const QModelIndex &parent) const + int rowCountImpl(const QModelIndex &parent) const { if (parent.isValid()) return 0; return int(Base::size(*this->m_data.model())); } - int columnCount(const QModelIndex &parent) const + int columnCountImpl(const QModelIndex &parent) const { if (parent.isValid()) return 0; @@ -2760,7 +2772,7 @@ protected: // dynamically sized rows all have to have the same column count if constexpr (Base::dynamicColumns() && row_features::has_resize) { if (QRangeModelDetails::isValid(empty_row)) - QRangeModelDetails::refTo(empty_row).resize(this->itemModel().columnCount()); + QRangeModelDetails::refTo(empty_row).resize(this->columnCount({})); } return empty_row; diff --git a/src/corelib/kernel/qcore_mac.mm b/src/corelib/kernel/qcore_mac.mm index db81108baef..687fc7e85fa 100644 --- a/src/corelib/kernel/qcore_mac.mm +++ b/src/corelib/kernel/qcore_mac.mm @@ -50,6 +50,8 @@ extern char **environ; QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + // -------------------------------------------------------------------------- #if defined(Q_OS_MACOS) @@ -396,6 +398,21 @@ std::optional<uint32_t> qt_mac_sipConfiguration() return configuration; } +bool qt_mac_processHasEntitlement(const QString &entitlement) +{ + if (QCFType<SecTaskRef> task = SecTaskCreateFromSelf(kCFAllocatorDefault)) { + if (QCFType<CFTypeRef> value = SecTaskCopyValueForEntitlement(task, + entitlement.toCFString(), nullptr)) { + + if (CFGetTypeID(value) != CFBooleanGetTypeID()) + return false; + + return CFBooleanGetValue(value.as<CFBooleanRef>()); + } + } + return false; +} + #define CHECK_SPAWN(expr) \ if ((expr) != 0) { \ posix_spawnattr_destroy(&attr); \ @@ -474,55 +491,11 @@ AppleApplication *qt_apple_sharedApplication() #if !defined(QT_BOOTSTRAPPED) -#if defined(Q_OS_MACOS) -namespace { -struct SandboxChecker -{ - SandboxChecker() : m_thread([this]{ - m_isSandboxed = []{ - QCFType<SecStaticCodeRef> staticCode = nullptr; - NSURL *executableUrl = NSBundle.mainBundle.executableURL; - if (SecStaticCodeCreateWithPath((__bridge CFURLRef)executableUrl, - kSecCSDefaultFlags, &staticCode) != errSecSuccess) - return false; - - QCFType<SecRequirementRef> sandboxRequirement; - if (SecRequirementCreateWithString(CFSTR("entitlement[\"com.apple.security.app-sandbox\"] exists"), - kSecCSDefaultFlags, &sandboxRequirement) != errSecSuccess) - return false; - - if (SecStaticCodeCheckValidityWithErrors(staticCode, - kSecCSBasicValidateOnly, sandboxRequirement, nullptr) != errSecSuccess) - return false; - - return true; - }(); - }) - {} - ~SandboxChecker() { - std::scoped_lock lock(m_mutex); - if (m_thread.joinable()) - m_thread.detach(); - } - bool isSandboxed() const { - std::scoped_lock lock(m_mutex); - if (m_thread.joinable()) - m_thread.join(); - return m_isSandboxed; - } -private: - bool m_isSandboxed; - mutable std::thread m_thread; - mutable std::mutex m_mutex; -}; -} // namespace -static SandboxChecker sandboxChecker; -#endif // Q_OS_MACOS - bool qt_apple_isSandboxed() { #if defined(Q_OS_MACOS) - return sandboxChecker.isSandboxed(); + static bool isSandboxed = qt_mac_processHasEntitlement(u"com.apple.security.app-sandbox"_s); + return isSandboxed; #else return true; // All other Apple platforms #endif diff --git a/src/corelib/kernel/qcore_mac_p.h b/src/corelib/kernel/qcore_mac_p.h index 9d1cdcac68a..2c4b4c02c55 100644 --- a/src/corelib/kernel/qcore_mac_p.h +++ b/src/corelib/kernel/qcore_mac_p.h @@ -216,6 +216,7 @@ public: #ifdef Q_OS_MACOS Q_CORE_EXPORT bool qt_mac_runningUnderRosetta(); Q_CORE_EXPORT std::optional<uint32_t> qt_mac_sipConfiguration(); +Q_CORE_EXPORT bool qt_mac_processHasEntitlement(const QString &entitlement); #ifdef QT_BUILD_INTERNAL Q_AUTOTEST_EXPORT void qt_mac_ensureResponsible(); #endif diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index de7c4df6d1d..afc85fe36fb 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -2917,7 +2917,7 @@ void QCoreApplication::requestPermissionImpl(const QPermission &requestedPermiss } private: - QtPrivate::SlotObjSharedPtr slotObject; + QtPrivate::SlotObjUniquePtr slotObject; QPointer<const QObject> context; }; diff --git a/src/corelib/kernel/qvariant.cpp b/src/corelib/kernel/qvariant.cpp index 8366787ea66..57089f164b2 100644 --- a/src/corelib/kernel/qvariant.cpp +++ b/src/corelib/kernel/qvariant.cpp @@ -2974,4 +2974,156 @@ const QVariant *QVariantConstPointer::operator->() const implement operator->(). */ +/*! + \class QVariant::ConstReference + \since 6.11 + \inmodule QtCore + \brief The QVariant::ConstReference acts as a const reference to a QVariant. + + As the generic iterators don't actually instantiate a QVariant on each + step, they cannot return a reference to one from operator*(). + QVariant::ConstReference provides the same functionality as an actual + reference to a QVariant would, but is backed a referred-to value given as + template parameter. The template is implemented for + QMetaSequence::ConstIterator, QMetaSequence::Iterator, + QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator. +*/ + +/*! + \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(const Referred &referred) + + Creates a QVariant::ConstReference from a \a referred. + */ + +/*! + \fn template<typename Referred> QVariant::ConstReference<Referred>::ConstReference(Referred &&referred) + + Creates a QVariant::ConstReference from a \a referred. + */ + +/*! + \fn template<typename Referred> QVariant::ConstReference<Referred>::operator QVariant() const + + Dereferences the reference to a QVariant. + This method needs to be specialized for each Referred type. It is + pre-defined for QMetaSequence::ConstIterator, QMetaSequence::Iterator, + QMetaAssociation::ConstIterator, and QMetaAssociation::Iterator. + */ + + +/*! + \class QVariant::Reference + \since 6.11 + \inmodule QtCore + \brief The QVariant::Reference acts as a non-const reference to a QVariant. + + As the generic iterators don't actually instantiate a QVariant on each + step, they cannot return a reference to one from operator*(). + QVariant::Reference provides the same functionality as an actual reference + to a QVariant would, but is backed a referred-to value given as template + parameter. The template is implemented for QMetaSequence::Iterator and + QMetaAssociation::Iterator. +*/ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred>::Reference(const Referred &referred) + + Creates a QVariant::Reference from a \a referred. + */ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred>::Reference(Referred &&referred) + + Creates a QVariant::Reference from a \a referred. + */ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const Reference<Referred> &value) + + Assigns a new \a value to the value referred to by this QVariant::Reference. + */ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(Reference<Referred> &&value) + + Assigns a new \a value to the value referred to by this QVariant::Reference. +*/ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred> &QVariant::Reference<Referred>::operator=(const QVariant &value) + + Assigns a new \a value to the value referred to by this QVariant::Reference. + This method needs to be specialized for each Referred type. It is + pre-defined for QMetaSequence::Iterator and QMetaAssociation::Iterator. + */ + +/*! + \fn template<typename Referred> QVariant::Reference<Referred>::operator QVariant() const + + Dereferences the reference to a QVariant. By default this instantiates a + temporary QVariant::ConstReference and calls dereferences that. In cases + where instantiating a temporary ConstReference is expensive, this method + should be specialized. + */ + +/*! + \class QVariant::ConstPointer + \since 6.11 + \inmodule QtCore + \brief QVariant::ConstPointer is a template class that emulates a const pointer to QVariant. + + QVariant::ConstPointer wraps pointed-to value and returns a + QVariant::ConstReference to it from its operator*(). This makes it suitable + as replacement for an actual pointer. We cannot return an actual pointer + from generic iterators as the iterators don't hold an actual QVariant. +*/ + +/*! + \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(const Pointed &pointed) + + Constructs a QVariant::ConstPointer from the value \a pointed to. + */ + +/*! + \fn template<typename Pointed> QVariant::ConstPointer<Pointed>::ConstPointer(Pointed &&pointed) + + Constructs a QVariant::ConstPointer from the value \a pointed to. + */ + +/*! + \fn template<typename Pointed> QVariant::ConstReference<Pointer> QVariant::ConstPointer<Pointed>::operator*() const + + Dereferences the QVariant::ConstPointer to a QVariant::ConstReference. + */ + +/*! + \class QVariant::Pointer + \since 6.11 + \inmodule QtCore + \brief QVariant::Pointer is a template class that emulates a non-const pointer to QVariant. + + QVariant::Pointer wraps pointed-to value and returns a QVariant::Reference + to it from its operator*(). This makes it suitable as replacement for an + actual pointer. We cannot return an actual pointer from generic iterators as + the iterators don't hold an actual QVariant. +*/ + +/*! + \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(const Pointed &pointed) + + Constructs a QVariant::Pointer from the value \a pointed to. + */ + +/*! + \fn template<typename Pointed> QVariant::Pointer<Pointed>::Pointer(Pointed &&pointed) + + Constructs a QVariant::Pointer from the value \a pointed to. + */ + +/*! + \fn template<typename Pointed> QVariant::Reference<Pointer> QVariant::Pointer<Pointed>::operator*() const + + Dereferences the QVariant::Pointer to a QVariant::Reference. + */ + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qvariant.h b/src/corelib/kernel/qvariant.h index 542b1d9b709..9b219d089b5 100644 --- a/src/corelib/kernel/qvariant.h +++ b/src/corelib/kernel/qvariant.h @@ -61,7 +61,20 @@ inline T qvariant_cast(const QVariant &); namespace QtPrivate { template<> constexpr inline bool qIsRelocatable<QVariant> = true; -} + +template<typename Referred> +class ConstReference; + +template<typename Referred> +class Reference; + +template<typename Pointed> +class ConstPointer; + +template<typename Pointed> +class Pointer; +} // namespace QtPrivate + class Q_CORE_EXPORT QVariant { template <typename T, typename... Args> @@ -228,6 +241,123 @@ private: >; public: + template<typename Referred> + class ConstReference + { + private: + const Referred m_referred; + + public: + // You can initialize a const reference from another one, but you can't assign to it. + + explicit ConstReference(const Referred &referred) + noexcept(std::is_nothrow_copy_constructible_v<Referred>) + : m_referred(referred) {} + explicit ConstReference(Referred &&referred) + noexcept(std::is_nothrow_move_constructible_v<Referred>) + : m_referred(std::move(referred)) {} + ConstReference(const ConstReference &) = default; + ConstReference(ConstReference &&) = default; + ~ConstReference() = default; + ConstReference &operator=(const ConstReference &value) = delete; + ConstReference &operator=(ConstReference &&value) = delete; + + // To be specialized for each Referred + operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant); + }; + + template<typename Referred> + class Reference + { + private: + Referred m_referred; + + friend void swap(Reference a, Reference b) { return a.swap(std::move(b)); } + + public: + // Assigning and initializing are different operations for references. + + explicit Reference(const Referred &referred) + noexcept(std::is_nothrow_copy_constructible_v<Referred>) + : m_referred(referred) {} + explicit Reference(Referred &&referred) + noexcept(std::is_nothrow_move_constructible_v<Referred>) + : m_referred(std::move(referred)) {} + Reference(const Reference &) = default; + Reference(Reference &&) = default; + ~Reference() = default; + + Reference &operator=(const Reference &value) + noexcept(Referred::canNoexceptAssignQVariant) + { + return operator=(QVariant(value)); + } + + Reference &operator=(Reference &&value) + noexcept(Referred::canNoexceptAssignQVariant) + { + return operator=(QVariant(value)); + } + + operator QVariant() const noexcept(Referred::canNoexceptConvertToQVariant) + { + return ConstReference(m_referred); + } + + void swap(Reference b) + { + // swapping a reference is not swapping the referred item, but swapping its contents. + QVariant tmp = *this; + *this = std::move(b); + b = std::move(tmp); + } + + // To be specialized for each Referred + Reference &operator=(const QVariant &value) noexcept(Referred::canNoexceptAssignQVariant); + }; + + template<typename Pointed> + class ConstPointer + { + private: + Pointed m_pointed; + + public: + explicit ConstPointer(const Pointed &pointed) + noexcept(std::is_nothrow_copy_constructible_v<Pointed>) + : m_pointed(pointed) {} + explicit ConstPointer(Pointed &&pointed) + noexcept(std::is_nothrow_move_constructible_v<Pointed>) + : m_pointed(std::move(pointed)) {} + + ConstReference<Pointed> operator*() + const noexcept(std::is_nothrow_copy_constructible_v<Pointed>) + { + return ConstReference<Pointed>(m_pointed); + } + }; + + template<typename Pointed> + class Pointer + { + private: + Pointed m_pointed; + + public: + explicit Pointer(const Pointed &pointed) + noexcept(std::is_nothrow_copy_constructible_v<Pointed>) + : m_pointed(pointed) {} + explicit Pointer(Pointed &&pointed) + noexcept(std::is_nothrow_move_constructible_v<Pointed>) + : m_pointed(std::move(pointed)) {} + + Reference<Pointed> operator*() + const noexcept(std::is_nothrow_copy_constructible_v<Pointed>) + { + return Reference<Pointed>(m_pointed); + } + }; + template <typename T, typename... Args, if_constructible<T, Args...> = true> explicit QVariant(std::in_place_type_t<T>, Args&&... args) diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 32abac71ca1..287138bb915 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -638,6 +638,249 @@ qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize) return size; } +FileSystemWritableFileStreamIODevice::FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream) + : m_stream(std::move(stream)) +{ +} + +bool FileSystemWritableFileStreamIODevice::open(QIODevice::OpenMode mode) +{ + if (mode.testFlag(QIODevice::ReadOnly)) + return false; + return QIODevice::open(mode); +} + +void FileSystemWritableFileStreamIODevice::close() +{ + if (!isOpen()) { + QIODevice::close(); + return; + } + + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("close"), { + .thenFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + QIODevice::close(); +} + +bool FileSystemWritableFileStreamIODevice::isSequential() const +{ + return false; +} + +qint64 FileSystemWritableFileStreamIODevice::size() const +{ + return m_size; +} + +bool FileSystemWritableFileStreamIODevice::seek(qint64 pos) +{ + bool success = false; + + emscripten::val seekParams = emscripten::val::object(); + seekParams.set("type", std::string("seek")); + seekParams.set("position", static_cast<double>(pos)); + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), { + .thenFunc = [&success](emscripten::val) { + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }, seekParams); + Promise::suspendExclusive(handlerIndex); + + if (!success) + return false; + + return QIODevice::seek(pos); +} + +qint64 FileSystemWritableFileStreamIODevice::readData(char *, qint64) +{ + Q_UNREACHABLE(); +} + +qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64 size) +{ + bool success = false; + + Uint8Array array = Uint8Array::copyFrom(data, size); + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), { + .thenFunc = [&success](emscripten::val) { + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }, array.val()); + Promise::suspendExclusive(handlerIndex); + + if (success) { + qint64 newPos = pos() + size; + m_size = std::max(m_size, newPos); + return size; + } + return -1; +} + +FileSystemWritableFileStream::FileSystemWritableFileStream(const emscripten::val &writableStream) + : m_writableStream(writableStream) +{ +} + +emscripten::val FileSystemWritableFileStream::val() const +{ + return m_writableStream; +} + +FileSystemFileHandle::FileSystemFileHandle(const emscripten::val &fileHandle) + : m_fileHandle(fileHandle) +{ +} + +std::string FileSystemFileHandle::name() const +{ + return m_fileHandle["name"].as<std::string>(); +} + +std::string FileSystemFileHandle::kind() const +{ + return m_fileHandle["kind"].as<std::string>(); +} + +emscripten::val FileSystemFileHandle::val() const +{ + return m_fileHandle; +} + +FileSystemFileIODevice::FileSystemFileIODevice(FileSystemFileHandle fileHandle) + : m_fileHandle(fileHandle) +{ +} + +bool FileSystemFileIODevice::open(QIODevice::OpenMode mode) +{ + if (isOpen()) + return false; + + // Read mode: get the File and create a BlobIODevice + if (mode & QIODevice::ReadOnly) { + File file; + bool success = false; + + uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("getFile"), { + .thenFunc = [&file, &success](emscripten::val fileVal) { + file = File(fileVal); + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + if (success) { + m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size())); + m_size = file.size(); + + if (!m_blobDevice->open(mode)) + return false; + } else { + return false; + } + } + + // Write mode: create a writable stream + if (mode & QIODevice::WriteOnly) { + FileSystemWritableFileStream writableStream; + bool success = false; + + uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("createWritable"), { + .thenFunc = [&writableStream, &success](emscripten::val writable) { + writableStream = FileSystemWritableFileStream(writable); + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + if (success) { + m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream); + if (!m_writableDevice->open(mode)) + return false; + } else { + return false; + } + } + + return QIODevice::open(mode); +} + +void FileSystemFileIODevice::close() +{ + if (!isOpen()) { + QIODevice::close(); + return; + } + + if (m_writableDevice) { + m_writableDevice->close(); + m_writableDevice.reset(); + } + if (m_blobDevice) { + m_blobDevice->close(); + m_blobDevice.reset(); + } + + QIODevice::close(); +} + +bool FileSystemFileIODevice::isSequential() const +{ + return false; +} + +qint64 FileSystemFileIODevice::size() const +{ + return m_size; +} + +bool FileSystemFileIODevice::seek(qint64 pos) +{ + if (m_blobDevice) { + if (!m_blobDevice->seek(pos)) + return false; + } + if (m_writableDevice) { + if (!m_writableDevice->seek(pos)) + return false; + } + return QIODevice::seek(pos); +} + +qint64 FileSystemFileIODevice::readData(char *data, qint64 maxSize) +{ + if (!m_blobDevice) + return -1; + + return m_blobDevice->read(data, maxSize); +} + +qint64 FileSystemFileIODevice::writeData(const char *data, qint64 size) +{ + if (!m_writableDevice) + return -1; + + qint64 written = m_writableDevice->write(data, size); + if (written > 0) { + qint64 newPos = pos() + written; + m_size = std::max(m_size, newPos); + } + return written; +} + } // namespace qstdweb QT_END_NAMESPACE diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index 0159ae62d6f..9a97370448e 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -195,6 +195,30 @@ namespace qstdweb { emscripten::val m_uint8Array = emscripten::val::undefined(); }; + class Q_CORE_EXPORT FileSystemWritableFileStream { + public: + FileSystemWritableFileStream() = default; + explicit FileSystemWritableFileStream(const emscripten::val &writableStream); + emscripten::val val() const; + + private: + emscripten::val m_writableStream = emscripten::val::undefined(); + }; + + class Q_CORE_EXPORT FileSystemFileHandle { + public: + FileSystemFileHandle() = default; + explicit FileSystemFileHandle(const emscripten::val &fileHandle); + + std::string name() const; + std::string kind() const; + + emscripten::val val() const; + + private: + emscripten::val m_fileHandle = emscripten::val::undefined(); + }; + // EventCallback here for source compatibility; prefer using QWasmEventHandler directly class Q_CORE_EXPORT EventCallback : public QWasmEventHandler { @@ -275,6 +299,46 @@ namespace qstdweb { Uint8Array m_array; }; + class Q_CORE_EXPORT FileSystemWritableFileStreamIODevice: public QIODevice + { + public: + FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream); + bool open(QIODevice::OpenMode mode) override; + void close() override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 size) override; + + private: + FileSystemWritableFileStream m_stream; + qint64 m_size = 0; + }; + + class Q_CORE_EXPORT FileSystemFileIODevice: public QIODevice + { + public: + FileSystemFileIODevice(FileSystemFileHandle fileHandle); + bool open(QIODevice::OpenMode mode) override; + void close() override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 size) override; + + private: + FileSystemFileHandle m_fileHandle; + std::unique_ptr<BlobIODevice> m_blobDevice; + std::unique_ptr<FileSystemWritableFileStreamIODevice> m_writableDevice; + qint64 m_size = 0; + }; + inline emscripten::val window() { static emscripten::val savedWindow = emscripten::val::global("window"); diff --git a/src/corelib/platform/windows/quniquehandle_types_windows.cpp b/src/corelib/platform/windows/quniquehandle_types_windows.cpp new file mode 100644 index 00000000000..801c9ab13d6 --- /dev/null +++ b/src/corelib/platform/windows/quniquehandle_types_windows.cpp @@ -0,0 +1,17 @@ +// 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 "quniquehandle_types_windows_p.h" + +QT_BEGIN_NAMESPACE + +namespace QtUniqueHandleTraits { + +bool HDCTraits::close(Type handle, HWND hwnd) noexcept +{ + return ::ReleaseDC(hwnd, handle); +} + +} // namespace QtUniqueHandleTraits + +QT_END_NAMESPACE diff --git a/src/corelib/platform/windows/quniquehandle_types_windows_p.h b/src/corelib/platform/windows/quniquehandle_types_windows_p.h new file mode 100644 index 00000000000..638441a87ef --- /dev/null +++ b/src/corelib/platform/windows/quniquehandle_types_windows_p.h @@ -0,0 +1,65 @@ +// 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 QUNIQUEHANDLE_TYPES_WINDOWS_P_H +#define QUNIQUEHANDLE_TYPES_WINDOWS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qnamespace.h> +#include <QtCore/qt_windows.h> +#include <QtCore/private/quniquehandle_p.h> + +#if defined(Q_OS_WIN) || defined(Q_QDOC) + +QT_BEGIN_NAMESPACE + +namespace QtUniqueHandleTraits { + +struct HDCTraits +{ + using Type = HDC; + static Type invalidValue() noexcept { return nullptr; } + Q_CORE_EXPORT static bool close(Type handle, HWND hwnd) noexcept; +}; + +struct HDCDeleter +{ + using Type = HDCTraits::Type; + + constexpr HDCDeleter() noexcept = default; + explicit constexpr HDCDeleter(HWND hwnd) noexcept + : hwnd(hwnd) + {} + + void operator()(Type handle) const noexcept + { + if (handle != HDCTraits::invalidValue()) { + const bool success = HDCTraits::close(handle, hwnd); + Q_ASSERT(success); + } + } + + HWND hwnd{ nullptr }; +}; + +} // namespace QtUniqueHandleTraits +using QUniqueHDCHandle = QUniqueHandle< + QtUniqueHandleTraits::HDCTraits, + QtUniqueHandleTraits::HDCDeleter +>; + +QT_END_NAMESPACE + +#endif // Q_OS_WIN + +#endif // QUNIQUEHANDLE_TYPES_WINDOWS_P_H diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 711b70ebf8f..c01c1a9999d 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -3681,95 +3681,24 @@ QString &QString::remove(QChar ch, Qt::CaseSensitivity cs) \sa remove() */ - -/*! \internal - Instead of detaching, or reallocating if "before" is shorter than "after" - and there isn't enough capacity, create a new string, copy characters to it - as needed, then swap it with "str". -*/ -static void replace_with_copy(QString &str, QSpan<size_t> indices, qsizetype blen, - QStringView after) -{ - const qsizetype alen = after.size(); - const char16_t *after_b = after.utf16(); - - const QString::DataPointer &str_d = str.data_ptr(); - auto src_start = str_d.begin(); - const qsizetype newSize = str_d.size + indices.size() * (alen - blen); - QString copy{ newSize, Qt::Uninitialized }; - QString::DataPointer ©_d = copy.data_ptr(); - auto dst = copy_d.begin(); - for (size_t index : indices) { - auto hit = str_d.begin() + index; - dst = std::copy(src_start, hit, dst); - dst = std::copy_n(after_b, alen, dst); - src_start = hit + blen; - } - dst = std::copy(src_start, str_d.end(), dst); - str.swap(copy); -} - -// No detaching or reallocation is needed -static void replace_in_place(QString &str, QSpan<size_t> indices, - qsizetype blen, QStringView after) -{ - const qsizetype alen = after.size(); - const char16_t *after_b = after.utf16(); - const char16_t *after_e = after.utf16() + after.size(); - - if (blen == alen) { // Replace in place - for (size_t index : indices) - std::copy_n(after_b, alen, str.data_ptr().begin() + index); - } else if (blen > alen) { // Replace from front - char16_t *begin = str.data_ptr().begin(); - char16_t *hit = begin + indices.front(); - char16_t *to = hit; - to = std::copy_n(after_b, alen, to); - char16_t *movestart = hit + blen; - for (size_t index : indices.sliced(1)) { - hit = begin + index; - to = std::move(movestart, hit, to); - to = std::copy_n(after_b, alen, to); - movestart = hit + blen; - } - to = std::move(movestart, str.data_ptr().end(), to); - str.resize(std::distance(begin, to)); - } else { // blen < alen, Replace from back - const qsizetype oldSize = str.data_ptr().size; - const qsizetype adjust = indices.size() * (alen - blen); - const qsizetype newSize = oldSize + adjust; - - str.resize(newSize); - char16_t *begin = str.data_ptr().begin(); - char16_t *moveend = begin + oldSize; - char16_t *to = str.data_ptr().end(); - - for (auto it = indices.rbegin(), end = indices.rend(); it != end; ++it) { - char16_t *hit = begin + *it; - char16_t *movestart = hit + blen; - to = std::move_backward(movestart, moveend, to); - to = std::copy_backward(after_b, after_e, to); - moveend = hit; - } - } -} - -static void replace_helper(QString &str, QSpan<size_t> indices, qsizetype blen, QStringView after) +static void replace_helper(QString &str, QSpan<qsizetype> indices, qsizetype blen, QStringView after) { const qsizetype oldSize = str.data_ptr().size; const qsizetype adjust = indices.size() * (after.size() - blen); const qsizetype newSize = oldSize + adjust; + using A = QStringAlgorithms<QString>; if (str.data_ptr().needsDetach() || needsReallocate(str, newSize)) { - replace_with_copy(str, indices, blen, after); + A::replace_helper(str, blen, after, indices); return; } - if (QtPrivate::q_points_into_range(after.begin(), str)) + if (QtPrivate::q_points_into_range(after.begin(), str)) { // Copy after if it lies inside our own d.b area (which we could // possibly invalidate via a realloc or modify by replacement) - replace_in_place(str, indices, blen, QVarLengthArray(after.begin(), after.end())); - else - replace_in_place(str, indices, blen, after); + A::replace_helper(str, blen, QVarLengthArray(after.begin(), after.end()), indices); + } else { + A::replace_helper(str, blen, after, indices); + } } /*! @@ -3811,8 +3740,8 @@ QString &QString::replace(qsizetype pos, qsizetype len, const QChar *after, qsiz if (len > this->size() - pos) len = this->size() - pos; - size_t index = pos; - replace_helper(*this, QSpan(&index, 1), len, QStringView{after, alen}); + qsizetype indices[] = {pos}; + replace_helper(*this, indices, len, QStringView{after, alen}); return *this; } @@ -3890,7 +3819,7 @@ QString &QString::replace(const QChar *before, qsizetype blen, qsizetype index = 0; - QVarLengthArray<size_t> indices; + QVarLengthArray<qsizetype> indices; while ((index = matcher.indexIn(*this, index)) != -1) { indices.push_back(index); if (blen) // Step over before: @@ -3925,7 +3854,7 @@ QString& QString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs const char16_t cc = (cs == Qt::CaseSensitive ? ch.unicode() : ch.toCaseFolded().unicode()); - QVarLengthArray<size_t> indices; + QVarLengthArray<qsizetype> indices; if (cs == Qt::CaseSensitive) { const char16_t *begin = d.begin(); const char16_t *end = d.end(); diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h index bb70678a958..6e4e532fd7d 100644 --- a/src/corelib/thread/qthread.h +++ b/src/corelib/thread/qthread.h @@ -172,27 +172,15 @@ inline Qt::HANDLE QThread::currentThreadId() noexcept #elif defined(Q_PROCESSOR_X86_64) && ((defined(Q_OS_LINUX) && defined(__GLIBC__)) || defined(Q_OS_FREEBSD)) // x86_64 Linux, BSD uses FS __asm__("mov %%fs:%c1, %0" : "=r" (tid) : "i" (2 * sizeof(void*)) : ); -#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) +#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) && defined(Q_CC_MSVC) // See https://en.wikipedia.org/wiki/Win32_Thread_Information_Block - // First get the pointer to the TIB - quint8 *tib; -# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics - __asm__("movq %%gs:0x30, %0" : "=r" (tib) : :); -# else - tib = reinterpret_cast<quint8 *>(__readgsqword(0x30)); -# endif - // Then read the thread ID - tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x48); -#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) - // First get the pointer to the TIB - quint8 *tib; -# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics - __asm__("movl %%fs:0x18, %0" : "=r" (tib) : :); -# else - tib = reinterpret_cast<quint8 *>(__readfsdword(0x18)); -# endif - // Then read the thread ID - tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x24); + tid = reinterpret_cast<Qt::HANDLE>(__readgsqword(0x48)); +#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) // !Q_CC_MSVC + __asm__("mov %%gs:0x48, %0" : "=r" (tid)); +#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) && defined(Q_CC_MSVC) + tid = reinterpret_cast<Qt::HANDLE>(__readfsdword(0x24)); +#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) // !Q_CC_MSVC + __asm__("mov %%fs:0x24, %0" : "=r" (tid)); #else #undef QT_HAS_FAST_CURRENT_THREAD_ID tid = currentThreadIdImpl(); diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 974c486b915..deac396061d 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -3991,7 +3991,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime \c{'compatible'} option corresponds to \c RelativeToBefore (and Python's \c{fold = True}). - \sa {Timezone transitions}, QDateTime::TransitionResolution + \sa {Timezone transitions} */ /*! diff --git a/src/corelib/time/qgregoriancalendar.cpp b/src/corelib/time/qgregoriancalendar.cpp index d46d24ac30d..dfb99c5073d 100644 --- a/src/corelib/time/qgregoriancalendar.cpp +++ b/src/corelib/time/qgregoriancalendar.cpp @@ -31,6 +31,7 @@ static_assert(qDivMod<86400>(-172800).remainder == 0); /*! \since 5.14 + \internal \class QGregorianCalendar \inmodule QtCore diff --git a/src/corelib/time/qjalalicalendar.cpp b/src/corelib/time/qjalalicalendar.cpp index 8bc9fe125e7..683ce6e7712 100644 --- a/src/corelib/time/qjalalicalendar.cpp +++ b/src/corelib/time/qjalalicalendar.cpp @@ -39,6 +39,7 @@ qint64 firstDayOfYear(int year, int cycleNo) /*! \since 5.14 + \internal \class QJalaliCalendar \inmodule QtCore diff --git a/src/corelib/time/qjuliancalendar.cpp b/src/corelib/time/qjuliancalendar.cpp index 47da952b84a..cf3718f471d 100644 --- a/src/corelib/time/qjuliancalendar.cpp +++ b/src/corelib/time/qjuliancalendar.cpp @@ -13,6 +13,7 @@ using namespace QRoundingDown; /*! \since 5.14 + \internal \class QJulianCalendar \inmodule QtCore diff --git a/src/corelib/time/qmilankoviccalendar.cpp b/src/corelib/time/qmilankoviccalendar.cpp index a3ffa2a3053..14aef83afe3 100644 --- a/src/corelib/time/qmilankoviccalendar.cpp +++ b/src/corelib/time/qmilankoviccalendar.cpp @@ -13,6 +13,7 @@ using namespace QRoundingDown; /*! \since 5.14 + \internal \class QMilankovicCalendar \inmodule QtCore diff --git a/src/corelib/time/qromancalendar.cpp b/src/corelib/time/qromancalendar.cpp index ae113cf8323..a8ce027275a 100644 --- a/src/corelib/time/qromancalendar.cpp +++ b/src/corelib/time/qromancalendar.cpp @@ -9,6 +9,7 @@ QT_BEGIN_NAMESPACE /*! \since 5.14 + \internal \class QRomanCalendar \inmodule QtCore diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index f44c681ea80..7b43aab22d1 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -29,7 +29,7 @@ static QTimeZonePrivate *newBackendTimeZone() return new QMacTimeZonePrivate(); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(); -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) return new QTzTimeZonePrivate(); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(); @@ -50,7 +50,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) return new QMacTimeZonePrivate(ianaId); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(ianaId); -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) return new QTzTimeZonePrivate(ianaId); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(ianaId); @@ -1482,7 +1482,8 @@ QTimeZone QTimeZone::utc() bool QTimeZone::isTimeZoneIdAvailable(const QByteArray &ianaId) { -#if defined(Q_OS_UNIX) && !(defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN)) +#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \ + || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS)) // Keep #if-ery consistent with selection of QTzTimeZonePrivate in // newBackendTimeZone(). Skip the pre-check, as the TZ backend accepts POSIX // zone IDs, which need not be valid IANA IDs. See also QTBUG-112006. diff --git a/src/corelib/time/qtimezonelocale.cpp b/src/corelib/time/qtimezonelocale.cpp index a794682fe0b..6ad4aa1479c 100644 --- a/src/corelib/time/qtimezonelocale.cpp +++ b/src/corelib/time/qtimezonelocale.cpp @@ -611,6 +611,27 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc // Custom zone with perverse m_id ? return; } + const auto isMixedCaseAbbrev = [tail](char ch) { + // cv-RU and en-GU abbreviate Chamorro as ChST + // scn-IT abbreviates Cuba as CuT/CuST/CuDT + // blo-BJ abbreviates GMT as Gk + switch (tail.size()) { + case 2: return tail == "Gk"; + case 3: return tail == "CuT"; + case 4: + if (tail[0] == 'C' && tail[1] == ch && tail[3] == 'T') { + switch (ch) { + case 'h': return tail[2] == 'S'; + case 'u': return tail[2] == 'S' || tail[2] == 'D'; + default: break; + } + } + return false; + default: + break; + } + return false; + }; // Even if it is abbr or city name, we don't care if we've found one before. bool maybeAbbr = ianaAbbrev.isEmpty(), maybeCityName = ianaTail.isEmpty(), inword = false; @@ -632,7 +653,7 @@ QString QTimeZonePrivate::localeName(qint64 atMSecsSinceEpoch, int offsetFromUtc maybeCityName = false; inword = false; } else if (QChar::isLower(ch)) { - maybeAbbr = false; + maybeAbbr = isMixedCaseAbbrev(ch); // Dar_es_Salaam shows both cases as word starts inword = true; } else if (QChar::isUpper(ch)) { @@ -891,8 +912,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale, if (best.ianaIdIndex != invalidIanaId) return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType }; - // Now try for a region format: - best = {}; + // Now try for a region format. + // Since we may get the IANA ID directly from a zone, we may not need an + // ianaIdIndex from CLDR-derived tables: and the active backend may know + // some zones newer than our latest CLDR. + NamePrefixMatch found; for (const qsizetype locInd : indices) { const LocaleZoneData &locData = localeZoneData[locInd]; const LocaleZoneData &nextData = localeZoneData[locInd + 1]; @@ -928,11 +952,11 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale, QStringView city = row.exemplarCity().viewData(exemplarCityTable); if (textMatches(city)) { qsizetype length = cut + city.size() + suffix.size(); - if (length > best.nameLength) { - bool gotZone = row.ianaIdIndex == best.ianaIdIndex + if (length > found.nameLength) { + bool gotZone = row.ianaId() == found.ianaId // (cheap pre-test) || QTimeZone::isTimeZoneIdAvailable(row.ianaId().toByteArray()); if (gotZone) - best = { length, timeType, row.ianaIdIndex }; + found = { row.ianaId().toByteArray(), length, timeType }; } } } @@ -945,38 +969,16 @@ QTimeZonePrivate::findLongNamePrefix(QStringView text, const QLocale &locale, QString city = QString::fromLatin1(local.replace('_', ' ')); if (textMatches(city)) { qsizetype length = cut + city.size() + suffix.size(); - if (length > best.nameLength) { - // Have to find iana in ianaIdData. Although its entries - // from locale-independent data are nicely sorted, the - // rest are (sadly) not. - QByteArrayView run(ianaIdData, qstrlen(ianaIdData)); - // std::size includes the trailing '\0', so subtract one: - const char *stop = ianaIdData + std::size(ianaIdData) - 1; - while (run != iana) { - if (run.end() < stop) { // Step to the next: - run = QByteArrayView(run.end() + 1); - } else { - run = QByteArrayView(); - break; - } - } - if (!run.isEmpty()) { - Q_ASSERT(run == iana); - const auto ianaIdIndex = run.begin() - ianaIdData; - Q_ASSERT(ianaIdIndex <= (std::numeric_limits<quint16>::max)()); - best = { length, timeType, quint16(ianaIdIndex) }; - } - } + if (length > found.nameLength) + found = { iana, length, timeType }; } } // TODO: similar for territories, at least once localeName() does so. } } - if (best.ianaIdIndex != invalidIanaId) - return { QByteArray(ianaIdData + best.ianaIdIndex), best.nameLength, best.timeType }; #undef localeRows - return {}; // No match found. + return found; } QTimeZonePrivate::NamePrefixMatch diff --git a/src/corelib/time/qtimezoneprivate.cpp b/src/corelib/time/qtimezoneprivate.cpp index 556f7e21402..3f0254b1c5f 100644 --- a/src/corelib/time/qtimezoneprivate.cpp +++ b/src/corelib/time/qtimezoneprivate.cpp @@ -1177,7 +1177,7 @@ QUtcTimeZonePrivate::QUtcTimeZonePrivate(qint32 offsetSeconds) } Q_ASSERT(!name.isEmpty()); } else { // Fall back to a UTC-offset name: - name = isoOffsetFormat(offsetSeconds, QTimeZone::ShortName); + name = isoOffsetFormat(offsetSeconds, QTimeZone::OffsetName); id = name.toUtf8(); } init(id, offsetSeconds, name, name, QLocale::AnyTerritory, name); @@ -1292,12 +1292,16 @@ QString QUtcTimeZonePrivate::displayName(QTimeZone::TimeType timeType, if (!(name.startsWith("GMT"_L1) || name.startsWith("UTC"_L1)) || name.size() < 5) return false; // Fallback drops trailing ":00" minute: - QStringView tail{avoid}; + QStringView tail{avoid}; // TODO: deal with sign earlier ! Also: invisible Unicode ! tail = tail.sliced(3); - if (tail.endsWith(":00"_L1)) - tail = tail.chopped(3); if (name.sliced(3) == tail) return true; + while (tail.endsWith(":00"_L1)) + tail = tail.chopped(3); + while (name.endsWith(":00"_L1)) + name = name.chopped(3); + if (name == tail) + return true; // Accept U+2212 as minus sign: const QChar sign = name[3] == u'\u2212' ? u'-' : name[3]; // Fallback doesn't zero-pad hour: diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 804c28af372..9a86ded6efb 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -50,7 +50,7 @@ class Q_AUTOTEST_EXPORT QTimeZonePrivate : public QSharedData { // Nothing should be copy-assigning instances of either this or its derived // classes (only clone() should copy, using the copy-constructor): - bool operator=(const QTimeZonePrivate &) const = delete; + QTimeZonePrivate &operator=(const QTimeZonePrivate &) const = delete; protected: QTimeZonePrivate(const QTimeZonePrivate &other) = default; public: @@ -210,7 +210,7 @@ Q_DECLARE_TYPEINFO(QTimeZonePrivate::Data, Q_RELOCATABLE_TYPE); class Q_AUTOTEST_EXPORT QUtcTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QUtcTimeZonePrivate &) const = delete; + QUtcTimeZonePrivate &operator=(const QUtcTimeZonePrivate &) const = delete; QUtcTimeZonePrivate(const QUtcTimeZonePrivate &other); public: // Create default UTC time zone @@ -273,7 +273,7 @@ private: #if QT_CONFIG(timezone_tzdb) class QChronoTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QChronoTimeZonePrivate &) const = delete; + QChronoTimeZonePrivate &operator=(const QChronoTimeZonePrivate &) const = delete; QChronoTimeZonePrivate(const QChronoTimeZonePrivate &) = default; public: QChronoTimeZonePrivate(); @@ -307,7 +307,7 @@ private: #elif defined(Q_OS_DARWIN) class Q_AUTOTEST_EXPORT QMacTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QMacTimeZonePrivate &) const = delete; + QMacTimeZonePrivate &operator=(const QMacTimeZonePrivate &) const = delete; QMacTimeZonePrivate(const QMacTimeZonePrivate &other); public: // Create default time zone @@ -353,7 +353,7 @@ private: #elif defined(Q_OS_ANDROID) class QAndroidTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QAndroidTimeZonePrivate &) const = delete; + QAndroidTimeZonePrivate &operator=(const QAndroidTimeZonePrivate &) const = delete; QAndroidTimeZonePrivate(const QAndroidTimeZonePrivate &) = default; public: // Create default time zone @@ -388,7 +388,7 @@ private: QJniObject androidTimeZone; }; -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) struct QTzTransitionTime { qint64 atMSecsSinceEpoch; @@ -421,7 +421,7 @@ struct QTzTimeZoneCacheEntry class Q_AUTOTEST_EXPORT QTzTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QTzTimeZonePrivate &) const = delete; + QTzTimeZonePrivate &operator=(const QTzTimeZonePrivate &) const = delete; QTzTimeZonePrivate(const QTzTimeZonePrivate &) = default; public: // Create default time zone @@ -474,7 +474,7 @@ private: #elif QT_CONFIG(icu) class Q_AUTOTEST_EXPORT QIcuTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QIcuTimeZonePrivate &) const = delete; + QIcuTimeZonePrivate &operator=(const QIcuTimeZonePrivate &) const = delete; QIcuTimeZonePrivate(const QIcuTimeZonePrivate &other); public: // Create default time zone @@ -518,7 +518,7 @@ private: #elif defined(Q_OS_WIN) class Q_AUTOTEST_EXPORT QWinTimeZonePrivate final : public QTimeZonePrivate { - bool operator=(const QWinTimeZonePrivate &) const = delete; + QWinTimeZonePrivate &operator=(const QWinTimeZonePrivate &) const = delete; QWinTimeZonePrivate(const QWinTimeZonePrivate &) = default; public: struct QWinTransitionRule { diff --git a/src/corelib/tools/qiterator.qdoc b/src/corelib/tools/qiterator.qdoc index 3d8ea595167..d517457027a 100644 --- a/src/corelib/tools/qiterator.qdoc +++ b/src/corelib/tools/qiterator.qdoc @@ -165,7 +165,7 @@ position between the second and third item, and returns the second item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -211,7 +211,7 @@ position between the second and third item, returning the second item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items If you want to find all occurrences of a particular value, use findNext() in a loop. @@ -260,7 +260,7 @@ position between the second and third item, returning the second item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -321,7 +321,7 @@ position between the second and third item, returning the second item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items If you want to remove items as you iterate over the set, use remove(). @@ -718,7 +718,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -768,7 +768,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -819,7 +819,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items If you want to find all occurrences of a particular value, use findNext() in a loop. For example: @@ -867,7 +867,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -931,7 +931,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's how to iterate over the elements in reverse order: @@ -994,7 +994,7 @@ next() advances the iterator to the position between the second and third item; and so on. - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items If you want to find all occurrences of a particular value, use findNext() in a loop. For example: diff --git a/src/corelib/tools/quniquehandle_p.h b/src/corelib/tools/quniquehandle_p.h index 3ba557e838d..fd6ab693912 100644 --- a/src/corelib/tools/quniquehandle_p.h +++ b/src/corelib/tools/quniquehandle_p.h @@ -18,6 +18,7 @@ #include <QtCore/qtconfigmacros.h> #include <QtCore/qassert.h> #include <QtCore/qcompare.h> +#include <QtCore/qfunctionaltools_impl.h> #include <QtCore/qswap.h> #include <QtCore/qtclasshelpermacros.h> @@ -99,6 +100,42 @@ QT_BEGIN_NAMESPACE ... + Example 3: + + struct TempFileTraits { + using Type = FILE*; + + static Type invalidValue() { + return nullptr; + } + + static bool close(Type handle) { + return fclose(handle) == 0; + } + }; + + struct TempFileDeleter { + using Type = TempFileTraits::Type; + + void operator()(Type handle) { + if (handle != TempFileTraits::invalidValue()) { + TempFileTraits::close(handle); + if (path) + remove(path); + } + } + + const char* path{ nullptr }; + }; + + using TempFileHandle = QUniqueHandle<TempFileTraits, TempFileDeleter>; + + Usage: + + TempFileHandle tempFile(fopen("temp.bin", "wb"), TempFileDeleter{ "my_temp.bin" }); + + ... + NOTE: The QUniqueHandle assumes that closing a resource is guaranteed to succeed, and provides no support for handling failure to close a resource. It is therefore only recommended for use cases @@ -108,9 +145,32 @@ QT_BEGIN_NAMESPACE // clang-format off +namespace QtUniqueHandleTraits { + template <typename HandleTraits> -class QUniqueHandle +struct DefaultDeleter { + using Type = typename HandleTraits::Type; + + void operator()(Type handle) const noexcept + { + if (handle != HandleTraits::invalidValue()) { + const bool success = HandleTraits::close(handle); + Q_ASSERT(success); + } + } +}; + +} // namespace QtUniqueHandleTraits + +template <typename HandleTraits, typename Deleter = QtUniqueHandleTraits::DefaultDeleter<HandleTraits>> +class QUniqueHandle : private QtPrivate::CompactStorage<Deleter> +{ + using Storage = QtPrivate::CompactStorage<Deleter>; + + template <typename D> + using if_default_constructible = std::enable_if_t<std::is_default_constructible_v<D>, bool>; + public: using Type = typename HandleTraits::Type; static_assert(std::is_nothrow_default_constructible_v<Type>); @@ -120,6 +180,11 @@ public: static_assert(std::is_nothrow_copy_assignable_v<Type>); static_assert(std::is_nothrow_move_assignable_v<Type>); static_assert(std::is_nothrow_destructible_v<Type>); + static_assert(std::is_nothrow_copy_constructible_v<Deleter>); + static_assert(std::is_nothrow_move_constructible_v<Deleter>); + static_assert(std::is_nothrow_copy_assignable_v<Deleter>); + static_assert(std::is_nothrow_move_assignable_v<Deleter>); + static_assert(std::is_nothrow_destructible_v<Deleter>); static_assert(noexcept(std::declval<Type>() == std::declval<Type>())); static_assert(noexcept(std::declval<Type>() != std::declval<Type>())); static_assert(noexcept(std::declval<Type>() < std::declval<Type>())); @@ -127,16 +192,24 @@ public: static_assert(noexcept(std::declval<Type>() > std::declval<Type>())); static_assert(noexcept(std::declval<Type>() >= std::declval<Type>())); - QUniqueHandle() = default; - - explicit QUniqueHandle(const Type &handle) noexcept + template <if_default_constructible<Deleter> = true> + explicit QUniqueHandle(const Type& handle = HandleTraits::invalidValue()) noexcept : m_handle{ handle } {} - QUniqueHandle(QUniqueHandle &&other) noexcept - : m_handle{ other.release() } + QUniqueHandle(const Type &handle, const Deleter &deleter) noexcept + : Storage{ deleter }, m_handle{ handle } + {} + + QUniqueHandle(const Type &handle, Deleter &&deleter) noexcept + : Storage{ std::move(deleter) }, m_handle{ handle } {} + QUniqueHandle(QUniqueHandle &&other) noexcept + : Storage{ std::move(other.deleter()) }, m_handle{ other.release() } + { + } + ~QUniqueHandle() noexcept { close(); @@ -145,6 +218,7 @@ public: void swap(QUniqueHandle &other) noexcept { qSwap(m_handle, other.m_handle); + qSwap(deleter(), other.deleter()); } QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_MOVE_AND_SWAP(QUniqueHandle) @@ -168,6 +242,16 @@ public: return m_handle; } + [[nodiscard]] Deleter& deleter() noexcept + { + return Storage::object(); + } + + [[nodiscard]] const Deleter& deleter() const noexcept + { + return Storage::object(); + } + void reset(const Type& handle = HandleTraits::invalidValue()) noexcept { if (handle == m_handle) @@ -193,8 +277,7 @@ public: if (!isValid()) return; - const bool success = HandleTraits::close(m_handle); - Q_ASSERT(success); + deleter()(m_handle); m_handle = HandleTraits::invalidValue(); } @@ -222,8 +305,8 @@ private: // clang-format on -template <typename Trait> -void swap(QUniqueHandle<Trait> &lhs, QUniqueHandle<Trait> &rhs) noexcept +template <typename Trait, typename Deleter> +void swap(QUniqueHandle<Trait, Deleter> &lhs, QUniqueHandle<Trait, Deleter> &rhs) noexcept { lhs.swap(rhs); } diff --git a/src/gui/configure.cmake b/src/gui/configure.cmake index ad76bb095a0..5001f2deeec 100644 --- a/src/gui/configure.cmake +++ b/src/gui/configure.cmake @@ -1217,17 +1217,6 @@ qt_feature("xcb-egl-plugin" PRIVATE CONDITION QT_FEATURE_egl AND QT_FEATURE_opengl EMIT_IF QT_FEATURE_xcb ) -qt_feature("xcb-native-painting" PRIVATE - LABEL "Native painting (experimental)" - AUTODETECT OFF - CONDITION QT_FEATURE_xcb_xlib AND QT_FEATURE_fontconfig AND XRender_FOUND - EMIT_IF QT_FEATURE_xcb -) -qt_feature("xrender" PRIVATE - LABEL "XRender for native painting" - CONDITION QT_FEATURE_xcb_native_painting - EMIT_IF QT_FEATURE_xcb AND QT_FEATURE_xcb_native_painting -) qt_feature("xcb-xlib" PRIVATE LABEL "XCB Xlib" CONDITION QT_FEATURE_xlib AND X11_XCB_FOUND diff --git a/src/gui/doc/src/richtext.qdoc b/src/gui/doc/src/richtext.qdoc index 2fa49a31e03..f94c436bd80 100644 --- a/src/gui/doc/src/richtext.qdoc +++ b/src/gui/doc/src/richtext.qdoc @@ -1047,6 +1047,12 @@ \li \c type (\c 1, \c a, \c A, \c square, \c disc, \c circle) \endlist + Additionally, the following attribute is supported by the \c ol tag: + + \list + \li \c start + \endlist + \section1 Table Cell Attributes The following attributes are supported by the \c td and \c th diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 6ba113ef8fb..4a8b409379c 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -47,6 +47,9 @@ #include <memory> +#define QT_XFORM_TYPE_MSBFIRST 0 +#define QT_XFORM_TYPE_LSBFIRST 1 + QT_BEGIN_NAMESPACE class QCmyk32; @@ -4447,6 +4450,8 @@ int QImage::metric(PaintDeviceMetric metric) const trigx += m11; \ trigy += m12; // END OF MACRO + +static bool qt_xForm_helper(const QTransform &trueMat, int xoffset, int type, int depth, uchar *dptr, qsizetype dbpl, int p_inc, int dHeight, const uchar *sptr, qsizetype sbpl, int sWidth, int sHeight) diff --git a/src/gui/image/qplatformpixmap.h b/src/gui/image/qplatformpixmap.h index 5621afa4da5..3346ca85375 100644 --- a/src/gui/image/qplatformpixmap.h +++ b/src/gui/image/qplatformpixmap.h @@ -33,7 +33,7 @@ public: enum ClassId { RasterClass, DirectFBClass, BlitterClass, Direct2DClass, - X11Class, CustomClass = 1024 }; + CustomClass = 1024 }; QPlatformPixmap(PixelType pixelType, int classId); virtual ~QPlatformPixmap(); @@ -111,7 +111,6 @@ protected: private: friend class QPixmap; - friend class QX11PlatformPixmap; friend class QImagePixmapCleanupHooks; // Needs to set is_cached int detach_no; @@ -122,10 +121,6 @@ private: uint is_cached; }; -# define QT_XFORM_TYPE_MSBFIRST 0 -# define QT_XFORM_TYPE_LSBFIRST 1 -Q_GUI_EXPORT bool qt_xForm_helper(const QTransform&, int, int, int, uchar*, qsizetype, int, int, const uchar*, qsizetype, int, int); - QT_END_NAMESPACE #endif // QPLATFORMPIXMAP_H diff --git a/src/gui/itemmodels/qfilesystemmodel.cpp b/src/gui/itemmodels/qfilesystemmodel.cpp index 622c2e5bc92..9eb31f1b6d4 100644 --- a/src/gui/itemmodels/qfilesystemmodel.cpp +++ b/src/gui/itemmodels/qfilesystemmodel.cpp @@ -1787,14 +1787,17 @@ bool QFileSystemModel::event(QEvent *event) bool QFileSystemModel::rmdir(const QModelIndex &aindex) { + Q_D(QFileSystemModel); + QString path = filePath(aindex); const bool success = QDir().rmdir(path); -#if QT_CONFIG(filesystemwatcher) if (success) { - QFileSystemModelPrivate * d = const_cast<QFileSystemModelPrivate*>(d_func()); +#if QT_CONFIG(filesystemwatcher) d->fileInfoGatherer->removePath(path); - } #endif + QFileSystemModelPrivate::QFileSystemNode *parentNode = d->node(aindex.parent()); + d->removeNode(parentNode, fileName(aindex)); + } return success; } diff --git a/src/gui/kernel/qevent.cpp b/src/gui/kernel/qevent.cpp index ae730a9b3af..7b1e76cc78f 100644 --- a/src/gui/kernel/qevent.cpp +++ b/src/gui/kernel/qevent.cpp @@ -1146,6 +1146,33 @@ Q_IMPL_POINTER_EVENT(QHoverEvent) */ /*! + \property QWheelEvent::device + \brief the device from which the wheel event originated + + \sa pointingDevice() +*/ + +/*! + \property QWheelEvent::inverted + \since 5.7 + \brief whether the delta values delivered with the event are inverted + + Normally, a vertical wheel will produce a QWheelEvent with positive delta + values if the top of the wheel is rotating away from the hand operating it. + Similarly, a horizontal wheel movement will produce a QWheelEvent with + positive delta values if the top of the wheel is moved to the left. + + However, on some platforms this is configurable, so that the same + operations described above will produce negative delta values (but with the + same magnitude). With the inverted property a wheel event consumer can + choose to always follow the direction of the wheel, regardless of the + system settings, but only for specific widgets. + + \note Many platforms provide no such information. On such platforms + \l inverted always returns false. +*/ + +/*! \fn bool QWheelEvent::inverted() const \since 5.7 @@ -1236,6 +1263,24 @@ bool QWheelEvent::isEndEvent() const #endif // QT_CONFIG(wheelevent) /*! + \property QWheelEvent::pixelDelta + \brief the scrolling distance in pixels on screen + + This value is provided on platforms that support high-resolution + pixel-based delta values, such as \macos. The value should be used + directly to scroll content on screen. + + \note On platforms that support scrolling \l{phase()}{phases}, the delta + may be null when scrolling is about to begin (Qt::ScrollBegin) or has + ended (Qt::ScrollEnd). + + \note On X11 this value is driver-specific and unreliable, use + angleDelta() instead. + + \sa angleDelta() +*/ + +/*! \fn QPoint QWheelEvent::pixelDelta() const Returns the scrolling distance in pixels on screen. This value is @@ -1256,6 +1301,27 @@ bool QWheelEvent::isEndEvent() const */ /*! + \property QWheelEvent::angleDelta + \brief the relative amount that the wheel was rotated, in eighths of a degree + + A positive value indicates that the wheel was rotated forwards away from the + user; a negative value indicates that the wheel was rotated backwards toward + the user. \c angleDelta().y() provides the angle through which the common + vertical mouse wheel was rotated since the previous event. \c angleDelta().x() + provides the angle through which the horizontal mouse wheel was rotated, if + the mouse has a horizontal wheel; otherwise it stays at zero. + + Most mouse types work in steps of 15 degrees, in which case the delta value + is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. + + \note On platforms that support scrolling \l{phase()}{phases}, the delta + may be null when scrolling is about to begin (Qt::ScrollBegin) or has + ended (Qt::ScrollEnd). + + \sa pixelDelta() +*/ + +/*! \fn QPoint QWheelEvent::angleDelta() const Returns the relative amount that the wheel was rotated, in eighths of a @@ -1294,6 +1360,15 @@ bool QWheelEvent::isEndEvent() const */ /*! + \property QWheelEvent::phase + \since 5.2 + \brief the scrolling phase of this wheel event + + \note The Qt::ScrollBegin and Qt::ScrollEnd phases are currently + supported only on \macos. +*/ + +/*! \fn Qt::ScrollPhase QWheelEvent::phase() const \since 5.2 diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 518843ffcbd..741b089306e 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -243,6 +243,7 @@ static void initThemeHints() touchDoubleTapDistance = QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::TouchDoubleTapDistance).toInt(); } +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) static bool checkNeedPortalSupport() { #if QT_CONFIG(dbus) @@ -251,6 +252,7 @@ static bool checkNeedPortalSupport() return false; #endif // QT_CONFIG(dbus) } +#endif // Using aggregate initialization instead of ctor so we can have a POD global static #define Q_WINDOW_GEOMETRY_SPECIFICATION_INITIALIZER { Qt::TopLeftCorner, -1, -1, -1, -1 } @@ -1349,11 +1351,13 @@ static void init_platform(const QString &pluginNamesWithArguments, const QString themeNames.append(platformThemeName); } +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) // 2) Special case - check whether it's a flatpak or snap app to use xdg-desktop-portal platform theme for portals support if (checkNeedPortalSupport()) { qCDebug(lcQpaTheme) << "Adding xdgdesktopportal to list of theme names"; themeNames.append(QStringLiteral("xdgdesktopportal")); } +#endif // 3) Ask the platform integration for a list of theme names const auto platformIntegrationThemeNames = QGuiApplicationPrivate::platform_integration->themeNames(); diff --git a/src/gui/kernel/qinputdevice.cpp b/src/gui/kernel/qinputdevice.cpp index 8e5c38922e0..caed0fc6135 100644 --- a/src/gui/kernel/qinputdevice.cpp +++ b/src/gui/kernel/qinputdevice.cpp @@ -187,6 +187,11 @@ QString QInputDevice::name() const } /*! + \property QInputDevice::type + \brief the device type +*/ + +/*! Returns the device type. */ QInputDevice::DeviceType QInputDevice::type() const diff --git a/src/gui/kernel/qpointingdevice.cpp b/src/gui/kernel/qpointingdevice.cpp index dcce354688a..7062340b287 100644 --- a/src/gui/kernel/qpointingdevice.cpp +++ b/src/gui/kernel/qpointingdevice.cpp @@ -241,6 +241,11 @@ void QPointingDevice::setMaximumTouchPoints(int c) #endif // QT_DEPRECATED_SINCE(6, 0) /*! + \property QPointingDevice::pointerType + \brief the pointer type +*/ + +/*! Returns the pointer type. */ QPointingDevice::PointerType QPointingDevice::pointerType() const @@ -250,6 +255,12 @@ QPointingDevice::PointerType QPointingDevice::pointerType() const } /*! + \property QPointingDevice::maximumPoints + \brief the maximum number of simultaneous touch points (fingers) that + can be detected +*/ + +/*! Returns the maximum number of simultaneous touch points (fingers) that can be detected. */ @@ -260,6 +271,11 @@ int QPointingDevice::maximumPoints() const } /*! + \property QPointingDevice::buttonCount + \brief the maximum number of on-device buttons that can be detected +*/ + +/*! Returns the maximum number of on-device buttons that can be detected. */ int QPointingDevice::buttonCount() const @@ -269,6 +285,13 @@ int QPointingDevice::buttonCount() const } /*! + \property QPointingDevice::uniqueId + \brief a unique ID (of dubious utility) for the device + + You probably should rather be concerned with QPointerEventPoint::uniqueId(). +*/ + +/*! Returns a unique ID (of dubious utility) for the device. You probably should rather be concerned with QPointerEventPoint::uniqueId(). diff --git a/src/gui/painting/qtextureglyphcache.cpp b/src/gui/painting/qtextureglyphcache.cpp index d27539b2419..c9df5d28a45 100644 --- a/src/gui/painting/qtextureglyphcache.cpp +++ b/src/gui/painting/qtextureglyphcache.cpp @@ -301,6 +301,8 @@ void QImageTextureGlyphCache::fillTexture(const Coord &c, const QFixedPoint &subPixelPosition) { QImage mask = textureMapForGlyph(g, subPixelPosition); + if (mask.isNull()) + return; #ifdef CACHE_DEBUG printf("fillTexture of %dx%d at %d,%d in the cache of %dx%d\n", c.w, c.h, c.x, c.y, m_image.width(), m_image.height()); diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 62b7ab6efb0..5590f2fb431 100644 --- a/src/gui/rhi/qrhi.cpp +++ b/src/gui/rhi/qrhi.cpp @@ -7186,6 +7186,26 @@ QRhiResource::Type QRhiGraphicsPipeline::resourceType() const */ /*! + \fn bool QRhiGraphicsPipeline::hasDepthClamp() const + \return true if depth clamp is enabled. + + \since 6.11 + */ + +/*! + \fn void QRhiGraphicsPipeline::setDepthClamp(bool enable) + + Enables depth clamping when \a enable is true. When depth clamping is + enabled, primitives that would otherwise be clipped by the near or far + clip plane are rasterized and their depth values are clamped to the + depth range. When disabled (the default), such primitives are clipped. + + \note This setting is ignored on OpenGL ES. + + \since 6.11 + */ + +/*! \fn QRhiGraphicsPipeline::CompareOp QRhiGraphicsPipeline::depthOp() const \return the depth comparison function. */ diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h index b5a3f7b43be..5ee2a9acd13 100644 --- a/src/gui/rhi/qrhi.h +++ b/src/gui/rhi/qrhi.h @@ -1453,6 +1453,9 @@ public: bool hasDepthWrite() const { return m_depthWrite; } void setDepthWrite(bool enable) { m_depthWrite = enable; } + bool hasDepthClamp() const { return m_depthClamp; } + void setDepthClamp(bool enable) { m_depthClamp = enable; } + CompareOp depthOp() const { return m_depthOp; } void setDepthOp(CompareOp op) { m_depthOp = op; } @@ -1524,6 +1527,7 @@ protected: QVarLengthArray<TargetBlend, 8> m_targetBlends; bool m_depthTest = false; bool m_depthWrite = false; + bool m_depthClamp = false; CompareOp m_depthOp = Less; bool m_stencilTest = false; StencilOpState m_stencilFront; diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp index a5f860e7724..1441be24043 100644 --- a/src/gui/rhi/qrhid3d11.cpp +++ b/src/gui/rhi/qrhid3d11.cpp @@ -4673,7 +4673,7 @@ bool QD3D11GraphicsPipeline::create() rastDesc.FrontCounterClockwise = m_frontFace == CCW; rastDesc.DepthBias = m_depthBias; rastDesc.SlopeScaledDepthBias = m_slopeScaledDepthBias; - rastDesc.DepthClipEnable = true; + rastDesc.DepthClipEnable = m_depthClamp ? FALSE : TRUE; rastDesc.ScissorEnable = m_flags.testFlag(UsesScissor); rastDesc.MultisampleEnable = rhiD->effectiveSampleDesc(m_sampleCount).Count > 1; HRESULT hr = rhiD->dev->CreateRasterizerState(&rastDesc, &rastState); diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp index 4f09b3c136b..b68b65b1063 100644 --- a/src/gui/rhi/qrhid3d12.cpp +++ b/src/gui/rhi/qrhid3d12.cpp @@ -6263,7 +6263,7 @@ bool QD3D12GraphicsPipeline::create() stream.rasterizerState.object.FrontCounterClockwise = m_frontFace == CCW; stream.rasterizerState.object.DepthBias = m_depthBias; stream.rasterizerState.object.SlopeScaledDepthBias = m_slopeScaledDepthBias; - stream.rasterizerState.object.DepthClipEnable = TRUE; + stream.rasterizerState.object.DepthClipEnable = m_depthClamp ? FALSE : TRUE; stream.rasterizerState.object.MultisampleEnable = sampleDesc.Count > 1; stream.depthStencilState.object.DepthEnable = m_depthTest; diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 15f5cd8f7e8..1308d4362e5 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -585,6 +585,10 @@ QT_BEGIN_NAMESPACE #define GL_PROGRAM 0x82E2 #endif +#ifndef GL_DEPTH_CLAMP +#define GL_DEPTH_CLAMP 0x864F +#endif + /*! Constructs a new QRhiGles2InitParams. @@ -998,6 +1002,13 @@ bool QRhiGles2::create(QRhi::Flags flags) } if (caps.gles) + caps.depthClamp = false; + else + caps.depthClamp = caps.ctxMajor > 3 || (caps.ctxMajor == 3 && caps.ctxMinor >= 2); // Desktop 3.2 + if (!caps.depthClamp) + caps.depthClamp = ctx->hasExtension("GL_EXT_depth_clamp") || ctx->hasExtension("GL_ARB_depth_clamp"); + + if (caps.gles) caps.textureCompareMode = caps.ctxMajor >= 3; // ES 3.0 else caps.textureCompareMode = true; @@ -3959,6 +3970,7 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb) break; case QGles2CommandBuffer::Command::InvalidateFramebuffer: if (caps.gles && caps.ctxMajor >= 3) { + f->glBindFramebuffer(GL_FRAMEBUFFER, cmd.args.invalidateFramebuffer.fbo); f->glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, cmd.args.invalidateFramebuffer.attCount, cmd.args.invalidateFramebuffer.att); @@ -4095,6 +4107,15 @@ void QRhiGles2::executeBindGraphicsPipeline(QGles2CommandBuffer *cbD, QGles2Grap f->glDepthMask(depthWrite); } + const bool depthClamp = psD->m_depthClamp; + if (caps.depthClamp && (forceUpdate || depthClamp != state.depthClamp)) { + state.depthClamp = depthClamp; + if (depthClamp) + f->glEnable(GL_DEPTH_CLAMP); + else + f->glDisable(GL_DEPTH_CLAMP); + } + const GLenum depthFunc = toGlCompareOp(psD->m_depthOp); if (forceUpdate || depthFunc != state.depthFunc) { state.depthFunc = depthFunc; @@ -4881,6 +4902,7 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource if (mayDiscardDepthStencil) { QGles2CommandBuffer::Command &cmd(cbD->commands.get()); cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer; + cmd.args.invalidateFramebuffer.fbo = rtTex->framebuffer; if (caps.needsDepthStencilCombinedAttach) { cmd.args.invalidateFramebuffer.attCount = 1; cmd.args.invalidateFramebuffer.att[0] = GL_DEPTH_STENCIL_ATTACHMENT; diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h index 725e71a3665..70dd96a8dc7 100644 --- a/src/gui/rhi/qrhigles2_p.h +++ b/src/gui/rhi/qrhigles2_p.h @@ -547,6 +547,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer GLbitfield barriers; } barrier; struct { + GLuint fbo; int attCount; GLenum att[3]; } invalidateFramebuffer; @@ -592,6 +593,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer } blend[16]; bool depthTest; bool depthWrite; + bool depthClamp; GLenum depthFunc; bool stencilTest; GLuint stencilReadMask; @@ -1074,6 +1076,7 @@ public: uint baseVertex : 1; uint compute : 1; uint textureCompareMode : 1; + uint depthClamp : 1; uint properMapBuffer : 1; uint nonBaseLevelFramebufferTexture : 1; uint texelFetch : 1; diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm index c3f1031b44b..7fa05f69232 100644 --- a/src/gui/rhi/qrhimetal.mm +++ b/src/gui/rhi/qrhimetal.mm @@ -403,6 +403,7 @@ struct QMetalGraphicsPipelineData MTLWinding winding; MTLCullMode cullMode; MTLTriangleFillMode triangleFillMode; + MTLDepthClipMode depthClipMode; float depthBias; float slopeScaledDepthBias; QMetalShader vs; @@ -1477,7 +1478,6 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand [cbD->d->currentRenderPassEncoder setDepthStencilState: d->ds]; cbD->d->currentDepthStencilState = d->ds; } - if (cbD->currentCullMode == -1 || d->cullMode != uint(cbD->currentCullMode)) { [cbD->d->currentRenderPassEncoder setCullMode: d->cullMode]; cbD->currentCullMode = int(d->cullMode); @@ -1486,6 +1486,10 @@ void QMetalGraphicsPipeline::makeActiveForCurrentRenderPassEncoder(QMetalCommand [cbD->d->currentRenderPassEncoder setTriangleFillMode: d->triangleFillMode]; cbD->currentTriangleFillMode = int(d->triangleFillMode); } + if (cbD->currentDepthClipMode == -1 || d->depthClipMode != uint(cbD->currentDepthClipMode)) { + [cbD->d->currentRenderPassEncoder setDepthClipMode: d->depthClipMode]; + cbD->currentDepthClipMode = int(d->depthClipMode); + } if (cbD->currentFrontFaceWinding == -1 || d->winding != uint(cbD->currentFrontFaceWinding)) { [cbD->d->currentRenderPassEncoder setFrontFacingWinding: d->winding]; cbD->currentFrontFaceWinding = int(d->winding); @@ -5035,6 +5039,7 @@ void QMetalGraphicsPipeline::mapStates() d->winding = m_frontFace == CCW ? MTLWindingCounterClockwise : MTLWindingClockwise; d->cullMode = toMetalCullMode(m_cullMode); d->triangleFillMode = toMetalTriangleFillMode(m_polygonMode); + d->depthClipMode = m_depthClamp ? MTLDepthClipModeClamp : MTLDepthClipModeClip; d->depthBias = float(m_depthBias); d->slopeScaledDepthBias = m_slopeScaledDepthBias; } @@ -6257,6 +6262,7 @@ void QMetalCommandBuffer::resetPerPassCachedState() currentIndexFormat = QRhiCommandBuffer::IndexUInt16; currentCullMode = -1; currentTriangleFillMode = -1; + currentDepthClipMode = -1; currentFrontFaceWinding = -1; currentDepthBiasValues = { 0.0f, 0.0f }; diff --git a/src/gui/rhi/qrhimetal_p.h b/src/gui/rhi/qrhimetal_p.h index 7c19ae9e767..6649a6cd304 100644 --- a/src/gui/rhi/qrhimetal_p.h +++ b/src/gui/rhi/qrhimetal_p.h @@ -299,6 +299,7 @@ struct QMetalCommandBuffer : public QRhiCommandBuffer QRhiCommandBuffer::IndexFormat currentIndexFormat; int currentCullMode; int currentTriangleFillMode; + int currentDepthClipMode; int currentFrontFaceWinding; std::pair<float, float> currentDepthBiasValues; diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp index c5167a6e7de..481ffd57b5d 100644 --- a/src/gui/rhi/qrhivulkan.cpp +++ b/src/gui/rhi/qrhivulkan.cpp @@ -949,6 +949,8 @@ bool QRhiVulkan::create(QRhi::Flags flags) // elsewhere states that the minimum bufferOffset is 4... texbufAlign = qMax<VkDeviceSize>(4, physDevProperties.limits.optimalBufferCopyOffsetAlignment); + caps.depthClamp = physDevFeatures.depthClamp; + caps.wideLines = physDevFeatures.wideLines; caps.texture3DSliceAs2D = caps.apiVersion >= QVersionNumber(1, 1); @@ -8402,6 +8404,8 @@ bool QVkGraphicsPipeline::create() VkPipelineRasterizationStateCreateInfo rastInfo = {}; rastInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + if (m_depthClamp && rhiD->caps.depthClamp) + rastInfo.depthClampEnable = m_depthClamp; rastInfo.cullMode = toVkCullMode(m_cullMode); rastInfo.frontFace = toVkFrontFace(m_frontFace); if (m_depthBias != 0 || !qFuzzyIsNull(m_slopeScaledDepthBias)) { diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h index d141a84c5fb..1e9318513fd 100644 --- a/src/gui/rhi/qrhivulkan_p.h +++ b/src/gui/rhi/qrhivulkan_p.h @@ -936,6 +936,7 @@ public: struct { bool compute = false; + bool depthClamp = false; bool wideLines = false; bool debugUtils = false; bool vertexAttribDivisor = false; diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp index b50dc4a43bf..7bbc9cf63db 100644 --- a/src/gui/text/qfont.cpp +++ b/src/gui/text/qfont.cpp @@ -257,6 +257,19 @@ QFontEngine *QFontPrivate::engineForScript(int script) const return QT_FONT_ENGINE_FROM_DATA(engineData, script); } +QFontEngine *QFontPrivate::engineForCharacter(char32_t c, EngineQueryOptions opt) const +{ + const bool smallCaps = !(opt & EngineQueryOption::IgnoreSmallCapsEngine); + const auto script = QChar::script(c); + QFontEngine *engine; + if (smallCaps && capital == QFont::SmallCaps && QChar::isLower(c)) + engine = smallCapsFontPrivate()->engineForScript(script); + else + engine = engineForScript(script); + Q_ASSERT(engine != nullptr); + return engine; +} + void QFontPrivate::alterCharForCapitalization(QChar &c) const { switch (capital) { case QFont::AllUppercase: diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index 75550439521..27bc2a6a7cc 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -163,6 +163,11 @@ private: class Q_GUI_EXPORT QFontPrivate { public: + enum class EngineQueryOption { + Default = 0, + IgnoreSmallCapsEngine = 0x1, + }; + Q_DECLARE_FLAGS(EngineQueryOptions, EngineQueryOption) QFontPrivate(); QFontPrivate(const QFontPrivate &other); @@ -170,6 +175,7 @@ public: ~QFontPrivate(); QFontEngine *engineForScript(int script) const; + QFontEngine *engineForCharacter(char32_t c, EngineQueryOptions opt = {}) const; void alterCharForCapitalization(QChar &c) const; QAtomicInt ref; @@ -208,6 +214,7 @@ public: void unsetVariableAxis(QFont::Tag tag); bool hasVariableAxis(QFont::Tag tag, float value) const; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(QFontPrivate::EngineQueryOptions) class Q_GUI_EXPORT QFontCache : public QObject diff --git a/src/gui/text/qfontmetrics.cpp b/src/gui/text/qfontmetrics.cpp index 4ea21c9f0f3..c4403a16c6d 100644 --- a/src/gui/text/qfontmetrics.cpp +++ b/src/gui/text/qfontmetrics.cpp @@ -410,9 +410,8 @@ bool QFontMetrics::inFont(QChar ch) const */ bool QFontMetrics::inFontUcs4(uint ucs4) const { - const int script = QChar::script(ucs4); - QFontEngine *engine = d->engineForScript(script); - Q_ASSERT(engine != nullptr); + constexpr auto Ignore = QFontPrivate::EngineQueryOption::IgnoreSmallCapsEngine; + QFontEngine *engine = d->engineForCharacter(ucs4, Ignore); if (engine->type() == QFontEngine::Box) return false; return engine->canRender(ucs4); @@ -432,13 +431,7 @@ bool QFontMetrics::inFontUcs4(uint ucs4) const */ int QFontMetrics::leftBearing(QChar ch) const { - const int script = ch.script(); - QFontEngine *engine; - if (d->capital == QFont::SmallCaps && ch.isLower()) - engine = d->smallCapsFontPrivate()->engineForScript(script); - else - engine = d->engineForScript(script); - Q_ASSERT(engine != nullptr); + QFontEngine *engine = d->engineForCharacter(ch.unicode()); if (engine->type() == QFontEngine::Box) return 0; @@ -465,12 +458,7 @@ int QFontMetrics::leftBearing(QChar ch) const */ int QFontMetrics::rightBearing(QChar ch) const { - const int script = ch.script(); - QFontEngine *engine; - if (d->capital == QFont::SmallCaps && ch.isLower()) - engine = d->smallCapsFontPrivate()->engineForScript(script); - else - engine = d->engineForScript(script); + QFontEngine *engine = d->engineForCharacter(ch.unicode()); Q_ASSERT(engine != nullptr); if (engine->type() == QFontEngine::Box) return 0; @@ -574,13 +562,7 @@ int QFontMetrics::horizontalAdvance(QChar ch) const if (QChar::category(ch.unicode()) == QChar::Mark_NonSpacing) return 0; - const int script = ch.script(); - QFontEngine *engine; - if (d->capital == QFont::SmallCaps && ch.isLower()) - engine = d->smallCapsFontPrivate()->engineForScript(script); - else - engine = d->engineForScript(script); - Q_ASSERT(engine != nullptr); + QFontEngine *engine = d->engineForCharacter(ch.unicode()); d->alterCharForCapitalization(ch); @@ -684,13 +666,7 @@ QRect QFontMetrics::boundingRect(const QString &text, const QTextOption &option) */ QRect QFontMetrics::boundingRect(QChar ch) const { - const int script = ch.script(); - QFontEngine *engine; - if (d->capital == QFont::SmallCaps && ch.isLower()) - engine = d->smallCapsFontPrivate()->engineForScript(script); - else - engine = d->engineForScript(script); - Q_ASSERT(engine != nullptr); + QFontEngine *engine = d->engineForCharacter(ch.unicode()); d->alterCharForCapitalization(ch); @@ -1345,13 +1321,7 @@ bool QFontMetricsF::inFontUcs4(uint ucs4) const */ qreal QFontMetricsF::leftBearing(QChar ch) const { - const int script = ch.script(); - QFontEngine *engine; - if (d->capital == QFont::SmallCaps && ch.isLower()) - engine = d->smallCapsFontPrivate()->engineForScript(script); - else - engine = d->engineForScript(script); - Q_ASSERT(engine != nullptr); + QFontEngine *engine = d->engineForCharacter(ch.unicode()); if (engine->type() == QFontEngine::Box) return 0; diff --git a/src/gui/text/qfontvariableaxis.cpp b/src/gui/text/qfontvariableaxis.cpp index be83a3e02ce..bbc14f4cd11 100644 --- a/src/gui/text/qfontvariableaxis.cpp +++ b/src/gui/text/qfontvariableaxis.cpp @@ -60,6 +60,18 @@ QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QFontVariableAxisPrivate) QFontVariableAxis::QFontVariableAxis(const QFontVariableAxis &axis) = default; /*! + \property QFontVariableAxis::tag + \brief the tag of the axis + + This is a four-character sequence which identifies the axis. Certain tags + have standardized meanings, such as "wght" (weight) and "wdth" (width), + but any sequence of four latin-1 characters is a valid tag. By convention, + non-standard/custom axes are denoted by tags in all uppercase. + + \sa QFont::setVariableAxis(), name() +*/ + +/*! Returns the tag of the axis. This is a four-character sequence which identifies the axis. Certain tags have standardized meanings, such as "wght" (weight) and "wdth" (width), but any sequence of four latin-1 characters is a valid tag. By convention, non-standard/custom axes @@ -91,6 +103,13 @@ void QFontVariableAxis::setTag(QFont::Tag tag) } /*! + \property QFontVariableAxis::name + \brief the name of the axis, if provided by the font + + \sa tag() +*/ + +/*! Returns the name of the axis, if provided by the font. \sa tag() @@ -153,6 +172,15 @@ void QFontVariableAxis::setMinimumValue(qreal minimumValue) } /*! + \property QFontVariableAxis::maximumValue + \brief the maximum value of the axis + + Setting the axis to a value which is higher than this is not supported. + + \sa minimumValue(), defaultValue() +*/ + +/*! Returns the maximum value of the axis. Setting the axis to a value which is higher than this is not supported. @@ -182,6 +210,16 @@ void QFontVariableAxis::setMaximumValue(qreal maximumValue) } /*! + \property QFontVariableAxis::defaultValue + \brief the default value of the axis + + This is the value the axis will have if none has been provided in the + QFont query. + + \sa minimumValue(), maximumValue() +*/ + +/*! Returns the default value of the axis. This is the value the axis will have if none has been provided in the QFont query. diff --git a/src/gui/text/qtextengine.cpp b/src/gui/text/qtextengine.cpp index 11c79f23d25..29fda652ef6 100644 --- a/src/gui/text/qtextengine.cpp +++ b/src/gui/text/qtextengine.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only // Qt-Security score:critical reason:data-parser +#include <QtCore/private/qflatmap_p.h> #include <QtGui/private/qtguiglobal_p.h> #include "qdebug.h" #include "qtextformat.h" @@ -1613,11 +1614,15 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st { uint glyphs_shaped = 0; - hb_buffer_t *buffer = hb_buffer_create(); - hb_buffer_set_unicode_funcs(buffer, hb_qt_get_unicode_funcs()); + if (!buffer) { + buffer = hb_buffer_create(); + hb_buffer_set_unicode_funcs(buffer, hb_qt_get_unicode_funcs()); + } + hb_buffer_pre_allocate(buffer, itemLength); if (Q_UNLIKELY(!hb_buffer_allocation_successful(buffer))) { hb_buffer_destroy(buffer); + buffer = nullptr; return 0; } @@ -1671,26 +1676,26 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st bool dontLigate = hasLetterSpacing && !scriptRequiresOpenType; - QHash<QFont::Tag, quint32> features; - features.insert(QFont::Tag("kern"), !!kerningEnabled); + QVarLengthFlatMap<QFont::Tag, hb_feature_t, 16> features; + auto insertFeature = [&features](QFont::Tag tag, quint32 value) { + features.insert(tag, { tag.value(), + value, + HB_FEATURE_GLOBAL_START, + HB_FEATURE_GLOBAL_END }); + }; + // fontFeatures have precedence + for (const auto &[tag, value]: fontFeatures.asKeyValueRange()) + insertFeature(tag, value); + insertFeature(QFont::Tag("kern"), !!kerningEnabled); if (dontLigate) { - features.insert(QFont::Tag("liga"), false); - features.insert(QFont::Tag("clig"), false); - features.insert(QFont::Tag("dlig"), false); - features.insert(QFont::Tag("hlig"), false); - } - features.insert(fontFeatures); - - QVarLengthArray<hb_feature_t, 16> featureArray; - for (auto it = features.constBegin(); it != features.constEnd(); ++it) { - featureArray.append({ it.key().value(), - it.value(), - HB_FEATURE_GLOBAL_START, - HB_FEATURE_GLOBAL_END }); + insertFeature(QFont::Tag("liga"), false); + insertFeature(QFont::Tag("clig"), false); + insertFeature(QFont::Tag("dlig"), false); + insertFeature(QFont::Tag("hlig"), false); } // whitelist cross-platforms shapers only - static const char *shaper_list[] = { + constexpr const char *shaper_list[] = { "graphite2", "ot", "fallback", @@ -1699,13 +1704,11 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st bool shapedOk = hb_shape_full(hb_font, buffer, - featureArray.constData(), - features.size(), + features.values().constData(), + features.values().size(), shaper_list); - if (Q_UNLIKELY(!shapedOk)) { - hb_buffer_destroy(buffer); + if (Q_UNLIKELY(!shapedOk)) return 0; - } if (Q_UNLIKELY(HB_DIRECTION_IS_BACKWARD(props.direction))) hb_buffer_reverse(buffer); @@ -1718,10 +1721,8 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st num_glyphs = 1; // ensure we have enough space for shaped glyphs and metrics - if (Q_UNLIKELY(!ensureSpace(glyphs_shaped + num_glyphs))) { - hb_buffer_destroy(buffer); + if (Q_UNLIKELY(!ensureSpace(glyphs_shaped + num_glyphs))) return 0; - } // fetch the shaped glyphs and metrics QGlyphLayout g = availableGlyphs(&si).mid(glyphs_shaped, num_glyphs); @@ -1781,8 +1782,6 @@ int QTextEngine::shapeTextWithHarfbuzzNG(const QScriptItem &si, const ushort *st glyphs_shaped += num_glyphs; } - hb_buffer_destroy(buffer); - return glyphs_shaped; } @@ -1826,6 +1825,12 @@ QTextEngine::~QTextEngine() delete layoutData; delete specialData; resetFontEngineCache(); +#if QT_CONFIG(harfbuzz) + if (buffer) { + hb_buffer_destroy(buffer); + buffer = nullptr; + } +#endif } const QCharAttributes *QTextEngine::attributes() const diff --git a/src/gui/text/qtextengine_p.h b/src/gui/text/qtextengine_p.h index dffbc129b3a..e513fd598ba 100644 --- a/src/gui/text/qtextengine_p.h +++ b/src/gui/text/qtextengine_p.h @@ -40,6 +40,8 @@ #include <stdlib.h> #include <vector> +struct hb_buffer_t; + QT_BEGIN_NAMESPACE class QFontPrivate; @@ -583,6 +585,8 @@ private: void indexFormats(); void resolveFormats() const; + mutable hb_buffer_t *buffer = nullptr; + public: bool atWordSeparator(int position) const; diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 3452d0a448e..dbcccd8e943 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -333,10 +333,16 @@ void QHttp2ProtocolHandler::handleHeadersReceived(const HPack::HttpHeader &heade // of parsing and related errors/bugs, but it would be nice to have // more detailed validation of headers. if (name == ":status") { - statusCode = value.left(3).toInt(); - httpReply->setStatusCode(statusCode); - m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth - httpReply->setReasonPhrase(QString::fromLatin1(value.mid(4))); + bool ok = false; + if (int status = value.toInt(&ok); ok && status >= 0 && status <= 999) { + statusCode = status; + httpReply->setStatusCode(statusCode); + m_channel->lastStatus = statusCode; // Mostly useless for http/2, needed for auth + } else { + finishStreamWithError(stream, QNetworkReply::ProtocolInvalidOperationError, + "invalid :status value"_L1); + return; + } } else if (name == "content-length") { bool ok = false; const qlonglong length = value.toLongLong(&ok); diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp index f680950701c..f76d79571c3 100644 --- a/src/network/access/qnetworkreplyhttpimpl.cpp +++ b/src/network/access/qnetworkreplyhttpimpl.cpp @@ -272,6 +272,11 @@ void QNetworkReplyHttpImpl::close() void QNetworkReplyHttpImpl::abort() { + abortImpl(QNetworkReply::OperationCanceledError); +} + +void QNetworkReplyHttpImpl::abortImpl(QNetworkReply::NetworkError error) +{ Q_D(QNetworkReplyHttpImpl); // FIXME if (d->state == QNetworkReplyPrivate::Finished || d->state == QNetworkReplyPrivate::Aborted) @@ -282,7 +287,8 @@ void QNetworkReplyHttpImpl::abort() if (d->state != QNetworkReplyPrivate::Finished) { // call finished which will emit signals // FIXME shouldn't this be emitted Queued? - d->error(OperationCanceledError, tr("Operation canceled")); + d->error(error, + error == TimeoutError ? tr("Operation timed out") : tr("Operation canceled")); d->finished(); } @@ -2120,7 +2126,7 @@ void QNetworkReplyHttpImplPrivate::_q_bufferOutgoingData() void QNetworkReplyHttpImplPrivate::_q_transferTimedOut() { Q_Q(QNetworkReplyHttpImpl); - q->abort(); + q->abortImpl(QNetworkReply::TimeoutError); } void QNetworkReplyHttpImplPrivate::setupTransferTimeout() @@ -2242,8 +2248,10 @@ void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, c // Can't set and emit multiple errors. if (errorCode != QNetworkReply::NoError) { // But somewhat unavoidable if we have cancelled the request: - if (errorCode != QNetworkReply::OperationCanceledError) + if (errorCode != QNetworkReply::OperationCanceledError + && errorCode != QNetworkReply::TimeoutError) { qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once."); + } return; } diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h index 0d16d02ff53..a354b388ad6 100644 --- a/src/network/access/qnetworkreplyhttpimpl_p.h +++ b/src/network/access/qnetworkreplyhttpimpl_p.h @@ -59,6 +59,7 @@ public: void close() override; void abort() override; + void abortImpl(QNetworkReply::NetworkError error); qint64 bytesAvailable() const override; bool isSequential () const override; qint64 size() const override; diff --git a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp index 62d24eefd33..e5ada714ba5 100644 --- a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp +++ b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp @@ -110,7 +110,8 @@ QGlibNetworkInformationBackend::QGlibNetworkInformationBackend() connectivityHandlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity", G_CALLBACK(updateConnectivity), this); - networkHandlerId = g_signal_connect_swapped(networkMonitor, "network-changed", + // needed until GLib 2.86 for netlink support + networkHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-available", G_CALLBACK(updateConnectivity), this); meteredHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-metered", diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index e0ef6cec794..4c4e5fac962 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -491,7 +491,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; return; if (m_panel.visible) { - const QString selection = QString::fromNSString(m_panel.URL.path); + const QString selection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C); if (selection != m_currentSelection) { m_currentSelection = selection; emit m_helper->currentChanged(QUrl::fromLocalFile(selection)); diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm index dab348beaa4..7a6f010ba8f 100644 --- a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm +++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm @@ -88,6 +88,11 @@ bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality w return false; } + // Tahoe has issues with window-modal alert buttons not responding to mouse + if (windowModality == Qt::WindowModal + && QOperatingSystemVersion::current() >= QOperatingSystemVersion::MacOSTahoe) + return false; + // And without options we don't know what to show if (!options()) return false; diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index b7ff3bb2a58..7af9cf80355 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -26,6 +26,7 @@ public: Qt::ColorScheme colorScheme() const override; void requestColorScheme(Qt::ColorScheme scheme) override; + Qt::ContrastPreference contrastPreference() const override; #if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS) QPlatformMenuItem* createPlatformMenuItem() const override; diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm index cdf669f921c..435c21bf579 100644 --- a/src/plugins/platforms/ios/qiostheme.mm +++ b/src/plugins/platforms/ios/qiostheme.mm @@ -202,6 +202,12 @@ void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme) #endif } +Qt::ContrastPreference QIOSTheme::contrastPreference() const +{ + return UIAccessibilityDarkerSystemColorsEnabled() ? Qt::ContrastPreference::HighContrast : Qt::ContrastPreference::NoPreference; +} + + void QIOSTheme::applyTheme(UIWindow *window) { const UIUserInterfaceStyle style = []{ diff --git a/src/plugins/platforms/ios/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm index bb4268e1c88..62fd9c5d770 100644 --- a/src/plugins/platforms/ios/quiwindow.mm +++ b/src/plugins/platforms/ios/quiwindow.mm @@ -54,7 +54,9 @@ if (self.screen == UIScreen.mainScreen) { // Check if the current userInterfaceStyle reports a different appearance than // the platformTheme's appearance. We might have set that one based on the UIScreen + // Check for changes in the "Increase contrast" setting too. if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle + || previousTraitCollection.accessibilityContrast != self.traitCollection.accessibilityContrast || QGuiApplicationPrivate::platformTheme()->colorScheme() != colorScheme) { QIOSTheme::initializeSystemPalette(); QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>(); diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index a87c33c8346..eb36f7351d0 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -322,6 +322,16 @@ void QWasmAccessibility::setProperty(emscripten::val element, const std::string element.set(property, val); } +void QWasmAccessibility::setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text) +{ + const emscripten::val element = getHtmlElement(iface); + setAttribute(element, attribute, iface->text(text).toStdString()); +} +void QWasmAccessibility::setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text) +{ + const emscripten::val element = getHtmlElement(iface); + setProperty(element, property, iface->text(text).toStdString()); +} void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripten::val element, const char *eventType) { @@ -331,6 +341,17 @@ void QWasmAccessibility::addEventListener(QAccessibleInterface *iface, emscripte true); } +void QWasmAccessibility::sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType) +{ + if (iface->object()) { + QAccessibleEvent event(iface->object(), eventType); + handleUpdateByInterfaceRole(&event); + } else { + QAccessibleEvent event(iface, eventType); + handleUpdateByInterfaceRole(&event); + } +} + emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *iface) { // Get the html container element for the interface; this depends on which @@ -484,11 +505,11 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac m_elements[iface] = element; setHtmlElementGeometry(iface); - setHtmlElementTextName(iface); setHtmlElementDisabled(iface); setHtmlElementVisibility(iface, !iface->state().invisible); handleIdentifierUpdate(iface); handleDescriptionChanged(iface); + sendEvent(iface, QAccessible::NameChanged); linkToParent(iface); // Link in child elements @@ -586,14 +607,7 @@ void QWasmAccessibility::linkToParent(QAccessibleInterface *iface) void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible) { emscripten::val element = getHtmlElement(iface); - - if (visible) { - setAttribute(element, "aria-hidden", false); - setAttribute(element, "tabindex", ""); - } else { - setAttribute(element, "aria-hidden", true); // aria-hidden mean completely hidden; maybe some sort of soft-hidden should be used. - setAttribute(element, "tabindex", "-1"); - } + setAttribute(element, "aria-hidden", !visible); } void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface) @@ -631,28 +645,6 @@ void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect g style.set("height", std::to_string(geometry.height()) + "px"); } -void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface) -{ - const emscripten::val element = getHtmlElement(iface); - const QString name = iface->text(QAccessible::Name); - const QString value = iface->text(QAccessible::Value); - - // A <div> cannot contain aria-label - if (iface->role() == QAccessible::StaticText) - setProperty(element, "innerText", name.toStdString()); - else if (iface->role() == QAccessible::EditableText) - setProperty(element, "value", value.toStdString()); - else - setAttribute(element, "aria-label", name.toStdString()); -} - -void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) -{ - const emscripten::val element = getHtmlElement(iface); - QString value = iface->text(QAccessible::Value); - setProperty(element, "value", value.toStdString()); -} - void QWasmAccessibility::setHtmlElementFocus(QAccessibleInterface *iface) { const auto element = getHtmlElement(iface); @@ -684,7 +676,8 @@ void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event) { switch (event->type()) { case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + // StaticText is a div + setNamedProperty(event->accessibleInterface(), "innerText", QAccessible::Name); } break; default: qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type(); @@ -705,7 +698,7 @@ void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) setProperty(element, "type", "text"); } break; case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value); } break; case QAccessible::ObjectShow: case QAccessible::Focus: { @@ -718,12 +711,12 @@ void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) else setProperty(element, "type", "text"); } - setHtmlElementTextNameLE(iface); + setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value); } break; case QAccessible::TextRemoved: case QAccessible::TextInserted: case QAccessible::TextCaretMoved: { - setHtmlElementTextNameLE(event->accessibleInterface()); + setNamedProperty(event->accessibleInterface(), "value", QAccessible::Value); } break; default: qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type(); @@ -758,7 +751,15 @@ void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event) void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event) { - qCDebug(lcQpaAccessibility) << "TODO: implement handleButtonUpdate for event" << event->type(); + switch (event->type()) { + case QAccessible::Focus: + case QAccessible::NameChanged: { + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); + } break; + default: + qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type(); + break; + } } void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event) @@ -766,7 +767,7 @@ void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event) switch (event->type()) { case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::StateChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -785,7 +786,8 @@ void QWasmAccessibility::handleSwitchUpdate(QAccessibleEvent *event) switch (event->type()) { case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + /* A switch is like a button in this regard */ + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::StateChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -848,7 +850,7 @@ void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) { case QAccessible::Focus: case QAccessible::DialogStart: case QAccessible::StateChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; default: qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type(); @@ -876,10 +878,10 @@ void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface) linkToParent(iface); setHtmlElementVisibility(iface, !iface->state().invisible); setHtmlElementGeometry(iface); - setHtmlElementTextName(iface); setHtmlElementDisabled(iface); handleIdentifierUpdate(iface); handleDescriptionChanged(iface); + sendEvent(iface, QAccessible::NameChanged); } } for (int i = 0; i < iface->childCount(); ++i) @@ -891,7 +893,7 @@ void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event) switch (event->type()) { case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::StateChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -912,7 +914,7 @@ void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event) } break; case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::ValueChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -934,7 +936,7 @@ void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event) } break; case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::ValueChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -953,7 +955,7 @@ void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event) switch (event->type()) { case QAccessible::Focus: case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::ValueChanged: { QAccessibleInterface *accessible = event->accessibleInterface(); @@ -972,10 +974,10 @@ void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event) { switch (event->type()) { case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::Focus: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; default: qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type(); @@ -987,10 +989,10 @@ void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event) { switch (event->type()) { case QAccessible::NameChanged: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; case QAccessible::Focus: { - setHtmlElementTextName(event->accessibleInterface()); + setNamedAttribute(event->accessibleInterface(), "aria-label", QAccessible::Name); } break; default: qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type(); @@ -1113,13 +1115,19 @@ void QWasmAccessibility::relinkParentForChildren(QAccessibleInterface *iface) void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) { + if (handleUpdateByEventType(event)) + handleUpdateByInterfaceRole(event); +} + +bool QWasmAccessibility::handleUpdateByEventType(QAccessibleEvent *event) +{ if (!m_accessibilityEnabled) - return; + return false; QAccessibleInterface *iface = event->accessibleInterface(); if (!iface) { - qWarning() << "notifyAccessibilityUpdate with null a11y interface" << event->type() << event->object(); - return; + qWarning() << "handleUpdateByEventType with null a11y interface" << event->type() << event->object(); + return false; } // Handle event types that creates/removes objects. @@ -1127,13 +1135,13 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) case QAccessible::ObjectCreated: // Do nothing, there are too many changes to the interface // before ObjectShow is called - return; + return false; case QAccessible::ObjectDestroyed: // The object might be under destruction, and the interface is not valid // but we can look at the pointer, removeObject(iface); - return; + return false; case QAccessible::ObjectShow: // We do not get ObjectCreated from widgets, we get ObjectShow createObject(iface); @@ -1149,7 +1157,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) }; if (getHtmlElement(iface).isUndefined()) - return; + return false; // Handle some common event types. See // https://doc.qt.io/qt-5/qaccessible.html#Event-enum @@ -1162,7 +1170,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) case QAccessible::DescriptionChanged: handleDescriptionChanged(iface); - return; + return false; case QAccessible::Focus: // We do not get all callbacks for the geometry @@ -1173,7 +1181,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) case QAccessible::IdentifierChanged: handleIdentifierUpdate(iface); - return; + return false; case QAccessible::ObjectShow: linkToParent(iface); @@ -1181,23 +1189,37 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) // Sync up properties on show; setHtmlElementGeometry(iface); - setHtmlElementTextName(iface); + sendEvent(iface, QAccessible::NameChanged); break; case QAccessible::ObjectHide: linkToParent(iface); setHtmlElementVisibility(iface, false); - return; + return false; case QAccessible::LocationChanged: setHtmlElementGeometry(iface); - return; + return false; // TODO: maybe handle more types here default: break; }; + return true; +} + +void QWasmAccessibility::handleUpdateByInterfaceRole(QAccessibleEvent *event) +{ + if (!m_accessibilityEnabled) + return; + + QAccessibleInterface *iface = event->accessibleInterface(); + if (!iface) { + qWarning() << "handleUpdateByInterfaceRole with null a11y interface" << event->type() << event->object(); + return; + } + // Switch on interface role, see // https://doc.qt.io/qt-5/qaccessibleinterface.html#role switch (iface->role()) { @@ -1205,7 +1227,7 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event) handleStaticTextUpdate(event); break; case QAccessible::Button: - handleStaticTextUpdate(event); + handleButtonUpdate(event); break; case QAccessible::CheckBox: handleCheckBoxUpdate(event); diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.h b/src/plugins/platforms/wasm/qwasmaccessibility.h index ddbfec918d6..f20c7db5ac3 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.h +++ b/src/plugins/platforms/wasm/qwasmaccessibility.h @@ -73,8 +73,6 @@ private: void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible); void setHtmlElementGeometry(QAccessibleInterface *iface); void setHtmlElementGeometry(emscripten::val element, QRect geometry); - void setHtmlElementTextName(QAccessibleInterface *iface); - void setHtmlElementTextNameLE(QAccessibleInterface *iface); void setHtmlElementFocus(QAccessibleInterface *iface); void setHtmlElementDisabled(QAccessibleInterface *iface); void setHtmlElementOrientation(emscripten::val element, QAccessibleInterface *iface); @@ -105,6 +103,9 @@ private: void relinkParentForChildren(QAccessibleInterface *iface); void notifyAccessibilityUpdate(QAccessibleEvent *event) override; + bool handleUpdateByEventType(QAccessibleEvent *event); + void handleUpdateByInterfaceRole(QAccessibleEvent *event); + void setRootObject(QObject *o) override; void initialize() override; void cleanup() override; @@ -117,7 +118,11 @@ private: void setProperty(emscripten::val element, const std::string &attr, const char *val); void setProperty(emscripten::val element, const std::string &attr, bool val); + void setNamedAttribute(QAccessibleInterface *iface, const std::string &attribute, QAccessible::Text text); + void setNamedProperty(QAccessibleInterface *iface, const std::string &property, QAccessible::Text text); + void addEventListener(QAccessibleInterface *, emscripten::val element, const char *eventType); + void sendEvent(QAccessibleInterface *iface, QAccessible::Event eventType); private: static QWasmAccessibility *s_instance; diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp index 18a457198f1..a0546fdc215 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.cpp +++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp @@ -40,8 +40,23 @@ void QWasmInputContext::inputCallback(emscripten::val event) // Some of them should be implemented here later. qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString; if (!inputTypeString.compare("deleteContentBackward")) { - QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier); - QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier); + + QInputMethodQueryEvent queryEvent(Qt::ImQueryAll); + QCoreApplication::sendEvent(m_focusObject, &queryEvent); + int cursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt(); + + int deleteLength = rangesPair.second - rangesPair.first; + int deleteFrom = -1; + if (cursorPosition > rangesPair.first) { + deleteFrom = -(cursorPosition - rangesPair.first); + } + QInputMethodEvent e; + e.setCommitString(QString(), deleteFrom, deleteLength); + QCoreApplication::sendEvent(m_focusObject, &e); + + rangesPair.first = 0; + rangesPair.second = 0; + event.call<void>("stopImmediatePropagation"); return; } else if (!inputTypeString.compare("deleteContentForward")) { @@ -138,41 +153,23 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event) const auto compositionStr = QString::fromEcmaString(event["data"]); qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr; - // WA for IOS. - // Not sure now because I cannot test it anymore. -// int replaceSize = 0; -// emscripten::val win = emscripten::val::global("window"); -// emscripten::val sel = win.call<emscripten::val>("getSelection"); -// if (!sel.isNull() && !sel.isUndefined() -// && sel["rangeCount"].as<int>() > 0) { -// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll); -// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent); -// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString(); -// -// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString")); -// const auto &preeditStr = preeditString(); -// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>(); -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr; -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr; -// if (!sel["type"].as<std::string>().compare("Range")) { -// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString(); -// if (surroundingTextBeforeCursor.endsWith(selectedStr)) { -// replaceSize = selectedStr.size(); -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize; -// } -// } -// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0); -// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>(); -// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>(); -// } -// -// setPreeditString(compositionStr, replaceSize); setPreeditString(compositionStr, 0); } +void QWasmInputContext::beforeInputCallback(emscripten::val event) +{ + emscripten::val ranges = event.call<emscripten::val>("getTargetRanges"); + + auto length = ranges["length"].as<int>(); + for (auto i = 0; i < length; i++) { + emscripten::val range = ranges[i]; + qCDebug(qLcQpaWasmInputContext) << "startOffset" << range["startOffset"].as<int>(); + qCDebug(qLcQpaWasmInputContext) << "endOffset" << range["endOffset"].as<int>(); + rangesPair.first = range["startOffset"].as<int>(); + rangesPair.second = range["endOffset"].as<int>(); + } +} + QWasmInputContext::QWasmInputContext() { qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO; diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h index 6d24c7fea0d..97415451b2a 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.h +++ b/src/plugins/platforms/wasm/qwasminputcontext.h @@ -45,6 +45,7 @@ public: void compositionEndCallback(emscripten::val event); void compositionStartCallback(emscripten::val event); void compositionUpdateCallback(emscripten::val event); + void beforeInputCallback(emscripten::val event); void updateGeometry(); @@ -62,6 +63,7 @@ private: bool m_inputMethodAccepted = false; QObject *m_focusObject = nullptr; emscripten::val m_inputElement = emscripten::val::null(); + QPair<int, int> rangesPair; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 04f52ac9b1b..e49c1dc49f1 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -110,6 +110,7 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, // Set up m_inputElement, which takes focus whenever a Qt text input UI element has // foucus. m_inputElement["classList"].call<void>("add", emscripten::val("qt-window-input-element")); + m_inputElement.call<void>("setAttribute", std::string("contenteditable"), std::string("true")); m_inputElement.set("type", "text"); m_inputElement["style"].set("position", "absolute"); m_inputElement["style"].set("left", 0); @@ -227,6 +228,8 @@ void QWasmWindow::registerEventHandlers() [this](emscripten::val event){ handleCompositionStartEvent(event); }); m_compositionEndCallback = QWasmEventHandler(m_window, "compositionend", [this](emscripten::val event){ handleCompositionEndEvent(event); }); + m_beforeInputCallback = QWasmEventHandler(m_window, "beforeinput", + [this](emscripten::val event){ handleBeforeInputEvent(event); }); } QWasmWindow::~QWasmWindow() @@ -789,6 +792,16 @@ void QWasmWindow::handleCompositionEndEvent(emscripten::val event) m_focusHelper.set("innerHTML", std::string()); } +void QWasmWindow::handleBeforeInputEvent(emscripten::val event) +{ + qWarning() << Q_FUNC_INFO; + + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->beforeInputCallback(event); + // else + // m_focusHelper.set("innerHTML", std::string()); +} + void QWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event) { if (processPointerEnterLeave(event)) diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index cbe930dce89..8e6e5021dcf 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -145,6 +145,7 @@ private: void handleCompositionStartEvent(emscripten::val event); void handleCompositionUpdateEvent(emscripten::val event); void handleCompositionEndEvent(emscripten::val event); + void handleBeforeInputEvent(emscripten::val event); void handlePointerEnterLeaveEvent(const PointerEvent &event); bool processPointerEnterLeave(const PointerEvent &event); @@ -183,6 +184,7 @@ private: QWasmEventHandler m_compositionStartCallback; QWasmEventHandler m_compositionUpdateCallback; QWasmEventHandler m_compositionEndCallback; + QWasmEventHandler m_beforeInputCallback; QWasmEventHandler m_pointerDownCallback; QWasmEventHandler m_pointerMoveCallback; diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt index 7e3589def6b..d9415f0a011 100644 --- a/src/plugins/platforms/wayland/CMakeLists.txt +++ b/src/plugins/platforms/wayland/CMakeLists.txt @@ -17,14 +17,6 @@ qt_internal_add_module(WaylandGlobalPrivate NO_GENERATE_CPP_EXPORTS ) -# Work around 115101. -# If nothing depends on the WaylandGlobalPrivate target it doesn't run custom commands that the -# target depends on. WaylandGlobalPrivate_ensure_sync_headers makes sure that 'all' depends on -# WaylandGlobalPrivate_sync_headers. -# TODO: This needs to be removed once the fix for QTBUG-115101 is merged in qtbase. -add_custom_target(WaylandGlobalPrivate_ensure_sync_headers ALL) -add_dependencies(WaylandGlobalPrivate_ensure_sync_headers WaylandGlobalPrivate_sync_headers) - # special case begin # TODO: Ideally these macros would be part of the qtwaylandscanner tool, and not the compositor/client include(../../../../src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake) @@ -165,7 +157,7 @@ qt6_generate_wayland_protocol_client_sources(WaylandClient ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/qt-windowmanager.xml ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/hardware-integration.xml ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/extensions/server-buffer-extension.xml - ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/color-management/xx-color-management-v4.xml + ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/color-management/color-management-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/pointer-warp/pointer-warp-v1.xml ${CMAKE_CURRENT_SOURCE_DIR}/../../../3rdparty/wayland/protocols/session-management/xx-session-management-v1.xml ) diff --git a/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp index 92c746d3541..edbeb1f72ea 100644 --- a/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp +++ b/src/plugins/platforms/wayland/global/qwaylandclientextension.cpp @@ -40,6 +40,11 @@ void QWaylandClientExtensionPrivate::globalRemoved(const RegistryGlobal &global) } } +/*! + \class QWaylandClientExtension + \internal +*/ + void QWaylandClientExtension::initialize() { Q_D(QWaylandClientExtension); diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp index 2114e59328b..2a0c8a4c854 100644 --- a/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp +++ b/src/plugins/platforms/wayland/qwaylandcolormanagement.cpp @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE namespace QtWaylandClient { ColorManager::ColorManager(struct ::wl_registry *registry, uint32_t id, int version) - : QtWayland::xx_color_manager_v4(registry, id, version) + : QtWayland::wp_color_manager_v1(registry, id, version) { } @@ -22,7 +22,7 @@ ColorManager::~ColorManager() destroy(); } -void ColorManager::xx_color_manager_v4_supported_feature(uint32_t feature) +void ColorManager::wp_color_manager_v1_supported_feature(uint32_t feature) { switch (feature) { case feature_icc_v2_v4: @@ -49,14 +49,14 @@ void ColorManager::xx_color_manager_v4_supported_feature(uint32_t feature) } } -void ColorManager::xx_color_manager_v4_supported_primaries_named(uint32_t primaries) +void ColorManager::wp_color_manager_v1_supported_primaries_named(uint32_t primaries) { - mPrimaries.push_back(QtWayland::xx_color_manager_v4::primaries(primaries)); + mPrimaries.push_back(QtWayland::wp_color_manager_v1::primaries(primaries)); } -void ColorManager::xx_color_manager_v4_supported_tf_named(uint32_t transferFunction) +void ColorManager::wp_color_manager_v1_supported_tf_named(uint32_t transferFunction) { - mTransferFunctions.push_back(QtWayland::xx_color_manager_v4::transfer_function(transferFunction)); + mTransferFunctions.push_back(QtWayland::wp_color_manager_v1::transfer_function(transferFunction)); } ColorManager::Features ColorManager::supportedFeatures() const @@ -64,12 +64,12 @@ ColorManager::Features ColorManager::supportedFeatures() const return mFeatures; } -bool ColorManager::supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const +bool ColorManager::supportsNamedPrimary(QtWayland::wp_color_manager_v1::primaries primaries) const { return mPrimaries.contains(primaries); } -bool ColorManager::supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const +bool ColorManager::supportsTransferFunction(QtWayland::wp_color_manager_v1::transfer_function transferFunction) const { return mTransferFunctions.contains(transferFunction); } @@ -92,8 +92,8 @@ std::unique_ptr<ImageDescription> ColorManager::createImageDescription(const QCo return nullptr; constexpr std::array tfMapping = { - std::make_pair(QColorSpace::TransferFunction::Linear, transfer_function_linear), - std::make_pair(QColorSpace::TransferFunction::SRgb, transfer_function_srgb), + std::make_pair(QColorSpace::TransferFunction::Linear, transfer_function_ext_linear), + std::make_pair(QColorSpace::TransferFunction::SRgb, transfer_function_gamma22), std::make_pair(QColorSpace::TransferFunction::St2084, transfer_function_st2084_pq), std::make_pair(QColorSpace::TransferFunction::Hlg, transfer_function_hlg), }; @@ -106,99 +106,105 @@ std::unique_ptr<ImageDescription> ColorManager::createImageDescription(const QCo transferFunction = transfer_function_gamma22; else if (qFuzzyCompare(colorspace.gamma(), 2.8f) && supportsTransferFunction(transfer_function_gamma28)) transferFunction = transfer_function_gamma28; - if (!transferFunction && !(mFeatures & Feature::PowerTransferFunction)) - return nullptr; - } else if (!transferFunction) { + if (!transferFunction && !(mFeatures & Feature::PowerTransferFunction)) { + if (qFuzzyCompare(colorspace.gamma(), 563.0f / 256.0f) && supportsTransferFunction(transfer_function_gamma22)) { + // If power tf is not supported, we can use Adobe RGB gamma approximation + transferFunction = transfer_function_gamma22; + } else { + return nullptr; + } + } + } else if (!transferFunction || !supportsTransferFunction(*transferFunction)) { return nullptr; } - auto creator = new_parametric_creator(); + auto creator = create_parametric_creator(); if (primary != primaryMapping.end()) { - xx_image_description_creator_params_v4_set_primaries_named(creator, primary->second); + wp_image_description_creator_params_v1_set_primaries_named(creator, primary->second); } else { const auto primaries = colorspace.primaryPoints(); - xx_image_description_creator_params_v4_set_primaries(creator, - std::round(10'000 * primaries.redPoint.x()), std::round(10'000 * primaries.redPoint.y()), - std::round(10'000 * primaries.greenPoint.x()), std::round(10'000 * primaries.greenPoint.y()), - std::round(10'000 * primaries.bluePoint.x()), std::round(10'000 * primaries.bluePoint.y()), - std::round(10'000 * primaries.whitePoint.x()), std::round(10'000 * primaries.whitePoint.y()) + wp_image_description_creator_params_v1_set_primaries(creator, + std::round(1'000'000 * primaries.redPoint.x()), std::round(1'000'000 * primaries.redPoint.y()), + std::round(1'000'000 * primaries.greenPoint.x()), std::round(1'000'000 * primaries.greenPoint.y()), + std::round(1'000'000 * primaries.bluePoint.x()), std::round(1'000'000 * primaries.bluePoint.y()), + std::round(1'000'000 * primaries.whitePoint.x()), std::round(1'000'000 * primaries.whitePoint.y()) ); } if (transferFunction) { - xx_image_description_creator_params_v4_set_tf_named(creator, *transferFunction); + wp_image_description_creator_params_v1_set_tf_named(creator, *transferFunction); } else { Q_ASSERT(colorspace.transferFunction() == QColorSpace::TransferFunction::Gamma); - xx_image_description_creator_params_v4_set_tf_power(creator, std::round(colorspace.gamma() * 10'000)); + wp_image_description_creator_params_v1_set_tf_power(creator, std::round(colorspace.gamma() * 10'000)); } - return std::make_unique<ImageDescription>(xx_image_description_creator_params_v4_create(creator)); + return std::make_unique<ImageDescription>(wp_image_description_creator_params_v1_create(creator)); } ImageDescriptionInfo::ImageDescriptionInfo(ImageDescription *descr) - : QtWayland::xx_image_description_info_v4(descr->get_information()) + : QtWayland::wp_image_description_info_v1(descr->get_information()) { } ImageDescriptionInfo::~ImageDescriptionInfo() { - xx_image_description_info_v4_destroy(object()); + wp_image_description_info_v1_destroy(object()); } -void ImageDescriptionInfo::xx_image_description_info_v4_done() +void ImageDescriptionInfo::wp_image_description_info_v1_done() { Q_EMIT done(); } -void ImageDescriptionInfo::xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size) +void ImageDescriptionInfo::wp_image_description_info_v1_icc_file(int32_t icc, uint32_t icc_size) { Q_UNUSED(icc_size) close(icc); } -void ImageDescriptionInfo::xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +void ImageDescriptionInfo::wp_image_description_info_v1_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - mContainerRed = QPointF(r_x, r_y) / 10'000.0; - mContainerGreen = QPointF(g_x, g_y) / 10'000.0; - mContainerBlue = QPointF(b_x, b_y) / 10'000.0; - mContainerWhite = QPointF(w_x, w_y) / 10'000.0; + mContainerRed = QPointF(r_x, r_y) / 1'000'000.0; + mContainerGreen = QPointF(g_x, g_y) / 1'000'000.0; + mContainerBlue = QPointF(b_x, b_y) / 1'000'000.0; + mContainerWhite = QPointF(w_x, w_y) / 1'000'000.0; } -void ImageDescriptionInfo::xx_image_description_info_v4_tf_named(uint32_t transferFunction) +void ImageDescriptionInfo::wp_image_description_info_v1_tf_named(uint32_t transferFunction) { mTransferFunction = transferFunction; } -void ImageDescriptionInfo::xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) +void ImageDescriptionInfo::wp_image_description_info_v1_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) { mMinLuminance = min_lum / 10'000.0; mMaxLuminance = max_lum; mReferenceLuminance = reference_lum; } -void ImageDescriptionInfo::xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) +void ImageDescriptionInfo::wp_image_description_info_v1_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) { - mTargetRed = QPointF(r_x, r_y) / 10'000.0; - mTargetGreen = QPointF(g_x, g_y) / 10'000.0; - mTargetBlue = QPointF(b_x, b_y) / 10'000.0; - mTargetWhite = QPointF(w_x, w_y) / 10'000.0; + mTargetRed = QPointF(r_x, r_y) / 1'000'000.0; + mTargetGreen = QPointF(g_x, g_y) / 1'000'000.0; + mTargetBlue = QPointF(b_x, b_y) / 1'000'000.0; + mTargetWhite = QPointF(w_x, w_y) / 1'000'000.0; } -void ImageDescriptionInfo::xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum) +void ImageDescriptionInfo::wp_image_description_info_v1_target_luminance(uint32_t min_lum, uint32_t max_lum) { mTargetMinLuminance = min_lum / 10'000.0; mTargetMaxLuminance = max_lum; } -ImageDescription::ImageDescription(::xx_image_description_v4 *descr) - : QtWayland::xx_image_description_v4(descr) +ImageDescription::ImageDescription(::wp_image_description_v1 *descr) + : QtWayland::wp_image_description_v1(descr) { } ImageDescription::~ImageDescription() { - xx_image_description_v4_destroy(object()); + wp_image_description_v1_destroy(object()); } -void ImageDescription::xx_image_description_v4_failed(uint32_t cause, const QString &msg) +void ImageDescription::wp_image_description_v1_failed(uint32_t cause, const QString &msg) { Q_UNUSED(cause); qCWarning(lcQpaWayland) << "image description failed!" << msg; @@ -206,25 +212,26 @@ void ImageDescription::xx_image_description_v4_failed(uint32_t cause, const QStr // maybe fall back to the previous or preferred image description } -void ImageDescription::xx_image_description_v4_ready(uint32_t identity) +void ImageDescription::wp_image_description_v1_ready(uint32_t identity) { Q_UNUSED(identity); Q_EMIT ready(); } -ColorManagementFeedback::ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj) - : QtWayland::xx_color_management_feedback_surface_v4(obj) +ColorManagementFeedback::ColorManagementFeedback(::wp_color_management_surface_feedback_v1 *obj) + : QtWayland::wp_color_management_surface_feedback_v1(obj) , mPreferred(std::make_unique<ImageDescription>(get_preferred())) { } ColorManagementFeedback::~ColorManagementFeedback() { - xx_color_management_feedback_surface_v4_destroy(object()); + wp_color_management_surface_feedback_v1_destroy(object()); } -void ColorManagementFeedback::xx_color_management_feedback_surface_v4_preferred_changed() +void ColorManagementFeedback::wp_color_management_surface_feedback_v1_preferred_changed(uint32_t identity) { + Q_UNUSED(identity); mPreferred = std::make_unique<ImageDescription>(get_preferred()); mPendingPreferredInfo = std::make_unique<ImageDescriptionInfo>(mPreferred.get()); connect(mPendingPreferredInfo.get(), &ImageDescriptionInfo::done, this, &ColorManagementFeedback::preferredChanged); @@ -235,22 +242,22 @@ void ColorManagementFeedback::handlePreferredDone() mPreferredInfo = std::move(mPendingPreferredInfo); } -ColorManagementSurface::ColorManagementSurface(::xx_color_management_surface_v4 *obj) - : QtWayland::xx_color_management_surface_v4(obj) +ColorManagementSurface::ColorManagementSurface(::wp_color_management_surface_v1 *obj) + : QtWayland::wp_color_management_surface_v1(obj) { } ColorManagementSurface::~ColorManagementSurface() { - xx_color_management_surface_v4_destroy(object()); + wp_color_management_surface_v1_destroy(object()); } void ColorManagementSurface::setImageDescription(ImageDescription *descr) { if (descr) - xx_color_management_surface_v4_set_image_description(object(), descr->object(), QtWayland::xx_color_manager_v4::render_intent::render_intent_perceptual); + wp_color_management_surface_v1_set_image_description(object(), descr->object(), QtWayland::wp_color_manager_v1::render_intent::render_intent_perceptual); else - xx_color_management_surface_v4_unset_image_description(object()); + wp_color_management_surface_v1_unset_image_description(object()); } } diff --git a/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h index 8e44bd66b7b..04c3962ff8d 100644 --- a/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h +++ b/src/plugins/platforms/wayland/qwaylandcolormanagement_p.h @@ -20,7 +20,7 @@ #include <QColorSpace> #include <QList> -#include "qwayland-xx-color-management-v4.h" +#include "qwayland-color-management-v1.h" QT_BEGIN_NAMESPACE @@ -28,7 +28,7 @@ namespace QtWaylandClient { class ImageDescription; -class ColorManager : public QObject, public QtWayland::xx_color_manager_v4 +class ColorManager : public QObject, public QtWayland::wp_color_manager_v1 { Q_OBJECT public: @@ -48,22 +48,22 @@ public: ~ColorManager() override; Features supportedFeatures() const; - bool supportsNamedPrimary(QtWayland::xx_color_manager_v4::primaries primaries) const; - bool supportsTransferFunction(QtWayland::xx_color_manager_v4::transfer_function transferFunction) const; + bool supportsNamedPrimary(QtWayland::wp_color_manager_v1::primaries primaries) const; + bool supportsTransferFunction(QtWayland::wp_color_manager_v1::transfer_function transferFunction) const; std::unique_ptr<ImageDescription> createImageDescription(const QColorSpace &colorspace); private: - void xx_color_manager_v4_supported_feature(uint32_t feature) override; - void xx_color_manager_v4_supported_primaries_named(uint32_t primaries) override; - void xx_color_manager_v4_supported_tf_named(uint32_t transferFunction) override; + void wp_color_manager_v1_supported_feature(uint32_t feature) override; + void wp_color_manager_v1_supported_primaries_named(uint32_t primaries) override; + void wp_color_manager_v1_supported_tf_named(uint32_t transferFunction) override; Features mFeatures; - QList<QtWayland::xx_color_manager_v4::primaries> mPrimaries; - QList<QtWayland::xx_color_manager_v4::transfer_function> mTransferFunctions; + QList<QtWayland::wp_color_manager_v1::primaries> mPrimaries; + QList<QtWayland::wp_color_manager_v1::transfer_function> mTransferFunctions; }; -class ImageDescriptionInfo : public QObject, public QtWayland::xx_image_description_info_v4 +class ImageDescriptionInfo : public QObject, public QtWayland::wp_image_description_info_v1 { Q_OBJECT public: @@ -88,34 +88,34 @@ public: double mTargetMaxLuminance; private: - void xx_image_description_info_v4_done() override; - void xx_image_description_info_v4_icc_file(int32_t icc, uint32_t icc_size) override; - void xx_image_description_info_v4_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; - void xx_image_description_info_v4_tf_named(uint32_t transferFunction) override; - void xx_image_description_info_v4_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override; - void xx_image_description_info_v4_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; - void xx_image_description_info_v4_target_luminance(uint32_t min_lum, uint32_t max_lum) override; + void wp_image_description_info_v1_done() override; + void wp_image_description_info_v1_icc_file(int32_t icc, uint32_t icc_size) override; + void wp_image_description_info_v1_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; + void wp_image_description_info_v1_tf_named(uint32_t transferFunction) override; + void wp_image_description_info_v1_luminances(uint32_t min_lum, uint32_t max_lum, uint32_t reference_lum) override; + void wp_image_description_info_v1_target_primaries(int32_t r_x, int32_t r_y, int32_t g_x, int32_t g_y, int32_t b_x, int32_t b_y, int32_t w_x, int32_t w_y) override; + void wp_image_description_info_v1_target_luminance(uint32_t min_lum, uint32_t max_lum) override; }; -class ImageDescription : public QObject, public QtWayland::xx_image_description_v4 +class ImageDescription : public QObject, public QtWayland::wp_image_description_v1 { Q_OBJECT public: - explicit ImageDescription(::xx_image_description_v4 *descr); + explicit ImageDescription(::wp_image_description_v1 *descr); ~ImageDescription(); Q_SIGNAL void ready(); private: - void xx_image_description_v4_failed(uint32_t cause, const QString &msg) override; - void xx_image_description_v4_ready(uint32_t identity) override; + void wp_image_description_v1_failed(uint32_t cause, const QString &msg) override; + void wp_image_description_v1_ready(uint32_t identity) override; }; -class ColorManagementFeedback : public QObject, public QtWayland::xx_color_management_feedback_surface_v4 +class ColorManagementFeedback : public QObject, public QtWayland::wp_color_management_surface_feedback_v1 { Q_OBJECT public: - explicit ColorManagementFeedback(::xx_color_management_feedback_surface_v4 *obj); + explicit ColorManagementFeedback(::wp_color_management_surface_feedback_v1 *obj); ~ColorManagementFeedback(); Q_SIGNAL void preferredChanged(); @@ -123,7 +123,7 @@ public: std::unique_ptr<ImageDescriptionInfo> mPreferredInfo; private: - void xx_color_management_feedback_surface_v4_preferred_changed() override; + void wp_color_management_surface_feedback_v1_preferred_changed(uint32_t identity) override; void handlePreferredDone(); std::unique_ptr<ImageDescription> mPreferred; @@ -131,11 +131,11 @@ private: }; -class ColorManagementSurface : public QObject, public QtWayland::xx_color_management_surface_v4 +class ColorManagementSurface : public QObject, public QtWayland::wp_color_management_surface_v1 { Q_OBJECT public: - explicit ColorManagementSurface(::xx_color_management_surface_v4 *obj); + explicit ColorManagementSurface(::wp_color_management_surface_v1 *obj); ~ColorManagementSurface(); void setImageDescription(ImageDescription *descr); diff --git a/src/plugins/platforms/wayland/qwaylanddisplay.cpp b/src/plugins/platforms/wayland/qwaylanddisplay.cpp index 10bc2d5bfa2..1fc5e5c30a0 100644 --- a/src/plugins/platforms/wayland/qwaylanddisplay.cpp +++ b/src/plugins/platforms/wayland/qwaylanddisplay.cpp @@ -485,9 +485,13 @@ void QWaylandDisplay::reconnect() connect( this, &QWaylandDisplay::connected, this, - [&allPlatformWindows] { + [this, &allPlatformWindows] { for (auto &window : std::as_const(allPlatformWindows)) { - window->initializeWlSurface(); + window->initializeWlSurface(false); + } + forceRoundTrip(); // we need a roundtrip to receive the color space features the compositor supports + for (auto &window : std::as_const(allPlatformWindows)) { + window->initializeColorSpace(); } }, Qt::SingleShotConnection); @@ -797,10 +801,8 @@ void QWaylandDisplay::registry_global(uint32_t id, const QString &interface, uin inputDevice->setDataControlDevice(mGlobals.dataControlManager->createDevice(inputDevice)); } #endif - } else if (interface == QLatin1String(QtWayland::xx_color_manager_v4::interface()->name)) { + } else if (interface == QLatin1String(QtWayland::wp_color_manager_v1::interface()->name)) { mGlobals.colorManager = std::make_unique<ColorManager>(registry, id, 1); - // we need a roundtrip to receive the features the compositor supports - forceRoundTrip(); } else if (interface == QLatin1String(QtWayland::wp_pointer_warp_v1::interface()->name)) { mGlobals.pointerWarp.reset(new WithDestructor<QtWayland::wp_pointer_warp_v1, wp_pointer_warp_v1_destroy>( registry, id, 1)); diff --git a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp index 0ccc4dba57a..5ab285ad97d 100644 --- a/src/plugins/platforms/wayland/qwaylandinputcontext.cpp +++ b/src/plugins/platforms/wayland/qwaylandinputcontext.cpp @@ -192,10 +192,12 @@ void QWaylandInputContext::setFocusObject(QObject *object) if (window && window->handle()) { if (mCurrentWindow.data() != window) { if (!inputMethodAccepted()) { - auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface(); - if (surface) - inputInterface->disableSurface(surface); - mCurrentWindow.clear(); + if (mCurrentWindow) { + auto *surface = static_cast<QWaylandWindow *>(mCurrentWindow->handle())->wlSurface(); + if (surface) + inputInterface->disableSurface(surface); + mCurrentWindow.clear(); + } } else { auto *surface = static_cast<QWaylandWindow *>(window->handle())->wlSurface(); if (surface) { diff --git a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp index b853db21529..fa70b53cbd0 100644 --- a/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp +++ b/src/plugins/platforms/wayland/qwaylandshmbackingstore.cpp @@ -229,7 +229,7 @@ void QWaylandShmBackingStore::endPaint() // Inspired by QCALayerBackingStore. bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) { - if (Q_UNLIKELY(!mBackBuffer || !mFrontBuffer)) + if (Q_UNLIKELY(!mBackBuffer)) return false; const qreal devicePixelRatio = waylandWindow()->scale(); @@ -241,6 +241,8 @@ bool QWaylandShmBackingStore::scroll(const QRegion ®ion, int dx, int dy) return false; recreateBackBufferIfNeeded(); + if (!mFrontBuffer) + return false; const QPoint scrollDelta(dx, dy); const QMargins margins = windowDecorationMargins(); diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp index be527b08f4d..0be22ff80e7 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow.cpp +++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp @@ -62,6 +62,7 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display) mFrameCallbackTimeout = frameCallbackTimeout; } + mSurfaceFormat.setColorSpace(QColorSpace{}); initializeWlSurface(); mFlags = window->flags(); @@ -214,7 +215,7 @@ void QWaylandWindow::setPendingImageDescription() mColorManagementSurface->setImageDescription(mPendingImageDescription.get()); } -void QWaylandWindow::initializeWlSurface() +void QWaylandWindow::initializeWlSurface(bool colorSpace) { Q_ASSERT(!mSurface); { @@ -242,6 +243,13 @@ void QWaylandWindow::initializeWlSurface() mViewport.reset(new QWaylandViewport(display()->createViewport(this))); } + if (colorSpace) { + initializeColorSpace(); + } +} + +void QWaylandWindow::initializeColorSpace() +{ QColorSpace requestedColorSpace = window()->requestedFormat().colorSpace(); if (requestedColorSpace != QColorSpace{} && mDisplay->colorManager()) { // TODO try a similar (same primaries + supported transfer function) color space if this fails? diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h index d6b24d0569f..9e1bd92af30 100644 --- a/src/plugins/platforms/wayland/qwaylandwindow_p.h +++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h @@ -247,7 +247,9 @@ public: virtual void reinit(); void reset(); - void initializeWlSurface(); + void initializeWlSurface(bool colorSpace = true); + + void initializeColorSpace(); bool windowEvent(QEvent *event) override; diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp index 1c3a3909bc2..15ab167c83f 100644 --- a/src/plugins/platforms/windows/qwindowscontext.cpp +++ b/src/plugins/platforms/windows/qwindowscontext.cpp @@ -179,9 +179,6 @@ QWindowsContextPrivate::QWindowsContextPrivate() QWindowsContext::QWindowsContext() : d(new QWindowsContextPrivate) { -#ifdef Q_CC_MSVC -# pragma warning( disable : 4996 ) -#endif m_instance = this; } diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp index 71103183183..edc1ea3a9dc 100644 --- a/src/plugins/platforms/windows/qwindowsiconengine.cpp +++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp @@ -63,8 +63,8 @@ static QString getGlyphs(QStringView iconName) {"go-home"_L1, u"\ue80f"}, // {"go-jump"_L1, u"\uf719"}, //{"go-last"_L1, u"\ue5dd"}, - {"go-next"_L1, u"\ue893"}, - {"go-previous"_L1, u"\ue892"}, + {"go-next"_L1, u"\ue72a"}, + {"go-previous"_L1, u"\ue72b"}, //{"go-top"_L1, u"\ue25a"}, {"go-up"_L1, u"\ue74a"}, {"help-about"_L1, u"\ue946"}, @@ -100,7 +100,7 @@ static QString getGlyphs(QStringView iconName) //{"object-flip-vertical"_L1, u"\u"}, {"object-rotate-left"_L1, u"\ue80c"}, {"object-rotate-right"_L1, u"\ue80d"}, - //{"process-stop"_L1, u"\ue5c9"}, + {"process-stop"_L1, u"\uf140"}, {"system-lock-screen"_L1, u"\uee3f"}, {"system-log-out"_L1, u"\uf3b1"}, //{"system-run"_L1, u"\u"}, diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 482810d5b7e..7908f22d99d 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -874,9 +874,8 @@ const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const return nullptr; } -const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const +const QWindowsScreen *QWindowsScreenManager::screenForMonitor(HMONITOR hMonitor) const { - HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); if (hMonitor == nullptr) return nullptr; const auto it = @@ -889,4 +888,18 @@ const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const return it != m_screens.cend() ? *it : nullptr; } +const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const +{ + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); + return screenForMonitor(hMonitor); +} + +const QWindowsScreen *QWindowsScreenManager::screenForRect(const RECT *rect) const +{ + if (rect == nullptr) + return nullptr; + HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONULL); + return screenForMonitor(hMonitor); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index 8d555998388..0032fd462cb 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -115,12 +115,14 @@ public: const QWindowsScreen *screenAtDp(const QPoint &p) const; const QWindowsScreen *screenForHwnd(HWND hwnd) const; + const QWindowsScreen *screenForRect(const RECT *rect) const; static bool isSingleScreen(); private: void addScreen(const QWindowsScreenData &screenData); void removeScreen(int index); + const QWindowsScreen *screenForMonitor(HMONITOR monitor) const; HWND m_displayChangeObserver = nullptr; WindowsScreenList m_screens; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 01716fba60c..72daffb56b1 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -466,15 +466,17 @@ static bool applyBlurBehindWindow(HWND hwnd) return result; } +static bool shouldShowTitlebarButton(Qt::WindowFlags flags, Qt::WindowFlags button) +{ + return !flags.testFlag(Qt::CustomizeWindowHint) || flags.testFlags(Qt::CustomizeWindowHint | button); +} + // from qwidget_win.cpp, pass flags separately in case they have been "autofixed". static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags) { - if ((flags & Qt::MSWindowsFixedSizeDialogHint) || !(flags & Qt::WindowMaximizeButtonHint)) - return false; - // if the user explicitly asked for the maximize button, we try to add - // it even if the window has fixed size. - return (flags & Qt::CustomizeWindowHint) || - w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX); + return !flags.testFlag(Qt::MSWindowsFixedSizeDialogHint) && + (shouldShowTitlebarButton(flags, Qt::WindowMaximizeButtonHint) || + w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX)); } bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags) @@ -805,6 +807,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag if (topLevel) { if ((type == Qt::Window || dialog || tool)) { + const bool defaultTitlebar = !flags.testFlag(Qt::CustomizeWindowHint); if (!(flags & Qt::FramelessWindowHint)) { style |= WS_POPUP; if (flags & Qt::MSWindowsFixedSizeDialogHint) { @@ -812,16 +815,16 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag } else { style |= WS_THICKFRAME; } - if (flags & Qt::WindowTitleHint) + if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint)) style |= WS_CAPTION; // Contains WS_DLGFRAME } - if (flags & Qt::WindowSystemMenuHint) + if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowSystemMenuHint)) style |= WS_SYSMENU; - else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) { + else if (dialog && (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint)) && !(flags & Qt::FramelessWindowHint)) { style |= WS_SYSMENU | WS_BORDER; // QTBUG-2027, dialogs without system menu. exStyle |= WS_EX_DLGMODALFRAME; } - const bool showMinimizeButton = flags & Qt::WindowMinimizeButtonHint; + const bool showMinimizeButton = shouldShowTitlebarButton(flags, Qt::WindowMinimizeButtonHint); if (showMinimizeButton) style |= WS_MINIMIZEBOX; const bool showMaximizeButton = shouldShowMaximizeButton(w, flags); @@ -2113,7 +2116,8 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) QWindowsThemeCache::clearThemeCache(hwnd); // Send screen change first, so that the new screen is set during any following resize - checkForScreenChanged(QWindowsWindow::FromDpiChange); + const auto prcNewWindow = reinterpret_cast<const RECT *>(lParam); + checkForScreenChanged(QWindowsWindow::FromDpiChange, !m_inSetgeometry ? prcNewWindow : nullptr); if (!IsZoomed(hwnd)) m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale); @@ -2136,7 +2140,6 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) // making the SetWindowPos() call. if (!m_inSetgeometry) { updateFullFrameMargins(); - const auto prcNewWindow = reinterpret_cast<RECT *>(lParam); SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); @@ -2351,14 +2354,15 @@ static inline bool equalDpi(const QDpi &d1, const QDpi &d2) return qFuzzyCompare(d1.first, d2.first) && qFuzzyCompare(d1.second, d2.second); } -void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode) +void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode, const RECT *suggestedRect) { if ((parent() && !parent()->isForeignWindow()) || QWindowsScreenManager::isSingleScreen()) return; QPlatformScreen *currentScreen = screen(); auto topLevel = isTopLevel_sys() ? m_data.hwnd : GetAncestor(m_data.hwnd, GA_ROOT); - const QWindowsScreen *newScreen = + const QWindowsScreen *newScreen = suggestedRect ? + QWindowsContext::instance()->screenManager().screenForRect(suggestedRect) : QWindowsContext::instance()->screenManager().screenForHwnd(topLevel); if (newScreen == nullptr || newScreen == currentScreen) diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 499b2a9ca86..ee348f1ac16 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -342,7 +342,7 @@ public: void stopAlertWindow(); enum ScreenChangeMode { FromGeometryChange, FromDpiChange, FromScreenAdded }; - void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange); + void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange, const RECT *suggestedRect = nullptr); void registerTouchWindow(); static void setHasBorderInFullScreenStatic(QWindow *window, bool border); diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt index 492d274c2fd..204acd72ba0 100644 --- a/src/plugins/platforms/xcb/CMakeLists.txt +++ b/src/plugins/platforms/xcb/CMakeLists.txt @@ -120,30 +120,6 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION CLANG -ftemplate-depth=1024 ) -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting - SOURCES - nativepainting/qbackingstore_x11.cpp nativepainting/qbackingstore_x11_p.h - nativepainting/qcolormap_x11.cpp nativepainting/qcolormap_x11_p.h - nativepainting/qpaintengine_x11.cpp nativepainting/qpaintengine_x11_p.h - nativepainting/qpixmap_x11.cpp nativepainting/qpixmap_x11_p.h - nativepainting/qpolygonclipper_p.h - nativepainting/qt_x11_p.h - nativepainting/qtessellator.cpp nativepainting/qtessellator_p.h - nativepainting/qxcbnativepainting.cpp nativepainting/qxcbnativepainting.h - INCLUDE_DIRECTORIES - nativepainting -) - -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting AND QT_FEATURE_xrender - PUBLIC_LIBRARIES - PkgConfig::XRender -) - -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_FEATURE_xcb_native_painting - LIBRARIES - WrapFreetype::WrapFreetype -) - if(QT_FEATURE_system_xcb_xinput) qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT) else() diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp deleted file mode 100644 index 1ac5f6a64bc..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include "qbackingstore_x11_p.h" -#include "qxcbwindow.h" -#include "qpixmap_x11_p.h" - -#include <private/qhighdpiscaling_p.h> -#include <QPainter> - -#if QT_CONFIG(xrender) -# include <X11/extensions/Xrender.h> -#endif - -#define register /* C++17 deprecated register */ -#include <X11/Xlib.h> -#undef register - -QT_BEGIN_NAMESPACE - -QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window) - : QPlatformBackingStore(window) - , m_translucentBackground(false) -{ - if (QXcbWindow *w = static_cast<QXcbWindow *>(window->handle())) { - m_translucentBackground = w->connection()->hasXRender() && - QImage::toPixelFormat(w->imageFormat()).alphaUsage() == QPixelFormat::UsesAlpha; - } -} - -QXcbNativeBackingStore::~QXcbNativeBackingStore() -{} - -QPaintDevice *QXcbNativeBackingStore::paintDevice() -{ - return &m_pixmap; -} - -void QXcbNativeBackingStore::flush(QWindow *window, const QRegion ®ion, const QPoint &offset) -{ - if (m_pixmap.isNull()) - return; - - QSize pixmapSize = m_pixmap.size(); - - QRegion clipped = region; - clipped &= QRect(QPoint(), QHighDpi::toNativePixels(window->size(), window)); - clipped &= QRect(0, 0, pixmapSize.width(), pixmapSize.height()).translated(-offset); - - QRect br = clipped.boundingRect(); - if (br.isNull()) - return; - - QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle()); - if (!platformWindow) { - qWarning("QXcbBackingStore::flush: QWindow has no platform window (QTBUG-32681)"); - return; - } - - Window wid = platformWindow->xcb_window(); - Pixmap pid = qt_x11PixmapHandle(m_pixmap); - - QList<XRectangle> clipRects = qt_region_to_xrectangles(clipped); - -#if QT_CONFIG(xrender) - if (m_translucentBackground) - { - XWindowAttributes attrib; - XGetWindowAttributes(display(), wid, &attrib); - XRenderPictFormat *format = XRenderFindVisualFormat(display(), attrib.visual); - - Picture srcPic = qt_x11PictureHandle(m_pixmap); - Picture dstPic = XRenderCreatePicture(display(), wid, format, 0, 0); - - XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size()); - - XRenderComposite(display(), PictOpSrc, srcPic, 0L /*None*/, dstPic, br.x() + offset.x(), - br.y() + offset.y(), 0, 0, br.x(), br.y(), br.width(), br.height()); - - XRenderFreePicture(display(), dstPic); - } - else -#endif - { - GC gc = XCreateGC(display(), wid, 0, nullptr); - - if (clipRects.size() != 1) - XSetClipRectangles(display(), gc, 0, 0, clipRects.data(), clipRects.size(), YXBanded); - - XCopyArea(display(), pid, wid, gc, br.x() + offset.x(), br.y() + offset.y(), br.width(), br.height(), br.x(), br.y()); - XFreeGC(display(), gc); - } - - - if (platformWindow->needsSync()) { - platformWindow->updateSyncRequestCounter(); - } else { - XFlush(display()); - } -} - -QImage QXcbNativeBackingStore::toImage() const -{ - return m_pixmap.toImage(); -} - -void QXcbNativeBackingStore::resize(const QSize &size, const QRegion &staticContents) -{ - if (size == m_pixmap.size()) - return; - - QPixmap newPixmap(size); - -#if QT_CONFIG(xrender) - if (m_translucentBackground && newPixmap.depth() != 32) - qt_x11Pixmap(newPixmap)->convertToARGB32(); -#endif - - if (!m_pixmap.isNull()) { - Pixmap from = qt_x11PixmapHandle(m_pixmap); - Pixmap to = qt_x11PixmapHandle(newPixmap); - QRect br = staticContents.boundingRect().intersected(QRect(QPoint(0, 0), size)); - - if (!br.isEmpty()) { - GC gc = XCreateGC(display(), to, 0, nullptr); - XCopyArea(display(), from, to, gc, br.x(), br.y(), br.width(), br.height(), br.x(), br.y()); - XFreeGC(display(), gc); - } - } - - m_pixmap = newPixmap; -} - -bool QXcbNativeBackingStore::scroll(const QRegion &area, int dx, int dy) -{ - if (m_pixmap.isNull()) - return false; - - QRect rect = area.boundingRect(); - Pixmap pix = qt_x11PixmapHandle(m_pixmap); - - GC gc = XCreateGC(display(), pix, 0, nullptr); - XCopyArea(display(), pix, pix, gc, - rect.x(), rect.y(), rect.width(), rect.height(), - rect.x()+dx, rect.y()+dy); - XFreeGC(display(), gc); - return true; -} - -void QXcbNativeBackingStore::beginPaint(const QRegion ®ion) -{ - QX11PlatformPixmap *x11pm = qt_x11Pixmap(m_pixmap); - if (x11pm) - x11pm->setIsBackingStore(true); - -#if QT_CONFIG(xrender) - if (m_translucentBackground) { - const QList<XRectangle> xrects = qt_region_to_xrectangles(region); - const XRenderColor color = { 0, 0, 0, 0 }; - XRenderFillRectangles(display(), PictOpSrc, - qt_x11PictureHandle(m_pixmap), &color, - xrects.constData(), xrects.size()); - } -#else - Q_UNUSED(region); -#endif -} - -Display *QXcbNativeBackingStore::display() const -{ - return static_cast<Display *>(static_cast<QXcbWindow *>(window()->handle())->connection()->xlib_display()); -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h deleted file mode 100644 index b730b076d3f..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11_p.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <qpa/qplatformbackingstore.h> - -typedef struct _XDisplay Display; - -QT_BEGIN_NAMESPACE - -class QXcbWindow; - -class QXcbNativeBackingStore : public QPlatformBackingStore -{ -public: - QXcbNativeBackingStore(QWindow *window); - ~QXcbNativeBackingStore(); - - QPaintDevice *paintDevice() override; - void flush(QWindow *window, const QRegion ®ion, const QPoint &offset) override; - - QImage toImage() const override; - - void resize(const QSize &size, const QRegion &staticContents) override; - bool scroll(const QRegion &area, int dx, int dy) override; - - void beginPaint(const QRegion ®ion) override; - -private: - Display *display() const; - - QPixmap m_pixmap; - bool m_translucentBackground; -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp deleted file mode 100644 index fddd3e03989..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11.cpp +++ /dev/null @@ -1,615 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include <QVarLengthArray> - -#include <private/qguiapplication_p.h> - -#include "qcolormap_x11_p.h" -#include "qxcbnativepainting.h" -#include "qt_x11_p.h" - -QT_BEGIN_NAMESPACE - -class QXcbColormapPrivate -{ -public: - QXcbColormapPrivate() - : ref(1), mode(QXcbColormap::Direct), depth(0), - colormap(0), defaultColormap(true), - visual(0), defaultVisual(true), - r_max(0), g_max(0), b_max(0), - r_shift(0), g_shift(0), b_shift(0) - {} - - QAtomicInt ref; - - QXcbColormap::Mode mode; - int depth; - - Colormap colormap; - bool defaultColormap; - - Visual *visual; - bool defaultVisual; - - int r_max; - int g_max; - int b_max; - - uint r_shift; - uint g_shift; - uint b_shift; - - QList<QColor> colors; - QList<int> pixels; -}; - -static uint right_align(uint v) -{ - while (!(v & 0x1)) - v >>= 1; - return v; -} - -static int cube_root(int v) -{ - if (v == 1) - return 1; - // brute force algorithm - int i = 1; - for (;;) { - const int b = i * i * i; - if (b <= v) { - ++i; - } else { - --i; - break; - } - } - return i; -} - -static Visual *find_visual(Display *display, - int screen, - int visual_class, - int visual_id, - int *depth, - bool *defaultVisual) -{ - XVisualInfo *vi, rvi; - int count; - - uint mask = VisualScreenMask; - rvi.screen = screen; - - if (visual_class != -1) { - rvi.c_class = visual_class; - mask |= VisualClassMask; - } - if (visual_id != -1) { - rvi.visualid = visual_id; - mask |= VisualIDMask; - } - - Visual *visual = DefaultVisual(display, screen); - *defaultVisual = true; - *depth = DefaultDepth(display, screen); - - vi = XGetVisualInfo(display, mask, &rvi, &count); - if (vi) { - int best = 0; - for (int x = 0; x < count; ++x) { - if (vi[x].depth > vi[best].depth) - best = x; - } - if (best >= 0 && best <= count && vi[best].visualid != XVisualIDFromVisual(visual)) { - visual = vi[best].visual; - *defaultVisual = (visual == DefaultVisual(display, screen)); - *depth = vi[best].depth; - } - } - if (vi) - XFree((char *)vi); - return visual; -} - -static void query_colormap(QXcbColormapPrivate *d, int screen) -{ - Display *display = X11->display; - - // query existing colormap - int q_colors = (((1u << d->depth) > 256u) ? 256u : (1u << d->depth)); - XColor queried[256]; - memset(queried, 0, sizeof(queried)); - for (int x = 0; x < q_colors; ++x) - queried[x].pixel = x; - XQueryColors(display, d->colormap, queried, q_colors); - - d->colors.resize(q_colors); - for (int x = 0; x < q_colors; ++x) { - if (queried[x].red == 0 - && queried[x].green == 0 - && queried[x].blue == 0 - && queried[x].pixel != BlackPixel(display, screen)) { - // unallocated color cell, skip it - continue; - } - - d->colors[x] = QColor::fromRgbF(queried[x].red / float(USHRT_MAX), - queried[x].green / float(USHRT_MAX), - queried[x].blue / float(USHRT_MAX)); - } - - // for missing colors, find the closest color in the existing colormap - Q_ASSERT(d->pixels.size()); - for (int x = 0; x < d->pixels.size(); ++x) { - if (d->pixels.at(x) != -1) - continue; - - QRgb rgb; - if (d->mode == QXcbColormap::Indexed) { - const int r = (x / (d->g_max * d->b_max)) % d->r_max; - const int g = (x / d->b_max) % d->g_max; - const int b = x % d->b_max; - rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), - (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), - (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); - } else { - rgb = qRgb(x, x, x); - } - - // find closest color - int mindist = INT_MAX, best = -1; - for (int y = 0; y < q_colors; ++y) { - int r = qRed(rgb) - (queried[y].red >> 8); - int g = qGreen(rgb) - (queried[y].green >> 8); - int b = qBlue(rgb) - (queried[y].blue >> 8); - int dist = (r * r) + (g * g) + (b * b); - if (dist < mindist) { - mindist = dist; - best = y; - } - } - - Q_ASSERT(best >= 0 && best < q_colors); - if (d->visual->c_class & 1) { - XColor xcolor; - xcolor.red = queried[best].red; - xcolor.green = queried[best].green; - xcolor.blue = queried[best].blue; - xcolor.pixel = queried[best].pixel; - - if (XAllocColor(display, d->colormap, &xcolor)) { - d->pixels[x] = xcolor.pixel; - } else { - // some weird stuff is going on... - d->pixels[x] = (qGray(rgb) < 127 - ? BlackPixel(display, screen) - : WhitePixel(display, screen)); - } - } else { - d->pixels[x] = best; - } - } -} - -static void init_gray(QXcbColormapPrivate *d, int screen) -{ - d->pixels.resize(d->r_max); - - for (int g = 0; g < d->g_max; ++g) { - const int gray = (g * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1); - const QRgb rgb = qRgb(gray, gray, gray); - - d->pixels[g] = -1; - - if (d->visual->c_class & 1) { - XColor xcolor; - xcolor.red = qRed(rgb) * 0x101; - xcolor.green = qGreen(rgb) * 0x101; - xcolor.blue = qBlue(rgb) * 0x101; - xcolor.pixel = 0ul; - - if (XAllocColor(X11->display, d->colormap, &xcolor)) - d->pixels[g] = xcolor.pixel; - } - } - - query_colormap(d, screen); -} - -static void init_indexed(QXcbColormapPrivate *d, int screen) -{ - d->pixels.resize(d->r_max * d->g_max * d->b_max); - - // create color cube - for (int x = 0, r = 0; r < d->r_max; ++r) { - for (int g = 0; g < d->g_max; ++g) { - for (int b = 0; b < d->b_max; ++b, ++x) { - const QRgb rgb = qRgb((r * 0xff + (d->r_max - 1) / 2) / (d->r_max - 1), - (g * 0xff + (d->g_max - 1) / 2) / (d->g_max - 1), - (b * 0xff + (d->b_max - 1) / 2) / (d->b_max - 1)); - - d->pixels[x] = -1; - - if (d->visual->c_class & 1) { - XColor xcolor; - xcolor.red = qRed(rgb) * 0x101; - xcolor.green = qGreen(rgb) * 0x101; - xcolor.blue = qBlue(rgb) * 0x101; - xcolor.pixel = 0ul; - - if (XAllocColor(X11->display, d->colormap, &xcolor)) - d->pixels[x] = xcolor.pixel; - } - } - } - } - - query_colormap(d, screen); -} - -static void init_direct(QXcbColormapPrivate *d, bool ownColormap) -{ - if (d->visual->c_class != DirectColor || !ownColormap) - return; - - // preallocate 768 on the stack, so that we don't have to malloc - // for the common case (<= 24 bpp) - QVarLengthArray<XColor, 768> colorTable(d->r_max + d->g_max + d->b_max); - int i = 0; - - for (int r = 0; r < d->r_max; ++r) { - colorTable[i].red = r << 8 | r; - colorTable[i].pixel = r << d->r_shift; - colorTable[i].flags = DoRed; - ++i; - } - - for (int g = 0; g < d->g_max; ++g) { - colorTable[i].green = g << 8 | g; - colorTable[i].pixel = g << d->g_shift; - colorTable[i].flags = DoGreen; - ++i; - } - - for (int b = 0; b < d->b_max; ++b) { - colorTable[i].blue = (b << 8 | b); - colorTable[i].pixel = b << d->b_shift; - colorTable[i].flags = DoBlue; - ++i; - } - - XStoreColors(X11->display, d->colormap, colorTable.data(), colorTable.count()); -} - -static QXcbColormap **cmaps = nullptr; - -void QXcbColormap::initialize() -{ - Display *display = X11->display; - const int screens = ScreenCount(display); - - cmaps = new QXcbColormap*[screens]; - - for (int i = 0; i < screens; ++i) { - cmaps[i] = new QXcbColormap; - QXcbColormapPrivate * const d = cmaps[i]->d; - - bool use_stdcmap = false; - int color_count = X11->color_count; - - // defaults - d->depth = DefaultDepth(display, i); - d->colormap = DefaultColormap(display, i); - d->defaultColormap = true; - d->visual = DefaultVisual(display, i); - d->defaultVisual = true; - - Visual *argbVisual = nullptr; - - if (X11->visual && i == DefaultScreen(display)) { - // only use the outside colormap on the default screen - d->visual = find_visual(display, i, X11->visual->c_class, - XVisualIDFromVisual(X11->visual), - &d->depth, &d->defaultVisual); - } else if ((X11->visual_class != -1 && X11->visual_class >= 0 && X11->visual_class < 6) - || (X11->visual_id != -1)) { - // look for a specific visual or type of visual - d->visual = find_visual(display, i, X11->visual_class, X11->visual_id, - &d->depth, &d->defaultVisual); - } else if (!X11->custom_cmap) { - XStandardColormap *stdcmap = nullptr; - int ncmaps = 0; - -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - int nvi; - XVisualInfo templ; - templ.screen = i; - templ.depth = 32; - templ.c_class = TrueColor; - XVisualInfo *xvi = XGetVisualInfo(X11->display, VisualScreenMask | - VisualDepthMask | - VisualClassMask, &templ, &nvi); - for (int idx = 0; idx < nvi; ++idx) { - XRenderPictFormat *format = XRenderFindVisualFormat(X11->display, - xvi[idx].visual); - if (format->type == PictTypeDirect && format->direct.alphaMask) { - argbVisual = xvi[idx].visual; - break; - } - } - XFree(xvi); - } -#endif - if (XGetRGBColormaps(display, RootWindow(display, i), - &stdcmap, &ncmaps, XA_RGB_DEFAULT_MAP)) { - if (stdcmap) { - for (int c = 0; c < ncmaps; ++c) { - if (!stdcmap[c].red_max || - !stdcmap[c].green_max || - !stdcmap[c].blue_max || - !stdcmap[c].red_mult || - !stdcmap[c].green_mult || - !stdcmap[c].blue_mult) - continue; // invalid stdcmap - - XVisualInfo proto; - proto.visualid = stdcmap[c].visualid; - proto.screen = i; - - int nvisuals = 0; - XVisualInfo *vi = XGetVisualInfo(display, VisualIDMask | VisualScreenMask, - &proto, &nvisuals); - if (vi) { - if (nvisuals > 0) { - use_stdcmap = true; - - d->mode = ((vi[0].visual->c_class < StaticColor) - ? Gray - : ((vi[0].visual->c_class < TrueColor) - ? Indexed - : Direct)); - - d->depth = vi[0].depth; - d->colormap = stdcmap[c].colormap; - d->defaultColormap = true; - d->visual = vi[0].visual; - d->defaultVisual = (d->visual == DefaultVisual(display, i)); - - d->r_max = stdcmap[c].red_max + 1; - d->g_max = stdcmap[c].green_max + 1; - d->b_max = stdcmap[c].blue_max + 1; - - if (d->mode == Direct) { - // calculate offsets - d->r_shift = lowest_bit(d->visual->red_mask); - d->g_shift = lowest_bit(d->visual->green_mask); - d->b_shift = lowest_bit(d->visual->blue_mask); - } else { - d->r_shift = 0; - d->g_shift = 0; - d->b_shift = 0; - } - } - XFree(vi); - } - break; - } - XFree(stdcmap); - } - } - } - if (!use_stdcmap) { - switch (d->visual->c_class) { - case StaticGray: - d->mode = Gray; - - d->r_max = d->g_max = d->b_max = d->visual->map_entries; - break; - - case XGrayScale: - d->mode = Gray; - - // follow precedent set in libXmu... - if (color_count != 0) - d->r_max = d->g_max = d->b_max = color_count; - else if (d->visual->map_entries > 65000) - d->r_max = d->g_max = d->b_max = 4096; - else if (d->visual->map_entries > 4000) - d->r_max = d->g_max = d->b_max = 512; - else if (d->visual->map_entries > 250) - d->r_max = d->g_max = d->b_max = 12; - else - d->r_max = d->g_max = d->b_max = 4; - break; - - case StaticColor: - d->mode = Indexed; - - d->r_max = right_align(d->visual->red_mask) + 1; - d->g_max = right_align(d->visual->green_mask) + 1; - d->b_max = right_align(d->visual->blue_mask) + 1; - break; - - case PseudoColor: - d->mode = Indexed; - - // follow precedent set in libXmu... - if (color_count != 0) - d->r_max = d->g_max = d->b_max = cube_root(color_count); - else if (d->visual->map_entries > 65000) - d->r_max = d->g_max = d->b_max = 27; - else if (d->visual->map_entries > 4000) - d->r_max = d->g_max = d->b_max = 12; - else if (d->visual->map_entries > 250) - d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries - 125); - else - d->r_max = d->g_max = d->b_max = cube_root(d->visual->map_entries); - break; - - case TrueColor: - case DirectColor: - d->mode = Direct; - - d->r_max = right_align(d->visual->red_mask) + 1; - d->g_max = right_align(d->visual->green_mask) + 1; - d->b_max = right_align(d->visual->blue_mask) + 1; - - d->r_shift = lowest_bit(d->visual->red_mask); - d->g_shift = lowest_bit(d->visual->green_mask); - d->b_shift = lowest_bit(d->visual->blue_mask); - break; - } - } - - bool ownColormap = false; - if (X11->colormap && i == DefaultScreen(display)) { - // only use the outside colormap on the default screen - d->colormap = X11->colormap; - d->defaultColormap = (d->colormap == DefaultColormap(display, i)); - } else if ((!use_stdcmap - && (((d->visual->c_class & 1) && X11->custom_cmap) - || d->visual != DefaultVisual(display, i))) - || d->visual->c_class == DirectColor) { - // allocate custom colormap (we always do this when using DirectColor visuals) - d->colormap = - XCreateColormap(display, RootWindow(display, i), d->visual, - d->visual->c_class == DirectColor ? AllocAll : AllocNone); - d->defaultColormap = false; - ownColormap = true; - } - - switch (d->mode) { - case Gray: - init_gray(d, i); - break; - case Indexed: - init_indexed(d, i); - break; - case Direct: - init_direct(d, ownColormap); - break; - } - - QX11InfoData *screen = X11->screens + i; - screen->depth = d->depth; - screen->visual = d->visual; - screen->defaultVisual = d->defaultVisual; - screen->colormap = d->colormap; - screen->defaultColormap = d->defaultColormap; - screen->cells = screen->visual->map_entries; - - if (argbVisual) { - X11->argbVisuals[i] = argbVisual; - X11->argbColormaps[i] = XCreateColormap(display, RootWindow(display, i), argbVisual, AllocNone); - } - - // ### - // We assume that 8bpp == pseudocolor, but this is not - // always the case (according to the X server), so we need - // to make sure that our internal data is setup in a way - // that is compatible with our assumptions - if (screen->visual->c_class == TrueColor && screen->depth == 8 && screen->cells == 8) - screen->cells = 256; - } -} - -void QXcbColormap::cleanup() -{ - Display *display = X11->display; - const int screens = ScreenCount(display); - - for (int i = 0; i < screens; ++i) - delete cmaps[i]; - - delete [] cmaps; - cmaps = 0; -} - - -QXcbColormap QXcbColormap::instance(int screen) -{ - if (screen == -1) - screen = QXcbX11Info::appScreen(); - return *cmaps[screen]; -} - -/*! \internal - Constructs a new colormap. -*/ -QXcbColormap::QXcbColormap() - : d(new QXcbColormapPrivate) -{} - -QXcbColormap::QXcbColormap(const QXcbColormap &colormap) - :d (colormap.d) -{ d->ref.ref(); } - -QXcbColormap::~QXcbColormap() -{ - if (!d->ref.deref()) { - if (!d->defaultColormap) - XFreeColormap(X11->display, d->colormap); - delete d; - } -} - -QXcbColormap::Mode QXcbColormap::mode() const -{ return d->mode; } - -int QXcbColormap::depth() const -{ return d->depth; } - -int QXcbColormap::size() const -{ - return (d->mode == Gray - ? d->r_max - : (d->mode == Indexed - ? d->r_max * d->g_max * d->b_max - : -1)); -} - -uint QXcbColormap::pixel(const QColor &color) const -{ - const QRgba64 rgba64 = color.rgba64(); - // XXX We emulate the raster engine here by getting the - // 8-bit values, but we could instead use the 16-bit - // values for slightly better color accuracy - const uint r = (rgba64.red8() * d->r_max) >> 8; - const uint g = (rgba64.green8() * d->g_max) >> 8; - const uint b = (rgba64.blue8() * d->b_max) >> 8; - if (d->mode != Direct) { - if (d->mode == Gray) - return d->pixels.at((r * 30 + g * 59 + b * 11) / 100); - return d->pixels.at(r * d->g_max * d->b_max + g * d->b_max + b); - } - return (r << d->r_shift) + (g << d->g_shift) + (b << d->b_shift); -} - -const QColor QXcbColormap::colorAt(uint pixel) const -{ - if (d->mode != Direct) { - Q_ASSERT(pixel <= (uint)d->colors.size()); - return d->colors.at(pixel); - } - - const int r = (((pixel & d->visual->red_mask) >> d->r_shift) << 8) / d->r_max; - const int g = (((pixel & d->visual->green_mask) >> d->g_shift) << 8) / d->g_max; - const int b = (((pixel & d->visual->blue_mask) >> d->b_shift) << 8) / d->b_max; - return QColor(r, g, b); -} - -const QList<QColor> QXcbColormap::colormap() const -{ return d->colors; } - -QXcbColormap &QXcbColormap::operator=(const QXcbColormap &colormap) -{ - qAtomicAssign(d, colormap.d); - return *this; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h deleted file mode 100644 index 9c9cce424c9..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qcolormap_x11_p.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <QColor> -#include <QList> - -QT_BEGIN_NAMESPACE - -class QXcbColormapPrivate; -class QXcbColormap -{ -public: - enum Mode { Direct, Indexed, Gray }; - - static void initialize(); - static void cleanup(); - - static QXcbColormap instance(int screen = -1); - - QXcbColormap(const QXcbColormap &colormap); - ~QXcbColormap(); - - QXcbColormap &operator=(const QXcbColormap &colormap); - - Mode mode() const; - - int depth() const; - int size() const; - - uint pixel(const QColor &color) const; - const QColor colorAt(uint pixel) const; - - const QList<QColor> colormap() const; - -private: - QXcbColormap(); - QXcbColormapPrivate *d; -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp deleted file mode 100644 index 2f3827025f3..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp +++ /dev/null @@ -1,2807 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include <QtCore/qrandom.h> - -#include <private/qpixmapcache_p.h> -#include <private/qpaintengine_p.h> -#include <private/qpainterpath_p.h> -#include <private/qdrawhelper_p.h> -#include <private/qfontengineglyphcache_p.h> - -#if QT_CONFIG(fontconfig) -#include <private/qfontengine_ft_p.h> -#endif - -#include "qpaintengine_x11_p.h" -#include "qpolygonclipper_p.h" -#include "qtessellator_p.h" -#include "qpixmap_x11_p.h" -#include "qcolormap_x11_p.h" -#include "qt_x11_p.h" -#include "qxcbexport.h" -#include "qxcbnativepainting.h" - -QT_BEGIN_NAMESPACE - -using namespace Qt::StringLiterals; - -#if QT_CONFIG(xrender) - -class QXRenderTessellator : public QTessellator -{ -public: - QXRenderTessellator() : traps(0), allocated(0), size(0) {} - ~QXRenderTessellator() { free(traps); } - XTrapezoid *traps; - int allocated; - int size; - void addTrap(const Trapezoid &trap) override; - QRect tessellate(const QPointF *points, int nPoints, bool winding) { - size = 0; - setWinding(winding); - return QTessellator::tessellate(points, nPoints).toRect(); - } - void done() { - if (allocated > 64) { - free(traps); - traps = 0; - allocated = 0; - } - } -}; - -void QXRenderTessellator::addTrap(const Trapezoid &trap) -{ - if (size == allocated) { - allocated = qMax(2*allocated, 64); - traps = q_check_ptr((XTrapezoid *)realloc(traps, allocated * sizeof(XTrapezoid))); - } - traps[size].top = Q27Dot5ToXFixed(trap.top); - traps[size].bottom = Q27Dot5ToXFixed(trap.bottom); - traps[size].left.p1.x = Q27Dot5ToXFixed(trap.topLeft->x); - traps[size].left.p1.y = Q27Dot5ToXFixed(trap.topLeft->y); - traps[size].left.p2.x = Q27Dot5ToXFixed(trap.bottomLeft->x); - traps[size].left.p2.y = Q27Dot5ToXFixed(trap.bottomLeft->y); - traps[size].right.p1.x = Q27Dot5ToXFixed(trap.topRight->x); - traps[size].right.p1.y = Q27Dot5ToXFixed(trap.topRight->y); - traps[size].right.p2.x = Q27Dot5ToXFixed(trap.bottomRight->x); - traps[size].right.p2.y = Q27Dot5ToXFixed(trap.bottomRight->y); - ++size; -} - -#endif // QT_CONFIG(xrender) - -class QX11PaintEnginePrivate : public QPaintEnginePrivate -{ - Q_DECLARE_PUBLIC(QX11PaintEngine) -public: - QX11PaintEnginePrivate() - { - opacity = 1.0; - scrn = -1; - hd = 0; - picture = 0; - gc = gc_brush = 0; - dpy = 0; - xinfo = 0; - txop = QTransform::TxNone; - has_clipping = false; - render_hints = 0; - xform_scale = 1; -#if QT_CONFIG(xrender) - tessellator = 0; -#endif - } - enum GCMode { - PenGC, - BrushGC - }; - - void init(); - void fillPolygon_translated(const QPointF *points, int pointCount, GCMode gcMode, - QPaintEngine::PolygonDrawMode mode); - void fillPolygon_dev(const QPointF *points, int pointCount, GCMode gcMode, - QPaintEngine::PolygonDrawMode mode); - void fillPath(const QPainterPath &path, GCMode gcmode, bool transform); - void strokePolygon_dev(const QPointF *points, int pointCount, bool close); - void strokePolygon_translated(const QPointF *points, int pointCount, bool close); - void setupAdaptedOrigin(const QPoint &p); - void resetAdaptedOrigin(); - void decidePathFallback() { - use_path_fallback = has_alpha_brush - || has_alpha_pen - || has_custom_pen - || has_complex_xform - || (render_hints & QPainter::Antialiasing); - } - void decideCoordAdjust() { - adjust_coords = false; - } - void clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly); - void systemStateChanged() override; - inline bool isCosmeticPen() const { - return cpen.isCosmetic(); - } - - Display *dpy; - int scrn; - int pdev_depth; - unsigned long hd; - QPixmap brush_pm; -#if QT_CONFIG(xrender) - unsigned long picture; - unsigned long current_brush; - QPixmap bitmap_texture; - int composition_mode; -#else - unsigned long picture; -#endif - GC gc; - GC gc_brush; - - QPen cpen; - QBrush cbrush; - QRegion crgn; - QTransform matrix; - qreal opacity; - - uint has_complex_xform : 1; - uint has_scaling_xform : 1; - uint has_non_scaling_xform : 1; - uint has_custom_pen : 1; - uint use_path_fallback : 1; - uint adjust_coords : 1; - uint has_clipping : 1; - uint adapted_brush_origin : 1; - uint adapted_pen_origin : 1; - uint has_pen : 1; - uint has_brush : 1; - uint has_texture : 1; - uint has_alpha_texture : 1; - uint has_pattern : 1; - uint has_alpha_pen : 1; - uint has_alpha_brush : 1; - uint use_sysclip : 1; - uint render_hints; - - const QXcbX11Info *xinfo; - QPointF bg_origin; - QTransform::TransformationType txop; - qreal xform_scale; - - struct qt_float_point - { - qreal x, y; - }; - QPolygonClipper<qt_float_point, qt_float_point, float> polygonClipper; - - int xlibMaxLinePoints; -#if QT_CONFIG(xrender) - QXRenderTessellator *tessellator; -#endif -}; - -#if QT_CONFIG(xrender) -class QXRenderGlyphCache : public QFontEngineGlyphCache -{ -public: - QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix); - ~QXRenderGlyphCache(); - - bool addGlyphs(const QTextItemInt &ti, - const QVarLengthArray<glyph_t> &glyphs, - const QVarLengthArray<QFixedPoint> &positions); - bool draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti); - - inline GlyphSet glyphSet(); - inline int glyphBufferSize(const QFontEngineFT::Glyph &glyph) const; - - inline QImage::Format imageFormat() const; - inline const XRenderPictFormat *renderPictFormat() const; - - static inline QFontEngine::GlyphFormat glyphFormatForDepth(QFontEngine *fontEngine, int depth); - static inline Glyph glyphId(glyph_t glyph, QFixed subPixelPosition); - - static inline bool isValidCoordinate(const QFixedPoint &fp); - -private: - QXcbX11Info xinfo; - GlyphSet gset; - QSet<Glyph> cachedGlyphs; -}; -#endif // QT_CONFIG(xrender) - -extern QPixmap qt_pixmapForBrush(int brushStyle, bool invert); //in qbrush.cpp -extern QPixmap qt_toX11Pixmap(const QPixmap &pixmap); - -extern "C" { -Q_XCB_EXPORT Drawable qt_x11Handle(const QPaintDevice *pd) -{ - if (!pd) - return 0; - -// if (pd->devType() == QInternal::Widget) -// return static_cast<const QWidget *>(pd)->handle(); - - if (pd->devType() == QInternal::Pixmap) - return qt_x11PixmapHandle(*static_cast<const QPixmap *>(pd)); - - return 0; -} -} - -static const QXcbX11Info *qt_x11Info(const QPaintDevice *pd) -{ - if (!pd) - return 0; - -// if (pd->devType() == QInternal::Widget) -// return &static_cast<const QWidget *>(pd)->x11Info(); - - if (pd->devType() == QInternal::Pixmap) - return &qt_x11Info(*static_cast<const QPixmap *>(pd)); - - return 0; -} - -// use the same rounding as in qrasterizer.cpp (6 bit fixed point) -static const qreal aliasedCoordinateDelta = 0.5 - 0.015625; - -#undef X11 // defined in qt_x11_p.h -extern "C" { -/*! - Returns the X11 specific pen GC for the painter \a p. Note that - QPainter::begin() must be called before this function returns a - valid GC. -*/ -GC Q_XCB_EXPORT qt_x11_get_pen_gc(QPainter *p) -{ - if (p && p->paintEngine() - && p->paintEngine()->isActive() - && p->paintEngine()->type() == QPaintEngine::X11) { - return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc; - } - return 0; -} - -/*! - Returns the X11 specific brush GC for the painter \a p. Note that - QPainter::begin() must be called before this function returns a - valid GC. -*/ -GC Q_XCB_EXPORT qt_x11_get_brush_gc(QPainter *p) -{ - if (p && p->paintEngine() - && p->paintEngine()->isActive() - && p->paintEngine()->type() == QPaintEngine::X11) { - return static_cast<QX11PaintEngine *>(p->paintEngine())->d_func()->gc_brush; - } - return 0; -} -} -#define X11 qt_x11Data - -// internal helper. Converts an integer value to an unique string token -template <typename T> - struct HexString -{ - inline HexString(const T t) - : val(t) - {} - - inline void write(QChar *&dest) const - { - const ushort hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - const char *c = reinterpret_cast<const char *>(&val); - for (uint i = 0; i < sizeof(T); ++i) { - *dest++ = hexChars[*c & 0xf]; - *dest++ = hexChars[(*c & 0xf0) >> 4]; - ++c; - } - } - const T val; -}; - -// specialization to enable fast concatenating of our string tokens to a string -template <typename T> - struct QConcatenable<HexString<T> > -{ - typedef HexString<T> type; - enum { ExactSize = true }; - static int size(const HexString<T> &) { return sizeof(T) * 2; } - static inline void appendTo(const HexString<T> &str, QChar *&out) { str.write(out); } - typedef QString ConvertTo; -}; - -#if QT_CONFIG(xrender) -static const int compositionModeToRenderOp[QPainter::CompositionMode_Xor + 1] = { - PictOpOver, //CompositionMode_SourceOver, - PictOpOverReverse, //CompositionMode_DestinationOver, - PictOpClear, //CompositionMode_Clear, - PictOpSrc, //CompositionMode_Source, - PictOpDst, //CompositionMode_Destination, - PictOpIn, //CompositionMode_SourceIn, - PictOpInReverse, //CompositionMode_DestinationIn, - PictOpOut, //CompositionMode_SourceOut, - PictOpOutReverse, //CompositionMode_DestinationOut, - PictOpAtop, //CompositionMode_SourceAtop, - PictOpAtopReverse, //CompositionMode_DestinationAtop, - PictOpXor //CompositionMode_Xor -}; - -static inline int qpainterOpToXrender(QPainter::CompositionMode mode) -{ - Q_ASSERT(mode <= QPainter::CompositionMode_Xor); - return compositionModeToRenderOp[mode]; -} - -static inline bool complexPictOp(int op) -{ - return op != PictOpOver && op != PictOpSrc; -} -#endif - -static inline void x11SetClipRegion(Display *dpy, GC gc, GC gc2, -#if QT_CONFIG(xrender) - Picture picture, -#else - Qt::HANDLE picture, -#endif - const QRegion &r) -{ -// int num; -// XRectangle *rects = (XRectangle *)qt_getClipRects(r, num); - QList<XRectangle> rects = qt_region_to_xrectangles(r); - int num = rects.size(); - - if (gc) - XSetClipRectangles( dpy, gc, 0, 0, rects.data(), num, Unsorted ); - if (gc2) - XSetClipRectangles( dpy, gc2, 0, 0, rects.data(), num, Unsorted ); - -#if QT_CONFIG(xrender) - if (picture) - XRenderSetPictureClipRectangles(dpy, picture, 0, 0, rects.data(), num); -#else - Q_UNUSED(picture); -#endif // QT_CONFIG(xrender) -} - - -static inline void x11ClearClipRegion(Display *dpy, GC gc, GC gc2, -#if QT_CONFIG(xrender) - Picture picture -#else - Qt::HANDLE picture -#endif - ) -{ - if (gc) - XSetClipMask(dpy, gc, XNone); - if (gc2) - XSetClipMask(dpy, gc2, XNone); - -#if QT_CONFIG(xrender) - if (picture) { - XRenderPictureAttributes attrs; - attrs.clip_mask = XNone; - XRenderChangePicture (dpy, picture, CPClipMask, &attrs); - } -#else - Q_UNUSED(picture); -#endif // QT_CONFIG(xrender) -} - - -#define DITHER_SIZE 16 -static const uchar base_dither_matrix[DITHER_SIZE][DITHER_SIZE] = { - { 0,192, 48,240, 12,204, 60,252, 3,195, 51,243, 15,207, 63,255 }, - { 128, 64,176,112,140, 76,188,124,131, 67,179,115,143, 79,191,127 }, - { 32,224, 16,208, 44,236, 28,220, 35,227, 19,211, 47,239, 31,223 }, - { 160, 96,144, 80,172,108,156, 92,163, 99,147, 83,175,111,159, 95 }, - { 8,200, 56,248, 4,196, 52,244, 11,203, 59,251, 7,199, 55,247 }, - { 136, 72,184,120,132, 68,180,116,139, 75,187,123,135, 71,183,119 }, - { 40,232, 24,216, 36,228, 20,212, 43,235, 27,219, 39,231, 23,215 }, - { 168,104,152, 88,164,100,148, 84,171,107,155, 91,167,103,151, 87 }, - { 2,194, 50,242, 14,206, 62,254, 1,193, 49,241, 13,205, 61,253 }, - { 130, 66,178,114,142, 78,190,126,129, 65,177,113,141, 77,189,125 }, - { 34,226, 18,210, 46,238, 30,222, 33,225, 17,209, 45,237, 29,221 }, - { 162, 98,146, 82,174,110,158, 94,161, 97,145, 81,173,109,157, 93 }, - { 10,202, 58,250, 6,198, 54,246, 9,201, 57,249, 5,197, 53,245 }, - { 138, 74,186,122,134, 70,182,118,137, 73,185,121,133, 69,181,117 }, - { 42,234, 26,218, 38,230, 22,214, 41,233, 25,217, 37,229, 21,213 }, - { 170,106,154, 90,166,102,150, 86,169,105,153, 89,165,101,149, 85 } -}; - -static QPixmap qt_patternForAlpha(uchar alpha, int screen) -{ - QPixmap pm; - QString key = "$qt-alpha-brush$"_L1 - % HexString<uchar>(alpha) - % HexString<int>(screen); - - if (!QPixmapCache::find(key, &pm)) { - // #### why not use a mono image here???? - QImage pattern(DITHER_SIZE, DITHER_SIZE, QImage::Format_ARGB32); - pattern.fill(0xffffffff); - for (int y = 0; y < DITHER_SIZE; ++y) { - for (int x = 0; x < DITHER_SIZE; ++x) { - if (base_dither_matrix[x][y] <= alpha) - pattern.setPixel(x, y, 0x00000000); - } - } - pm = QBitmap::fromImage(pattern); - qt_x11SetScreen(pm, screen); - //pm.x11SetScreen(screen); - QPixmapCache::insert(key, pm); - } - return pm; -} - - -#if QT_CONFIG(xrender) -static Picture getPatternFill(int screen, const QBrush &b) -{ - if (!X11->use_xrender) - return XNone; - - XRenderColor color = X11->preMultiply(b.color()); - XRenderColor bg_color; - - bg_color = X11->preMultiply(QColor(0, 0, 0, 0)); - - for (int i = 0; i < X11->pattern_fill_count; ++i) { - if (X11->pattern_fills[i].screen == screen - && X11->pattern_fills[i].opaque == false - && X11->pattern_fills[i].style == b.style() - && X11->pattern_fills[i].color.alpha == color.alpha - && X11->pattern_fills[i].color.red == color.red - && X11->pattern_fills[i].color.green == color.green - && X11->pattern_fills[i].color.blue == color.blue - && X11->pattern_fills[i].bg_color.alpha == bg_color.alpha - && X11->pattern_fills[i].bg_color.red == bg_color.red - && X11->pattern_fills[i].bg_color.green == bg_color.green - && X11->pattern_fills[i].bg_color.blue == bg_color.blue) - return X11->pattern_fills[i].picture; - } - // none found, replace one - int i = QRandomGenerator::global()->generate() % 16; - - if (X11->pattern_fills[i].screen != screen && X11->pattern_fills[i].picture) { - XRenderFreePicture (QXcbX11Info::display(), X11->pattern_fills[i].picture); - X11->pattern_fills[i].picture = 0; - } - - if (!X11->pattern_fills[i].picture) { - Pixmap pixmap = XCreatePixmap (QXcbX11Info::display(), RootWindow (QXcbX11Info::display(), screen), 8, 8, 32); - XRenderPictureAttributes attrs; - attrs.repeat = True; - X11->pattern_fills[i].picture = XRenderCreatePicture (QXcbX11Info::display(), pixmap, - XRenderFindStandardFormat(QXcbX11Info::display(), PictStandardARGB32), - CPRepeat, &attrs); - XFreePixmap (QXcbX11Info::display(), pixmap); - } - - X11->pattern_fills[i].screen = screen; - X11->pattern_fills[i].color = color; - X11->pattern_fills[i].bg_color = bg_color; - X11->pattern_fills[i].opaque = false; - X11->pattern_fills[i].style = b.style(); - - XRenderFillRectangle(QXcbX11Info::display(), PictOpSrc, X11->pattern_fills[i].picture, &bg_color, 0, 0, 8, 8); - - QPixmap pattern(qt_pixmapForBrush(b.style(), true)); - XRenderPictureAttributes attrs; - attrs.repeat = true; - XRenderChangePicture(QXcbX11Info::display(), qt_x11PictureHandle(pattern), CPRepeat, &attrs); - - Picture fill_fg = X11->getSolidFill(screen, b.color()); - XRenderComposite(QXcbX11Info::display(), PictOpOver, fill_fg, qt_x11PictureHandle(pattern), - X11->pattern_fills[i].picture, - 0, 0, 0, 0, 0, 0, 8, 8); - - return X11->pattern_fills[i].picture; -} - -static void qt_render_bitmap(Display *dpy, int scrn, Picture src, Picture dst, - int sx, int sy, int x, int y, int sw, int sh, - const QPen &pen) -{ - Picture fill_fg = X11->getSolidFill(scrn, pen.color()); - XRenderComposite(dpy, PictOpOver, - fill_fg, src, dst, sx, sy, sx, sy, x, y, sw, sh); -} -#endif - -void QX11PaintEnginePrivate::init() -{ - dpy = 0; - scrn = 0; - hd = 0; - picture = 0; - xinfo = 0; -#if QT_CONFIG(xrender) - current_brush = 0; - composition_mode = PictOpOver; - tessellator = new QXRenderTessellator; -#endif -} - -void QX11PaintEnginePrivate::setupAdaptedOrigin(const QPoint &p) -{ - if (adapted_pen_origin) - XSetTSOrigin(dpy, gc, p.x(), p.y()); - if (adapted_brush_origin) - XSetTSOrigin(dpy, gc_brush, p.x(), p.y()); -} - -void QX11PaintEnginePrivate::resetAdaptedOrigin() -{ - if (adapted_pen_origin) - XSetTSOrigin(dpy, gc, 0, 0); - if (adapted_brush_origin) - XSetTSOrigin(dpy, gc_brush, 0, 0); -} - -void QX11PaintEnginePrivate::clipPolygon_dev(const QPolygonF &poly, QPolygonF *clipped_poly) -{ - int clipped_count = 0; - qt_float_point *clipped_points = 0; - polygonClipper.clipPolygon((qt_float_point *) poly.data(), poly.size(), - &clipped_points, &clipped_count); - clipped_poly->resize(clipped_count); - for (int i=0; i<clipped_count; ++i) - (*clipped_poly)[i] = *((QPointF *)(&clipped_points[i])); -} - -void QX11PaintEnginePrivate::systemStateChanged() -{ - Q_Q(QX11PaintEngine); - QPainter *painter = q->state ? q->state->painter() : nullptr; - if (painter && painter->hasClipping()) { - if (q->testDirty(QPaintEngine::DirtyTransform)) - q->updateMatrix(q->state->transform()); - QPolygonF clip_poly_dev(matrix.map(painter->clipPath().toFillPolygon())); - QPolygonF clipped_poly_dev; - clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); - q->updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); - } else { - q->updateClipRegion_dev(QRegion(), Qt::NoClip); - } -} - -static QPaintEngine::PaintEngineFeatures qt_decide_features() -{ - QPaintEngine::PaintEngineFeatures features = - QPaintEngine::PrimitiveTransform - | QPaintEngine::PatternBrush - | QPaintEngine::AlphaBlend - | QPaintEngine::PainterPaths - | QPaintEngine::RasterOpModes; - - if (X11->use_xrender) { - features |= QPaintEngine::Antialiasing; - features |= QPaintEngine::PorterDuff; - features |= QPaintEngine::MaskedBrush; -#if 0 - if (X11->xrender_version > 10) { - features |= QPaintEngine::LinearGradientFill; - // ### - } -#endif - } - - return features; -} - -/* - * QX11PaintEngine members - */ - -QX11PaintEngine::QX11PaintEngine() - : QPaintEngine(*(new QX11PaintEnginePrivate), qt_decide_features()) -{ - Q_D(QX11PaintEngine); - d->init(); -} - -QX11PaintEngine::QX11PaintEngine(QX11PaintEnginePrivate &dptr) - : QPaintEngine(dptr, qt_decide_features()) -{ - Q_D(QX11PaintEngine); - d->init(); -} - -QX11PaintEngine::~QX11PaintEngine() -{ -#if QT_CONFIG(xrender) - Q_D(QX11PaintEngine); - delete d->tessellator; -#endif -} - -bool QX11PaintEngine::begin(QPaintDevice *pdev) -{ - Q_D(QX11PaintEngine); - d->xinfo = qt_x11Info(pdev); -#if QT_CONFIG(xrender) - if (pdev->devType() == QInternal::Pixmap) { - const QPixmap *pm = static_cast<const QPixmap *>(pdev); - QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(pm->handle()); - if (X11->use_xrender && data->depth() != 32 && data->x11_mask) - data->convertToARGB32(); - d->picture = qt_x11PictureHandle(*static_cast<const QPixmap *>(pdev)); - } -#else - d->picture = 0; -#endif - d->hd = qt_x11Handle(pdev); - - Q_ASSERT(d->xinfo != 0); - d->dpy = d->xinfo->display(); // get display variable - d->scrn = d->xinfo->screen(); // get screen variable - - d->crgn = QRegion(); - d->gc = XCreateGC(d->dpy, d->hd, 0, 0); - d->gc_brush = XCreateGC(d->dpy, d->hd, 0, 0); - d->has_alpha_brush = false; - d->has_alpha_pen = false; - d->has_clipping = false; - d->has_complex_xform = false; - d->has_scaling_xform = false; - d->has_non_scaling_xform = true; - d->xform_scale = 1; - d->has_custom_pen = false; - d->matrix = QTransform(); - d->pdev_depth = d->pdev->depth(); - d->render_hints = 0; - d->txop = QTransform::TxNone; - d->use_path_fallback = false; -#if QT_CONFIG(xrender) - d->composition_mode = PictOpOver; -#endif - d->xlibMaxLinePoints = 32762; // a safe number used to avoid, call to XMaxRequestSize(d->dpy) - 3; - d->opacity = 1; - - QX11PlatformPixmap *x11pm = paintDevice()->devType() == QInternal::Pixmap ? qt_x11Pixmap(*static_cast<QPixmap *>(paintDevice())) : nullptr; - d->use_sysclip = paintDevice()->devType() == QInternal::Widget || (x11pm ? x11pm->isBackingStore() : false); - - // Set up the polygon clipper. Note: This will only work in - // polyline mode as long as we have a buffer zone, since a - // polyline may be clipped into several non-connected polylines. - const int BUFFERZONE = 1000; - QRect devClipRect(-BUFFERZONE, -BUFFERZONE, - pdev->width() + 2*BUFFERZONE, pdev->height() + 2*BUFFERZONE); - d->polygonClipper.setBoundingRect(devClipRect); - - setSystemClip(systemClip()); - d->systemStateChanged(); - - qt_x11SetDefaultScreen(d->xinfo->screen()); - - updatePen(QPen(Qt::black)); - updateBrush(QBrush(Qt::white), QPoint()); - - setDirty(QPaintEngine::DirtyClipRegion); - setDirty(QPaintEngine::DirtyPen); - setDirty(QPaintEngine::DirtyBrush); - setDirty(QPaintEngine::DirtyBackground); - - setActive(true); - return true; -} - -bool QX11PaintEngine::end() -{ - Q_D(QX11PaintEngine); - -#if QT_CONFIG(xrender) - if (d->picture) { - // reset clipping/subwindow mode on our render picture - XRenderPictureAttributes attrs; - attrs.subwindow_mode = ClipByChildren; - attrs.clip_mask = XNone; - XRenderChangePicture(d->dpy, d->picture, CPClipMask|CPSubwindowMode, &attrs); - } -#endif - - if (d->gc_brush && d->pdev->painters < 2) { - XFreeGC(d->dpy, d->gc_brush); - d->gc_brush = 0; - } - - if (d->gc && d->pdev->painters < 2) { - XFreeGC(d->dpy, d->gc); - d->gc = 0; - } - - // Restore system clip for alien widgets painting outside the paint event. -// if (d->pdev->devType() == QInternal::Widget && !static_cast<QWidget *>(d->pdev)->internalWinId()) - d->currentClipDevice = nullptr; - setSystemClip(QRegion()); - - setActive(false); - return true; -} - -static bool clipLine(QLineF *line, const QRect &rect) -{ - qreal x1 = line->x1(); - qreal x2 = line->x2(); - qreal y1 = line->y1(); - qreal y2 = line->y2(); - - qreal left = rect.x(); - qreal right = rect.x() + rect.width() - 1; - qreal top = rect.y(); - qreal bottom = rect.y() + rect.height() - 1; - - enum { Left, Right, Top, Bottom }; - // clip the lines, after cohen-sutherland, see e.g. http://www.nondot.org/~sabre/graphpro/line6.html - int p1 = ((x1 < left) << Left) - | ((x1 > right) << Right) - | ((y1 < top) << Top) - | ((y1 > bottom) << Bottom); - int p2 = ((x2 < left) << Left) - | ((x2 > right) << Right) - | ((y2 < top) << Top) - | ((y2 > bottom) << Bottom); - - if (p1 & p2) - // completely outside - return false; - - if (p1 | p2) { - qreal dx = x2 - x1; - qreal dy = y2 - y1; - - // clip x coordinates - if (x1 < left) { - y1 += dy/dx * (left - x1); - x1 = left; - } else if (x1 > right) { - y1 -= dy/dx * (x1 - right); - x1 = right; - } - if (x2 < left) { - y2 += dy/dx * (left - x2); - x2 = left; - } else if (x2 > right) { - y2 -= dy/dx * (x2 - right); - x2 = right; - } - p1 = ((y1 < top) << Top) - | ((y1 > bottom) << Bottom); - p2 = ((y2 < top) << Top) - | ((y2 > bottom) << Bottom); - if (p1 & p2) - return false; - // clip y coordinates - if (y1 < top) { - x1 += dx/dy * (top - y1); - y1 = top; - } else if (y1 > bottom) { - x1 -= dx/dy * (y1 - bottom); - y1 = bottom; - } - if (y2 < top) { - x2 += dx/dy * (top - y2); - y2 = top; - } else if (y2 > bottom) { - x2 -= dx/dy * (y2 - bottom); - y2 = bottom; - } - *line = QLineF(QPointF(x1, y1), QPointF(x2, y2)); - } - return true; -} - -void QX11PaintEngine::drawLines(const QLine *lines, int lineCount) -{ - Q_ASSERT(lines); - Q_ASSERT(lineCount); - Q_D(QX11PaintEngine); - - if (d->has_alpha_brush - || d->has_alpha_pen - || d->has_custom_pen - || (d->cpen.widthF() > 0 && d->has_complex_xform - && !d->has_non_scaling_xform) - || (d->render_hints & QPainter::Antialiasing)) { - for (int i = 0; i < lineCount; ++i) { - QPainterPath path(lines[i].p1()); - path.lineTo(lines[i].p2()); - drawPath(path); - } - return; - } - - if (d->has_pen) { - for (int i = 0; i < lineCount; ++i) { - QLineF linef; - if (d->txop == QTransform::TxNone) { - linef = lines[i]; - } else { - linef = d->matrix.map(QLineF(lines[i])); - } - if (clipLine(&linef, d->polygonClipper.boundingRect())) { - int x1 = qRound(linef.x1() + aliasedCoordinateDelta); - int y1 = qRound(linef.y1() + aliasedCoordinateDelta); - int x2 = qRound(linef.x2() + aliasedCoordinateDelta); - int y2 = qRound(linef.y2() + aliasedCoordinateDelta); - - XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); - } - } - } -} - -void QX11PaintEngine::drawLines(const QLineF *lines, int lineCount) -{ - Q_ASSERT(lines); - Q_ASSERT(lineCount); - Q_D(QX11PaintEngine); - - if (d->has_alpha_brush - || d->has_alpha_pen - || d->has_custom_pen - || (d->cpen.widthF() > 0 && d->has_complex_xform - && !d->has_non_scaling_xform) - || (d->render_hints & QPainter::Antialiasing)) { - for (int i = 0; i < lineCount; ++i) { - QPainterPath path(lines[i].p1()); - path.lineTo(lines[i].p2()); - drawPath(path); - } - return; - } - - if (d->has_pen) { - for (int i = 0; i < lineCount; ++i) { - QLineF linef = d->matrix.map(lines[i]); - if (clipLine(&linef, d->polygonClipper.boundingRect())) { - int x1 = qRound(linef.x1() + aliasedCoordinateDelta); - int y1 = qRound(linef.y1() + aliasedCoordinateDelta); - int x2 = qRound(linef.x2() + aliasedCoordinateDelta); - int y2 = qRound(linef.y2() + aliasedCoordinateDelta); - - XDrawLine(d->dpy, d->hd, d->gc, x1, y1, x2, y2); - } - } - } -} - -static inline QLine clipStraightLine(const QRect &clip, const QLine &l) -{ - if (l.p1().x() == l.p2().x()) { - int x = qBound(clip.left(), l.p1().x(), clip.right()); - int y1 = qBound(clip.top(), l.p1().y(), clip.bottom()); - int y2 = qBound(clip.top(), l.p2().y(), clip.bottom()); - - return QLine(x, y1, x, y2); - } else { - Q_ASSERT(l.p1().y() == l.p2().y()); - - int x1 = qBound(clip.left(), l.p1().x(), clip.right()); - int x2 = qBound(clip.left(), l.p2().x(), clip.right()); - int y = qBound(clip.top(), l.p1().y(), clip.bottom()); - - return QLine(x1, y, x2, y); - } -} - -void QX11PaintEngine::drawRects(const QRectF *rects, int rectCount) -{ - Q_D(QX11PaintEngine); - Q_ASSERT(rects); - Q_ASSERT(rectCount); - - if (rectCount != 1 - || d->has_pen - || d->has_alpha_brush - || d->has_complex_xform - || d->has_custom_pen - || d->cbrush.style() != Qt::SolidPattern -#if QT_CONFIG(xrender) - || complexPictOp(d->composition_mode) -#endif - ) - { - QPaintEngine::drawRects(rects, rectCount); - return; - } - - QPoint alignedOffset; - if (d->txop == QTransform::TxTranslate) { - QPointF offset(d->matrix.dx(), d->matrix.dy()); - alignedOffset = offset.toPoint(); - if (offset != QPointF(alignedOffset)) { - QPaintEngine::drawRects(rects, rectCount); - return; - } - } - - const QRectF& r = rects[0]; - QRect alignedRect = r.toAlignedRect(); - if (r != QRectF(alignedRect)) { - QPaintEngine::drawRects(rects, rectCount); - return; - } - alignedRect.translate(alignedOffset); - - QRect clip(d->polygonClipper.boundingRect()); - alignedRect = alignedRect.intersected(clip); - if (alignedRect.isEmpty()) - return; - - // simple-case: - // the rectangle is pixel-aligned - // the fill brush is just a solid non-alpha color - // the painter transform is only integer translation - // ignore: antialiasing and just XFillRectangles directly - XRectangle xrect; - xrect.x = short(alignedRect.x()); - xrect.y = short(alignedRect.y()); - xrect.width = ushort(alignedRect.width()); - xrect.height = ushort(alignedRect.height()); - XFillRectangles(d->dpy, d->hd, d->gc_brush, &xrect, 1); -} - -void QX11PaintEngine::drawRects(const QRect *rects, int rectCount) -{ - Q_D(QX11PaintEngine); - Q_ASSERT(rects); - Q_ASSERT(rectCount); - - if (d->has_alpha_pen - || d->has_complex_xform - || d->has_custom_pen - || (d->render_hints & QPainter::Antialiasing)) - { - for (int i = 0; i < rectCount; ++i) { - QPainterPath path; - path.addRect(rects[i]); - drawPath(path); - } - return; - } - - QRect clip(d->polygonClipper.boundingRect()); - QPoint offset(qRound(d->matrix.dx()), qRound(d->matrix.dy())); -#if QT_CONFIG(xrender) - ::Picture pict = d->picture; - - if (X11->use_xrender && pict && d->has_brush && d->pdev_depth != 1 - && (d->has_texture || d->has_alpha_brush || complexPictOp(d->composition_mode))) - { - XRenderColor xc; - if (!d->has_texture && !d->has_pattern) - xc = X11->preMultiply(d->cbrush.color()); - - for (int i = 0; i < rectCount; ++i) { - QRect r(rects[i]); - if (d->txop == QTransform::TxTranslate) - r.translate(offset); - - if (r.width() == 0 || r.height() == 0) { - if (d->has_pen) { - const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); - XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); - } - continue; - } - - r = r.intersected(clip); - if (r.isEmpty()) - continue; - if (d->has_texture || d->has_pattern) { - XRenderComposite(d->dpy, d->composition_mode, d->current_brush, 0, pict, - qRound(r.x() - d->bg_origin.x()), qRound(r.y() - d->bg_origin.y()), - 0, 0, r.x(), r.y(), r.width(), r.height()); - } else { - XRenderFillRectangle(d->dpy, d->composition_mode, pict, &xc, r.x(), r.y(), r.width(), r.height()); - } - if (d->has_pen) - XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); - } - } else -#endif // QT_CONFIG(xrender) - { - if (d->has_brush && d->has_pen) { - for (int i = 0; i < rectCount; ++i) { - QRect r(rects[i]); - if (d->txop == QTransform::TxTranslate) - r.translate(offset); - - if (r.width() == 0 || r.height() == 0) { - const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); - XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); - continue; - } - - r = r.intersected(clip); - if (r.isEmpty()) - continue; - d->setupAdaptedOrigin(r.topLeft()); - XFillRectangle(d->dpy, d->hd, d->gc_brush, r.x(), r.y(), r.width(), r.height()); - XDrawRectangle(d->dpy, d->hd, d->gc, r.x(), r.y(), r.width(), r.height()); - } - d->resetAdaptedOrigin(); - } else { - QVarLengthArray<XRectangle> xrects(rectCount); - int numClipped = rectCount; - for (int i = 0; i < rectCount; ++i) { - QRect r(rects[i]); - if (d->txop == QTransform::TxTranslate) - r.translate(offset); - - if (r.width() == 0 || r.height() == 0) { - --numClipped; - if (d->has_pen) { - const QLine l = clipStraightLine(clip, QLine(r.left(), r.top(), r.left() + r.width(), r.top() + r.height())); - XDrawLine(d->dpy, d->hd, d->gc, l.p1().x(), l.p1().y(), l.p2().x(), l.p2().y()); - } - continue; - } - - r = r.intersected(clip); - if (r.isEmpty()) { - --numClipped; - continue; - } - xrects[i].x = short(r.x()); - xrects[i].y = short(r.y()); - xrects[i].width = ushort(r.width()); - xrects[i].height = ushort(r.height()); - } - if (numClipped) { - d->setupAdaptedOrigin(rects[0].topLeft()); - if (d->has_brush) - XFillRectangles(d->dpy, d->hd, d->gc_brush, xrects.data(), numClipped); - else if (d->has_pen) - XDrawRectangles(d->dpy, d->hd, d->gc, xrects.data(), numClipped); - d->resetAdaptedOrigin(); - } - } - } -} - -static inline void setCapStyle(int cap_style, GC gc) -{ - ulong mask = GCCapStyle; - XGCValues vals; - vals.cap_style = cap_style; - XChangeGC(QXcbX11Info::display(), gc, mask, &vals); -} - -void QX11PaintEngine::drawPoints(const QPoint *points, int pointCount) -{ - Q_ASSERT(points); - Q_ASSERT(pointCount); - Q_D(QX11PaintEngine); - - if (!d->has_pen) - return; - - // use the same test here as in drawPath to ensure that we don't use the path fallback - // and end up in XDrawLines for pens with width <= 1 - if (d->cpen.widthF() > 1.0f - || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) - || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) - { - Qt::PenCapStyle capStyle = d->cpen.capStyle(); - if (capStyle == Qt::FlatCap) { - setCapStyle(CapProjecting, d->gc); - d->cpen.setCapStyle(Qt::SquareCap); - } - const QPoint *end = points + pointCount; - while (points < end) { - QPainterPath path; - path.moveTo(*points); - path.lineTo(points->x()+.005, points->y()); - drawPath(path); - ++points; - } - - if (capStyle == Qt::FlatCap) { - setCapStyle(CapButt, d->gc); - d->cpen.setCapStyle(capStyle); - } - return; - } - - static const int BUF_SIZE = 1024; - XPoint xPoints[BUF_SIZE]; - int i = 0, j = 0; - while (i < pointCount) { - while (i < pointCount && j < BUF_SIZE) { - const QPoint &xformed = d->matrix.map(points[i]); - int x = xformed.x(); - int y = xformed.y(); - if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { - xPoints[j].x = x; - xPoints[j].y = y; - ++j; - } - ++i; - } - if (j) - XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); - - j = 0; - } -} - -void QX11PaintEngine::drawPoints(const QPointF *points, int pointCount) -{ - Q_ASSERT(points); - Q_ASSERT(pointCount); - Q_D(QX11PaintEngine); - - if (!d->has_pen) - return; - - // use the same test here as in drawPath to ensure that we don't use the path fallback - // and end up in XDrawLines for pens with width <= 1 - if (d->cpen.widthF() > 1.0f - || (X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) - || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate)) - { - Qt::PenCapStyle capStyle = d->cpen.capStyle(); - if (capStyle == Qt::FlatCap) { - setCapStyle(CapProjecting, d->gc); - d->cpen.setCapStyle(Qt::SquareCap); - } - - const QPointF *end = points + pointCount; - while (points < end) { - QPainterPath path; - path.moveTo(*points); - path.lineTo(points->x() + 0.005, points->y()); - drawPath(path); - ++points; - } - if (capStyle == Qt::FlatCap) { - setCapStyle(CapButt, d->gc); - d->cpen.setCapStyle(capStyle); - } - return; - } - - static const int BUF_SIZE = 1024; - XPoint xPoints[BUF_SIZE]; - int i = 0, j = 0; - while (i < pointCount) { - while (i < pointCount && j < BUF_SIZE) { - const QPointF &xformed = d->matrix.map(points[i]); - int x = qFloor(xformed.x()); - int y = qFloor(xformed.y()); - - if (x >= SHRT_MIN && y >= SHRT_MIN && x < SHRT_MAX && y < SHRT_MAX) { - xPoints[j].x = x; - xPoints[j].y = y; - ++j; - } - ++i; - } - if (j) - XDrawPoints(d->dpy, d->hd, d->gc, xPoints, j, CoordModeOrigin); - - j = 0; - } -} - -QPainter::RenderHints QX11PaintEngine::supportedRenderHints() const -{ -#if QT_CONFIG(xrender) - if (X11->use_xrender) - return QPainter::Antialiasing; -#endif - return QFlag(0); -} - -void QX11PaintEngine::updateState(const QPaintEngineState &state) -{ - Q_D(QX11PaintEngine); - QPaintEngine::DirtyFlags flags = state.state(); - - - if (flags & DirtyOpacity) { - d->opacity = state.opacity(); - // Force update pen/brush as to get proper alpha colors propagated - flags |= DirtyPen; - flags |= DirtyBrush; - } - - if (flags & DirtyTransform) updateMatrix(state.transform()); - if (flags & DirtyPen) updatePen(state.pen()); - if (flags & (DirtyBrush | DirtyBrushOrigin)) updateBrush(state.brush(), state.brushOrigin()); - if (flags & DirtyFont) updateFont(state.font()); - - if (state.state() & DirtyClipEnabled) { - if (state.isClipEnabled()) { - QPolygonF clip_poly_dev(d->matrix.map(painter()->clipPath().toFillPolygon())); - QPolygonF clipped_poly_dev; - d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); - updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), Qt::ReplaceClip); - } else { - updateClipRegion_dev(QRegion(), Qt::NoClip); - } - } - - if (flags & DirtyClipPath) { - QPolygonF clip_poly_dev(d->matrix.map(state.clipPath().toFillPolygon())); - QPolygonF clipped_poly_dev; - d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); - updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon(), state.clipPath().fillRule()), - state.clipOperation()); - } else if (flags & DirtyClipRegion) { - extern QPainterPath qt_regionToPath(const QRegion ®ion); - QPainterPath clip_path = qt_regionToPath(state.clipRegion()); - QPolygonF clip_poly_dev(d->matrix.map(clip_path.toFillPolygon())); - QPolygonF clipped_poly_dev; - d->clipPolygon_dev(clip_poly_dev, &clipped_poly_dev); - updateClipRegion_dev(QRegion(clipped_poly_dev.toPolygon()), state.clipOperation()); - } - if (flags & DirtyHints) updateRenderHints(state.renderHints()); - if (flags & DirtyCompositionMode) { - int function = GXcopy; - if (state.compositionMode() >= QPainter::RasterOp_SourceOrDestination) { - switch (state.compositionMode()) { - case QPainter::RasterOp_SourceOrDestination: - function = GXor; - break; - case QPainter::RasterOp_SourceAndDestination: - function = GXand; - break; - case QPainter::RasterOp_SourceXorDestination: - function = GXxor; - break; - case QPainter::RasterOp_NotSourceAndNotDestination: - function = GXnor; - break; - case QPainter::RasterOp_NotSourceOrNotDestination: - function = GXnand; - break; - case QPainter::RasterOp_NotSourceXorDestination: - function = GXequiv; - break; - case QPainter::RasterOp_NotSource: - function = GXcopyInverted; - break; - case QPainter::RasterOp_SourceAndNotDestination: - function = GXandReverse; - break; - case QPainter::RasterOp_NotSourceAndDestination: - function = GXandInverted; - break; - default: - function = GXcopy; - } - } -#if QT_CONFIG(xrender) - else { - d->composition_mode = - qpainterOpToXrender(state.compositionMode()); - } -#endif - XSetFunction(X11->display, d->gc, function); - XSetFunction(X11->display, d->gc_brush, function); - } - d->decidePathFallback(); - d->decideCoordAdjust(); -} - -void QX11PaintEngine::updateRenderHints(QPainter::RenderHints hints) -{ - Q_D(QX11PaintEngine); - d->render_hints = hints; - -#if QT_CONFIG(xrender) - if (X11->use_xrender && d->picture) { - XRenderPictureAttributes attrs; - attrs.poly_edge = (hints & QPainter::Antialiasing) ? PolyEdgeSmooth : PolyEdgeSharp; - XRenderChangePicture(d->dpy, d->picture, CPPolyEdge, &attrs); - } -#endif -} - -void QX11PaintEngine::updatePen(const QPen &pen) -{ - Q_D(QX11PaintEngine); - d->cpen = pen; - int cp = CapButt; - int jn = JoinMiter; - int ps = pen.style(); - - if (d->opacity < 1.0) { - QColor c = d->cpen.color(); - c.setAlpha(qRound(c.alpha()*d->opacity)); - d->cpen.setColor(c); - } - - d->has_pen = (ps != Qt::NoPen); - d->has_alpha_pen = (pen.color().alpha() != 255); - - switch (pen.capStyle()) { - case Qt::SquareCap: - cp = CapProjecting; - break; - case Qt::RoundCap: - cp = CapRound; - break; - case Qt::FlatCap: - default: - cp = CapButt; - break; - } - switch (pen.joinStyle()) { - case Qt::BevelJoin: - jn = JoinBevel; - break; - case Qt::RoundJoin: - jn = JoinRound; - break; - case Qt::MiterJoin: - default: - jn = JoinMiter; - break; - } - - d->adapted_pen_origin = false; - - char dashes[10]; // custom pen dashes - int dash_len = 0; // length of dash list - int xStyle = LineSolid; - - /* - We are emulating Windows here. Windows treats cpen.width() == 1 - (or 0) as a very special case. The fudge variable unifies this - case with the general case. - */ - qreal pen_width = pen.widthF(); - int scale = qRound(pen_width < 1 ? 1 : pen_width); - int space = (pen_width < 1 && pen_width > 0 ? 1 : (2 * scale)); - int dot = 1 * scale; - int dash = 4 * scale; - - d->has_custom_pen = false; - - switch (ps) { - case Qt::NoPen: - case Qt::SolidLine: - xStyle = LineSolid; - break; - case Qt::DashLine: - dashes[0] = dash; - dashes[1] = space; - dash_len = 2; - xStyle = LineOnOffDash; - break; - case Qt::DotLine: - dashes[0] = dot; - dashes[1] = space; - dash_len = 2; - xStyle = LineOnOffDash; - break; - case Qt::DashDotLine: - dashes[0] = dash; - dashes[1] = space; - dashes[2] = dot; - dashes[3] = space; - dash_len = 4; - xStyle = LineOnOffDash; - break; - case Qt::DashDotDotLine: - dashes[0] = dash; - dashes[1] = space; - dashes[2] = dot; - dashes[3] = space; - dashes[4] = dot; - dashes[5] = space; - dash_len = 6; - xStyle = LineOnOffDash; - break; - case Qt::CustomDashLine: - d->has_custom_pen = true; - break; - } - - ulong mask = GCForeground | GCBackground | GCGraphicsExposures | GCLineWidth - | GCCapStyle | GCJoinStyle | GCLineStyle; - XGCValues vals; - vals.graphics_exposures = false; - if (d->pdev_depth == 1) { - vals.foreground = qGray(pen.color().rgb()) > 127 ? 0 : 1; - vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; - } else if (d->pdev->devType() == QInternal::Pixmap && d->pdev_depth == 32 - && X11->use_xrender) { - vals.foreground = pen.color().rgba(); - vals.background = QColor(Qt::transparent).rgba(); - } else { - QXcbColormap cmap = QXcbColormap::instance(d->scrn); - vals.foreground = cmap.pixel(pen.color()); - vals.background = cmap.pixel(QColor(Qt::transparent)); - } - - - vals.line_width = qRound(pen.widthF()); - vals.cap_style = cp; - vals.join_style = jn; - vals.line_style = xStyle; - - XChangeGC(d->dpy, d->gc, mask, &vals); - - if (dash_len) { // make dash list - XSetDashes(d->dpy, d->gc, 0, dashes, dash_len); - } - - if (!d->has_clipping) { // if clipping is set the paintevent clip region is merged with the clip region - QRegion sysClip = d->use_sysclip ? systemClip() : QRegion(); - if (!sysClip.isEmpty()) - x11SetClipRegion(d->dpy, d->gc, 0, d->picture, sysClip); - else - x11ClearClipRegion(d->dpy, d->gc, 0, d->picture); - } -} - -void QX11PaintEngine::updateBrush(const QBrush &brush, const QPointF &origin) -{ - Q_D(QX11PaintEngine); - d->cbrush = brush; - d->bg_origin = origin; - d->adapted_brush_origin = false; -#if QT_CONFIG(xrender) - d->current_brush = 0; -#endif - if (d->opacity < 1.0) { - QColor c = d->cbrush.color(); - c.setAlpha(qRound(c.alpha()*d->opacity)); - d->cbrush.setColor(c); - } - - int s = FillSolid; - int bs = d->cbrush.style(); - d->has_brush = (bs != Qt::NoBrush); - d->has_pattern = bs >= Qt::Dense1Pattern && bs <= Qt::DiagCrossPattern; - d->has_texture = bs == Qt::TexturePattern; - d->has_alpha_brush = brush.color().alpha() != 255; - d->has_alpha_texture = d->has_texture && d->cbrush.texture().hasAlphaChannel(); - - ulong mask = GCForeground | GCBackground | GCGraphicsExposures - | GCLineStyle | GCCapStyle | GCJoinStyle | GCFillStyle; - XGCValues vals; - vals.graphics_exposures = false; - if (d->pdev_depth == 1) { - vals.foreground = qGray(d->cbrush.color().rgb()) > 127 ? 0 : 1; - vals.background = qGray(QColor(Qt::transparent).rgb()) > 127 ? 0 : 1; - } else if (X11->use_xrender && d->pdev->devType() == QInternal::Pixmap - && d->pdev_depth == 32) { - vals.foreground = d->cbrush.color().rgba(); - vals.background = QColor(Qt::transparent).rgba(); - } else { - QXcbColormap cmap = QXcbColormap::instance(d->scrn); - vals.foreground = cmap.pixel(d->cbrush.color()); - vals.background = cmap.pixel(QColor(Qt::transparent)); - - if (!X11->use_xrender && d->has_brush && !d->has_pattern && !brush.isOpaque()) { - QPixmap pattern = qt_patternForAlpha(brush.color().alpha(), d->scrn); - mask |= GCStipple; - vals.stipple = qt_x11PixmapHandle(pattern); - s = FillStippled; - d->adapted_brush_origin = true; - } - } - vals.cap_style = CapButt; - vals.join_style = JoinMiter; - vals.line_style = LineSolid; - - if (d->has_pattern || d->has_texture) { - if (bs == Qt::TexturePattern) { - d->brush_pm = qt_toX11Pixmap(d->cbrush.texture()); -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - XRenderPictureAttributes attrs; - attrs.repeat = true; - XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->brush_pm), CPRepeat, &attrs); - QX11PlatformPixmap *data = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle()); - if (data->mask_picture) - XRenderChangePicture(d->dpy, data->mask_picture, CPRepeat, &attrs); - } -#endif - } else { - d->brush_pm = qt_toX11Pixmap(qt_pixmapForBrush(bs, true)); - } - qt_x11SetScreen(d->brush_pm, d->scrn); - if (d->brush_pm.depth() == 1) { - mask |= GCStipple; - vals.stipple = qt_x11PixmapHandle(d->brush_pm); - s = FillStippled; -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - d->bitmap_texture = QPixmap(d->brush_pm.size()); - d->bitmap_texture.fill(Qt::transparent); - d->bitmap_texture = qt_toX11Pixmap(d->bitmap_texture); - qt_x11SetScreen(d->bitmap_texture, d->scrn); - - ::Picture src = X11->getSolidFill(d->scrn, d->cbrush.color()); - XRenderComposite(d->dpy, PictOpSrc, src, qt_x11PictureHandle(d->brush_pm), - qt_x11PictureHandle(d->bitmap_texture), - 0, 0, d->brush_pm.width(), d->brush_pm.height(), - 0, 0, d->brush_pm.width(), d->brush_pm.height()); - - XRenderPictureAttributes attrs; - attrs.repeat = true; - XRenderChangePicture(d->dpy, qt_x11PictureHandle(d->bitmap_texture), CPRepeat, &attrs); - - d->current_brush = qt_x11PictureHandle(d->bitmap_texture); - } -#endif - } else { - mask |= GCTile; -#if QT_CONFIG(xrender) - if (d->pdev_depth == 32 && d->brush_pm.depth() != 32) { - d->brush_pm.detach(); - QX11PlatformPixmap *brushData = static_cast<QX11PlatformPixmap*>(d->brush_pm.handle()); - brushData->convertToARGB32(); - } -#endif - vals.tile = (d->brush_pm.depth() == d->pdev_depth - ? qt_x11PixmapHandle(d->brush_pm) - : static_cast<QX11PlatformPixmap*>(d->brush_pm.handle())->x11ConvertToDefaultDepth()); - s = FillTiled; -#if QT_CONFIG(xrender) - d->current_brush = qt_x11PictureHandle(d->cbrush.texture()); -#endif - } - - mask |= GCTileStipXOrigin | GCTileStipYOrigin; - vals.ts_x_origin = qRound(origin.x()); - vals.ts_y_origin = qRound(origin.y()); - } -#if QT_CONFIG(xrender) - else if (d->has_alpha_brush) { - d->current_brush = X11->getSolidFill(d->scrn, d->cbrush.color()); - } -#endif - - vals.fill_style = s; - XChangeGC(d->dpy, d->gc_brush, mask, &vals); - if (!d->has_clipping) { - QRegion sysClip = d->use_sysclip ? systemClip() : QRegion(); - if (!sysClip.isEmpty()) - x11SetClipRegion(d->dpy, d->gc_brush, 0, d->picture, sysClip); - else - x11ClearClipRegion(d->dpy, d->gc_brush, 0, d->picture); - } -} - -void QX11PaintEngine::drawEllipse(const QRectF &rect) -{ - QRect aligned = rect.toAlignedRect(); - if (aligned == rect) - drawEllipse(aligned); - else - QPaintEngine::drawEllipse(rect); -} - -void QX11PaintEngine::drawEllipse(const QRect &rect) -{ - if (rect.isEmpty()) { - drawRects(&rect, 1); - return; - } - - Q_D(QX11PaintEngine); - QRect devclip(SHRT_MIN, SHRT_MIN, SHRT_MAX*2 - 1, SHRT_MAX*2 - 1); - QRect r(rect); - if (d->txop < QTransform::TxRotate) { - r = d->matrix.mapRect(rect); - } else if (d->txop == QTransform::TxRotate && rect.width() == rect.height()) { - QPainterPath path; - path.addEllipse(rect); - r = d->matrix.map(path).boundingRect().toRect(); - } - - if (d->has_alpha_brush || d->has_alpha_pen || d->has_custom_pen || (d->render_hints & QPainter::Antialiasing) - || d->has_alpha_texture || devclip.intersected(r) != r - || (d->has_complex_xform - && !(d->has_non_scaling_xform && rect.width() == rect.height()))) - { - QPainterPath path; - path.addEllipse(rect); - drawPath(path); - return; - } - - int x = r.x(); - int y = r.y(); - int w = r.width(); - int h = r.height(); - if (w < 1 || h < 1) - return; - if (w == 1 && h == 1) { - XDrawPoint(d->dpy, d->hd, d->has_pen ? d->gc : d->gc_brush, x, y); - return; - } - d->setupAdaptedOrigin(rect.topLeft()); - if (d->has_brush) { // draw filled ellipse - XFillArc(d->dpy, d->hd, d->gc_brush, x, y, w, h, 0, 360*64); - if (!d->has_pen) // make smoother outline - XDrawArc(d->dpy, d->hd, d->gc_brush, x, y, w-1, h-1, 0, 360*64); - } - if (d->has_pen) // draw outline - XDrawArc(d->dpy, d->hd, d->gc, x, y, w, h, 0, 360*64); - d->resetAdaptedOrigin(); -} - - - -void QX11PaintEnginePrivate::fillPolygon_translated(const QPointF *polygonPoints, int pointCount, - QX11PaintEnginePrivate::GCMode gcMode, - QPaintEngine::PolygonDrawMode mode) -{ - - QVarLengthArray<QPointF> translated_points(pointCount); - QPointF offset(matrix.dx(), matrix.dy()); - - qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; - if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) - offset += QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta); - - for (int i = 0; i < pointCount; ++i) { - translated_points[i] = polygonPoints[i] + offset; - - translated_points[i].rx() = qRound(translated_points[i].x()) + offs; - translated_points[i].ry() = qRound(translated_points[i].y()) + offs; - } - - fillPolygon_dev(translated_points.data(), pointCount, gcMode, mode); -} - -#if QT_CONFIG(xrender) -static void qt_XRenderCompositeTrapezoids(Display *dpy, - int op, - Picture src, - Picture dst, - _Xconst XRenderPictFormat *maskFormat, - int xSrc, - int ySrc, - const XTrapezoid *traps, int size) -{ - const int MAX_TRAPS = 50000; - while (size) { - int to_draw = size; - if (to_draw > MAX_TRAPS) - to_draw = MAX_TRAPS; - XRenderCompositeTrapezoids(dpy, op, src, dst, - maskFormat, - xSrc, ySrc, - traps, to_draw); - size -= to_draw; - traps += to_draw; - } -} -#endif - -void QX11PaintEnginePrivate::fillPolygon_dev(const QPointF *polygonPoints, int pointCount, - QX11PaintEnginePrivate::GCMode gcMode, - QPaintEngine::PolygonDrawMode mode) -{ - Q_Q(QX11PaintEngine); - - int clippedCount = 0; - qt_float_point *clippedPoints = 0; - -#if QT_CONFIG(xrender) - //can change if we switch to pen if gcMode != BrushGC - bool has_fill_texture = has_texture; - bool has_fill_pattern = has_pattern; - ::Picture src; -#endif - QBrush fill; - GC fill_gc; - if (gcMode == BrushGC) { - fill = cbrush; - fill_gc = gc_brush; -#if QT_CONFIG(xrender) - if (current_brush) - src = current_brush; - else - src = X11->getSolidFill(scrn, fill.color()); -#endif - } else { - fill = QBrush(cpen.brush()); - fill_gc = gc; -#if QT_CONFIG(xrender) - //we use the pens brush - has_fill_texture = (fill.style() == Qt::TexturePattern); - has_fill_pattern = (fill.style() >= Qt::Dense1Pattern && fill.style() <= Qt::DiagCrossPattern); - if (has_fill_texture) - src = qt_x11PictureHandle(fill.texture()); - else if (has_fill_pattern) - src = getPatternFill(scrn, fill); - else - src = X11->getSolidFill(scrn, fill.color()); -#endif - } - - polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, - &clippedPoints, &clippedCount); - -#if QT_CONFIG(xrender) - bool solid_fill = fill.color().alpha() == 255; - if (has_fill_texture && fill.texture().depth() == 1 && solid_fill) { - has_fill_texture = false; - has_fill_pattern = true; - } - - bool antialias = render_hints & QPainter::Antialiasing; - - if (X11->use_xrender - && picture - && !has_fill_pattern - && (clippedCount > 0) - && (fill.style() != Qt::NoBrush) - && ((has_fill_texture && fill.texture().hasAlpha()) || antialias || !solid_fill || has_alpha_pen != has_alpha_brush)) - { - tessellator->tessellate((QPointF *)clippedPoints, clippedCount, - mode == QPaintEngine::WindingMode); - if (tessellator->size > 0) { - XRenderPictureAttributes attrs; - attrs.poly_edge = antialias ? PolyEdgeSmooth : PolyEdgeSharp; - XRenderChangePicture(dpy, picture, CPPolyEdge, &attrs); - int x_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.x) - bg_origin.x()); - int y_offset = int(XFixedToDouble(tessellator->traps[0].left.p1.y) - bg_origin.y()); - qt_XRenderCompositeTrapezoids(dpy, composition_mode, src, picture, - antialias - ? XRenderFindStandardFormat(dpy, PictStandardA8) - : XRenderFindStandardFormat(dpy, PictStandardA1), - x_offset, y_offset, - tessellator->traps, tessellator->size); - tessellator->done(); - } - } else -#endif - if (fill.style() != Qt::NoBrush) { - if (clippedCount > 200000) { - QPolygon poly; - for (int i = 0; i < clippedCount; ++i) - poly << QPoint(qFloor(clippedPoints[i].x), qFloor(clippedPoints[i].y)); - - const QRect bounds = poly.boundingRect(); - const QRect aligned = bounds - & QRect(QPoint(), QSize(pdev->width(), pdev->height())); - - QImage img(aligned.size(), QImage::Format_ARGB32_Premultiplied); - img.fill(0); - - QPainter painter(&img); - painter.translate(-aligned.x(), -aligned.y()); - painter.setPen(Qt::NoPen); - painter.setBrush(fill); - if (gcMode == BrushGC) - painter.setBrushOrigin(q->painter()->brushOriginF()); - painter.drawPolygon(poly); - painter.end(); - - q->drawImage(aligned, img, img.rect(), Qt::AutoColor); - } else if (clippedCount > 0) { - QVarLengthArray<XPoint> xpoints(clippedCount); - for (int i = 0; i < clippedCount; ++i) { - xpoints[i].x = qFloor(clippedPoints[i].x); - xpoints[i].y = qFloor(clippedPoints[i].y); - } - if (mode == QPaintEngine::WindingMode) - XSetFillRule(dpy, fill_gc, WindingRule); - setupAdaptedOrigin(QPoint(xpoints[0].x, xpoints[0].y)); - XFillPolygon(dpy, hd, fill_gc, - xpoints.data(), clippedCount, - mode == QPaintEngine::ConvexMode ? Convex : Complex, CoordModeOrigin); - resetAdaptedOrigin(); - if (mode == QPaintEngine::WindingMode) - XSetFillRule(dpy, fill_gc, EvenOddRule); - } - } -} - -void QX11PaintEnginePrivate::strokePolygon_translated(const QPointF *polygonPoints, int pointCount, bool close) -{ - QVarLengthArray<QPointF> translated_points(pointCount); - QPointF offset(matrix.dx(), matrix.dy()); - for (int i = 0; i < pointCount; ++i) - translated_points[i] = polygonPoints[i] + offset; - strokePolygon_dev(translated_points.data(), pointCount, close); -} - -void QX11PaintEnginePrivate::strokePolygon_dev(const QPointF *polygonPoints, int pointCount, bool close) -{ - int clippedCount = 0; - qt_float_point *clippedPoints = 0; - polygonClipper.clipPolygon((qt_float_point *) polygonPoints, pointCount, - &clippedPoints, &clippedCount, close); - - if (clippedCount > 0) { - QVarLengthArray<XPoint> xpoints(clippedCount); - for (int i = 0; i < clippedCount; ++i) { - xpoints[i].x = qRound(clippedPoints[i].x + aliasedCoordinateDelta); - xpoints[i].y = qRound(clippedPoints[i].y + aliasedCoordinateDelta); - } - uint numberPoints = qMin(clippedCount, xlibMaxLinePoints); - XPoint *pts = xpoints.data(); - XDrawLines(dpy, hd, gc, pts, numberPoints, CoordModeOrigin); - pts += numberPoints; - clippedCount -= numberPoints; - numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); - while (clippedCount) { - XDrawLines(dpy, hd, gc, pts-1, numberPoints+1, CoordModeOrigin); - pts += numberPoints; - clippedCount -= numberPoints; - numberPoints = qMin(clippedCount, xlibMaxLinePoints-1); - } - } -} - -void QX11PaintEngine::drawPolygon(const QPointF *polygonPoints, int pointCount, PolygonDrawMode mode) -{ - Q_D(QX11PaintEngine); - - if (d->use_path_fallback) { - QPainterPath path(polygonPoints[0]); - for (int i = 1; i < pointCount; ++i) - path.lineTo(polygonPoints[i]); - if (mode == PolylineMode) { - QBrush oldBrush = d->cbrush; - d->cbrush = QBrush(Qt::NoBrush); - path.setFillRule(Qt::WindingFill); - drawPath(path); - d->cbrush = oldBrush; - } else { - path.setFillRule(mode == OddEvenMode ? Qt::OddEvenFill : Qt::WindingFill); - path.closeSubpath(); - drawPath(path); - } - return; - } - if (mode != PolylineMode && d->has_brush) - d->fillPolygon_translated(polygonPoints, pointCount, QX11PaintEnginePrivate::BrushGC, mode); - - if (d->has_pen) - d->strokePolygon_translated(polygonPoints, pointCount, mode != PolylineMode); -} - - -void QX11PaintEnginePrivate::fillPath(const QPainterPath &path, QX11PaintEnginePrivate::GCMode gc_mode, bool transform) -{ - qreal offs = adjust_coords ? aliasedCoordinateDelta : 0.0; - - QPainterPath clippedPath; - QPainterPath clipPath; - clipPath.addRect(polygonClipper.boundingRect()); - - if (transform) - clippedPath = (path*matrix).intersected(clipPath); - else - clippedPath = path.intersected(clipPath); - - QList<QPolygonF> polys = clippedPath.toFillPolygons(); - for (int i = 0; i < polys.size(); ++i) { - QVarLengthArray<QPointF> translated_points(polys.at(i).size()); - - for (int j = 0; j < polys.at(i).size(); ++j) { - translated_points[j] = polys.at(i).at(j); - if (!X11->use_xrender || !(render_hints & QPainter::Antialiasing)) { - translated_points[j].rx() = qRound(translated_points[j].rx() + aliasedCoordinateDelta) + offs; - translated_points[j].ry() = qRound(translated_points[j].ry() + aliasedCoordinateDelta) + offs; - } - } - - fillPolygon_dev(translated_points.data(), polys.at(i).size(), gc_mode, - path.fillRule() == Qt::OddEvenFill ? QPaintEngine::OddEvenMode : QPaintEngine::WindingMode); - } -} - -void QX11PaintEngine::drawPath(const QPainterPath &path) -{ - Q_D(QX11PaintEngine); - if (path.isEmpty()) - return; - - if (d->has_brush) - d->fillPath(path, QX11PaintEnginePrivate::BrushGC, true); - if (d->has_pen - && ((X11->use_xrender && (d->has_alpha_pen || (d->render_hints & QPainter::Antialiasing))) - || (!d->isCosmeticPen() && d->txop > QTransform::TxTranslate - && !d->has_non_scaling_xform) - || (d->cpen.style() == Qt::CustomDashLine))) { - QPainterPathStroker stroker; - if (d->cpen.style() == Qt::CustomDashLine) { - stroker.setDashPattern(d->cpen.dashPattern()); - stroker.setDashOffset(d->cpen.dashOffset()); - } else { - stroker.setDashPattern(d->cpen.style()); - } - stroker.setCapStyle(d->cpen.capStyle()); - stroker.setJoinStyle(d->cpen.joinStyle()); - QPainterPath stroke; - qreal width = d->cpen.widthF(); - QPolygonF poly; - QRectF deviceRect(0, 0, d->pdev->width(), d->pdev->height()); - // necessary to get aliased alphablended primitives to be drawn correctly - if (d->isCosmeticPen() || d->has_scaling_xform) { - if (d->isCosmeticPen()) - stroker.setWidth(width == 0 ? 1 : width); - else - stroker.setWidth(width * d->xform_scale); - stroker.d_ptr->stroker.setClipRect(deviceRect); - stroke = stroker.createStroke(path * d->matrix); - if (stroke.isEmpty()) - return; - stroke.setFillRule(Qt::WindingFill); - d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, false); - } else { - stroker.setWidth(width); - stroker.d_ptr->stroker.setClipRect(d->matrix.inverted().mapRect(deviceRect)); - stroke = stroker.createStroke(path); - if (stroke.isEmpty()) - return; - stroke.setFillRule(Qt::WindingFill); - d->fillPath(stroke, QX11PaintEnginePrivate::PenGC, true); - } - } else if (d->has_pen) { - // if we have a cosmetic pen - use XDrawLine() for speed - QList<QPolygonF> polys = path.toSubpathPolygons(d->matrix); - for (int i = 0; i < polys.size(); ++i) - d->strokePolygon_dev(polys.at(i).data(), polys.at(i).size(), false); - } -} - -Q_GUI_EXPORT void qt_x11_drawImage(const QRect &rect, const QPoint &pos, const QImage &image, - Drawable hd, GC gc, Display *dpy, Visual *visual, int depth) -{ - Q_ASSERT(image.format() == QImage::Format_RGB32); - Q_ASSERT(image.depth() == 32); - - XImage *xi; - // Note: this code assumes either RGB or BGR, 8 bpc server layouts - const uint red_mask = (uint) visual->red_mask; - bool bgr_layout = (red_mask == 0xff); - - const int w = rect.width(); - const int h = rect.height(); - - QImage im; - int image_byte_order = ImageByteOrder(QXcbX11Info::display()); - if ((QSysInfo::ByteOrder == QSysInfo::BigEndian && ((image_byte_order == LSBFirst) || bgr_layout)) - || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) - || (image_byte_order == LSBFirst && bgr_layout)) - { - im = image.copy(rect); - const qsizetype iw = im.bytesPerLine() / 4; - uint *data = (uint *)im.bits(); - for (int i=0; i < h; i++) { - uint *p = data; - uint *end = p + w; - if (bgr_layout && image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian) { - while (p < end) { - *p = ((*p << 8) & 0xffffff00) | ((*p >> 24) & 0x000000ff); - p++; - } - } else if ((image_byte_order == LSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) - || (image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::LittleEndian)) { - while (p < end) { - *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) - | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); - p++; - } - } else if ((image_byte_order == MSBFirst && QSysInfo::ByteOrder == QSysInfo::BigEndian) - || (image_byte_order == LSBFirst && bgr_layout)) - { - while (p < end) { - *p = ((*p << 16) & 0x00ff0000) | ((*p >> 16) & 0x000000ff) - | ((*p ) & 0xff00ff00); - p++; - } - } - data += iw; - } - xi = XCreateImage(dpy, visual, depth, ZPixmap, - 0, (char *) im.bits(), w, h, 32, im.bytesPerLine()); - } else { - xi = XCreateImage(dpy, visual, depth, ZPixmap, - 0, (char *) image.scanLine(rect.y())+rect.x()*sizeof(uint), w, h, 32, image.bytesPerLine()); - } - XPutImage(dpy, hd, gc, xi, 0, 0, pos.x(), pos.y(), w, h); - xi->data = 0; // QImage owns these bits - XDestroyImage(xi); -} - -void QX11PaintEngine::drawImage(const QRectF &r, const QImage &image, const QRectF &sr, Qt::ImageConversionFlags flags) -{ - Q_D(QX11PaintEngine); - - if (image.format() == QImage::Format_RGB32 - && d->pdev_depth >= 24 && image.depth() == 32 - && r.size() == sr.size()) - { - int sx = qRound(sr.x()); - int sy = qRound(sr.y()); - int x = qRound(r.x()); - int y = qRound(r.y()); - int w = qRound(r.width()); - int h = qRound(r.height()); - - qt_x11_drawImage(QRect(sx, sy, w, h), QPoint(x, y), image, d->hd, d->gc, d->dpy, - (Visual *)d->xinfo->visual(), d->pdev_depth); - } else { - QPaintEngine::drawImage(r, image, sr, flags); - } -} - -void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRectF &_sr) -{ - Q_D(QX11PaintEngine); - QRectF sr = _sr; - int x = qRound(r.x()); - int y = qRound(r.y()); - int sx = qRound(sr.x()); - int sy = qRound(sr.y()); - int sw = qRound(sr.width()); - int sh = qRound(sr.height()); - - QPixmap pixmap = qt_toX11Pixmap(px); - if (pixmap.isNull()) - return; - - if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) - || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { - qt_x11SetScreen(pixmap, d->xinfo ? d->xinfo->screen() : DefaultScreen(X11->display)); - } - - qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); - -#if QT_CONFIG(xrender) - ::Picture src_pict = qt_x11PictureHandle(pixmap); - if (src_pict && d->picture) { - const int pDepth = pixmap.depth(); - if (pDepth == 1 && (d->has_alpha_pen)) { - qt_render_bitmap(d->dpy, d->scrn, src_pict, d->picture, - sx, sy, x, y, sw, sh, d->cpen); - return; - } else if (pDepth != 1 && (pDepth == 32 || pDepth != d->pdev_depth)) { - XRenderComposite(d->dpy, d->composition_mode, - src_pict, 0, d->picture, sx, sy, 0, 0, x, y, sw, sh); - return; - } - } -#endif - - bool mono_src = pixmap.depth() == 1; - bool mono_dst = d->pdev_depth == 1; - bool restore_clip = false; - - if (static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) { // pixmap has a mask - QBitmap comb(sw, sh); - GC cgc = XCreateGC(d->dpy, qt_x11PixmapHandle(comb), 0, 0); - XSetForeground(d->dpy, cgc, 0); - XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); - XSetBackground(d->dpy, cgc, 0); - XSetForeground(d->dpy, cgc, 1); - if (!d->crgn.isEmpty()) { - QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn); - XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); - } else if (d->has_clipping) { - XSetClipRectangles(d->dpy, cgc, 0, 0, 0, 0, Unsorted); - } - XSetFillStyle(d->dpy, cgc, FillOpaqueStippled); - XSetTSOrigin(d->dpy, cgc, -sx, -sy); - XSetStipple(d->dpy, cgc, - static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask); - XFillRectangle(d->dpy, qt_x11PixmapHandle(comb), cgc, 0, 0, sw, sh); - XFreeGC(d->dpy, cgc); - - XSetClipOrigin(d->dpy, d->gc, x, y); - XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(comb)); - restore_clip = true; - } - - if (mono_src) { - if (!d->crgn.isEmpty()) { - Pixmap comb = XCreatePixmap(d->dpy, d->hd, sw, sh, 1); - GC cgc = XCreateGC(d->dpy, comb, 0, 0); - XSetForeground(d->dpy, cgc, 0); - XFillRectangle(d->dpy, comb, cgc, 0, 0, sw, sh); - QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn); - XSetClipRectangles(d->dpy, cgc, -x, -y, rects.data(), rects.size(), Unsorted); - XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), comb, cgc, sx, sy, sw, sh, 0, 0); - XFreeGC(d->dpy, cgc); - - XSetClipMask(d->dpy, d->gc, comb); - XSetClipOrigin(d->dpy, d->gc, x, y); - XFreePixmap(d->dpy, comb); - } else { - XSetClipMask(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); - XSetClipOrigin(d->dpy, d->gc, x - sx, y - sy); - } - - if (mono_dst) { - XSetForeground(d->dpy, d->gc, qGray(d->cpen.color().rgb()) > 127 ? 0 : 1); - } else { - QXcbColormap cmap = QXcbColormap::instance(d->scrn); - XSetForeground(d->dpy, d->gc, cmap.pixel(d->cpen.color())); - } - XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh); - restore_clip = true; - } else if (mono_dst && !mono_src) { - QBitmap bitmap = QBitmap::fromPixmap(pixmap); - XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y); - } else { - XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y); - } - - if (d->pdev->devType() == QInternal::Pixmap) { - const QPixmap *px = static_cast<const QPixmap*>(d->pdev); - Pixmap src_mask = static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask; - Pixmap dst_mask = static_cast<QX11PlatformPixmap*>(px->handle())->x11_mask; - if (dst_mask) { - GC cgc = XCreateGC(d->dpy, dst_mask, 0, 0); - XSetClipOrigin(d->dpy, cgc, x, y); - XSetClipMask(d->dpy, cgc, src_mask); - if (src_mask) { // copy src mask into dst mask - XCopyArea(d->dpy, src_mask, dst_mask, cgc, sx, sy, sw, sh, x, y); - } else { // no src mask, but make sure the area copied is opaque in dest - XSetBackground(d->dpy, cgc, 0); - XSetForeground(d->dpy, cgc, 1); - XFillRectangle(d->dpy, dst_mask, cgc, x, y, sw, sh); - } - XFreeGC(d->dpy, cgc); - } - } - - if (restore_clip) { - XSetClipOrigin(d->dpy, d->gc, 0, 0); - QList<XRectangle> rects = qt_region_to_xrectangles(d->crgn); - if (rects.isEmpty()) - XSetClipMask(d->dpy, d->gc, XNone); - else - XSetClipRectangles(d->dpy, d->gc, 0, 0, rects.data(), rects.size(), Unsorted); - } -} - -void QX11PaintEngine::updateMatrix(const QTransform &mtx) -{ - Q_D(QX11PaintEngine); - d->txop = mtx.type(); - d->matrix = mtx; - - d->has_complex_xform = (d->txop > QTransform::TxTranslate); - - extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); - bool scaling = qt_scaleForTransform(d->matrix, &d->xform_scale); - d->has_scaling_xform = scaling && d->xform_scale != 1.0; - d->has_non_scaling_xform = scaling && d->xform_scale == 1.0; -} - -/* - NB! the clip region is expected to be in dev coordinates -*/ -void QX11PaintEngine::updateClipRegion_dev(const QRegion &clipRegion, Qt::ClipOperation op) -{ - Q_D(QX11PaintEngine); - QRegion sysClip = d->use_sysclip ? systemClip() : QRegion(); - if (op == Qt::NoClip) { - d->has_clipping = false; - d->crgn = sysClip; - if (!sysClip.isEmpty()) { - x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, sysClip); - } else { - x11ClearClipRegion(d->dpy, d->gc, d->gc_brush, d->picture); - } - return; - } - - switch (op) { - case Qt::IntersectClip: - if (d->has_clipping) { - d->crgn &= clipRegion; - break; - } - // fall through - case Qt::ReplaceClip: - if (!sysClip.isEmpty()) - d->crgn = clipRegion.intersected(sysClip); - else - d->crgn = clipRegion; - break; -// case Qt::UniteClip: -// d->crgn |= clipRegion; -// if (!sysClip.isEmpty()) -// d->crgn = d->crgn.intersected(sysClip); -// break; - default: - break; - } - d->has_clipping = true; - x11SetClipRegion(d->dpy, d->gc, d->gc_brush, d->picture, d->crgn); -} - -void QX11PaintEngine::updateFont(const QFont &) -{ -} - -Drawable QX11PaintEngine::handle() const -{ - Q_D(const QX11PaintEngine); - Q_ASSERT(isActive()); - Q_ASSERT(d->hd); - return d->hd; -} - -extern void qt_draw_tile(QPaintEngine *, qreal, qreal, qreal, qreal, const QPixmap &, - qreal, qreal); - -void QX11PaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &p) -{ - int x = qRound(r.x()); - int y = qRound(r.y()); - int w = qRound(r.width()); - int h = qRound(r.height()); - int sx = qRound(p.x()); - int sy = qRound(p.y()); - - bool mono_src = pixmap.depth() == 1; - Q_D(QX11PaintEngine); - - if ((d->xinfo && d->xinfo->screen() != qt_x11Info(pixmap).screen()) - || (qt_x11Info(pixmap).screen() != DefaultScreen(QXcbX11Info::display()))) { - QPixmap* p = const_cast<QPixmap *>(&pixmap); - qt_x11SetScreen(*p, d->xinfo ? d->xinfo->screen() : DefaultScreen(QXcbX11Info::display())); - } - - qt_x11SetDefaultScreen(qt_x11Info(pixmap).screen()); - -#if QT_CONFIG(xrender) - if (X11->use_xrender && d->picture && qt_x11PictureHandle(pixmap)) { - const int numTiles = (w / pixmap.width()) * (h / pixmap.height()); - if (numTiles < 100) { - // this is essentially qt_draw_tile(), inlined for - // the XRenderComposite call - int yPos, xPos, drawH, drawW, yOff, xOff; - yPos = y; - yOff = sy; - while (yPos < y + h) { - drawH = pixmap.height() - yOff; // Cropping first row - if (yPos + drawH > y + h) // Cropping last row - drawH = y + h - yPos; - xPos = x; - xOff = sx; - while (xPos < x + w) { - drawW = pixmap.width() - xOff; // Cropping first column - if (xPos + drawW > x + w) // Cropping last column - drawW = x + w - xPos; - if (mono_src) { - qt_render_bitmap(d->dpy, d->scrn, qt_x11PictureHandle(pixmap), d->picture, - xOff, yOff, xPos, yPos, drawW, drawH, d->cpen); - } else { - XRenderComposite(d->dpy, d->composition_mode, - qt_x11PictureHandle(pixmap), XNone, d->picture, - xOff, yOff, 0, 0, xPos, yPos, drawW, drawH); - } - xPos += drawW; - xOff = 0; - } - yPos += drawH; - yOff = 0; - } - } else { - w = qMin(w, d->pdev->width() - x); - h = qMin(h, d->pdev->height() - y); - if (w <= 0 || h <= 0) - return; - - const int pw = w + sx; - const int ph = h + sy; - QPixmap pm(pw, ph); - if (pixmap.hasAlpha() || mono_src) - pm.fill(Qt::transparent); - - const int mode = pixmap.hasAlpha() ? PictOpOver : PictOpSrc; - const ::Picture pmPicture = qt_x11PictureHandle(pm); - - // first tile - XRenderComposite(d->dpy, mode, - qt_x11PictureHandle(pixmap), XNone, pmPicture, - 0, 0, 0, 0, 0, 0, qMin(pw, pixmap.width()), qMin(ph, pixmap.height())); - - // first row of tiles - int xPos = pixmap.width(); - const int sh = qMin(ph, pixmap.height()); - while (xPos < pw) { - const int sw = qMin(xPos, pw - xPos); - XRenderComposite(d->dpy, mode, - pmPicture, XNone, pmPicture, - 0, 0, 0, 0, xPos, 0, sw, sh); - xPos *= 2; - } - - // remaining rows - int yPos = pixmap.height(); - const int sw = pw; - while (yPos < ph) { - const int sh = qMin(yPos, ph - yPos); - XRenderComposite(d->dpy, mode, - pmPicture, XNone, pmPicture, - 0, 0, 0, 0, 0, yPos, sw, sh); - yPos *= 2; - } - - // composite - if (mono_src) - qt_render_bitmap(d->dpy, d->scrn, pmPicture, d->picture, - sx, sy, x, y, w, h, d->cpen); - else - XRenderComposite(d->dpy, d->composition_mode, - pmPicture, XNone, d->picture, - sx, sy, 0, 0, x, y, w, h); - } - } else -#endif // QT_CONFIG(xrender) - if (pixmap.depth() > 1 && !static_cast<QX11PlatformPixmap*>(pixmap.handle())->x11_mask) { - XSetTile(d->dpy, d->gc, qt_x11PixmapHandle(pixmap)); - XSetFillStyle(d->dpy, d->gc, FillTiled); - XSetTSOrigin(d->dpy, d->gc, x-sx, y-sy); - XFillRectangle(d->dpy, d->hd, d->gc, x, y, w, h); - XSetTSOrigin(d->dpy, d->gc, 0, 0); - XSetFillStyle(d->dpy, d->gc, FillSolid); - } else { - qt_draw_tile(this, x, y, w, h, pixmap, sx, sy); - } -} - -bool QX11PaintEngine::drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti) -{ -#if QT_CONFIG(xrender) - Q_D(QX11PaintEngine); - Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); - - if (!X11->use_xrender) - return false; - - QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); - QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); - - if (!set || set->outline_drawing) - return false; - - QFontEngine::GlyphFormat glyphFormat = QXRenderGlyphCache::glyphFormatForDepth(ft, d->pdev_depth); - - QXRenderGlyphCache *cache = static_cast<QXRenderGlyphCache *>(ft->glyphCache(set, glyphFormat, transform)); - if (!cache) { - cache = new QXRenderGlyphCache(QXcbX11Info(), glyphFormat, transform); - ft->setGlyphCache(set, cache); - } - - return cache->draw(X11->getSolidFill(d->scrn, d->cpen.color()), d->picture, transform, ti); -#else // !QT_CONFIG(xrender) - return false; -#endif // QT_CONFIG(xrender) -} - -void QX11PaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) -{ - Q_D(QX11PaintEngine); - const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); - - switch (ti.fontEngine->type()) { - case QFontEngine::TestFontEngine: - case QFontEngine::Box: - d->drawBoxTextItem(p, ti); - break; -#if QT_CONFIG(fontconfig) - case QFontEngine::Freetype: - drawFreetype(p, ti); - break; -#endif - default: - Q_ASSERT(false); - } -} - -#if QT_CONFIG(fontconfig) -static bool path_for_glyphs(QPainterPath *path, - const QVarLengthArray<glyph_t> &glyphs, - const QVarLengthArray<QFixedPoint> &positions, - const QFontEngineFT *ft) -{ - bool result = true; - *path = QPainterPath(); - path->setFillRule(Qt::WindingFill); - ft->lockFace(); - int i = 0; - while (i < glyphs.size()) { - QFontEngineFT::Glyph *glyph = ft->loadGlyph(glyphs[i], QFixedPoint(), QFontEngineFT::Format_Mono); - // #### fix case where we don't get a glyph - if (!glyph || glyph->format != QFontEngineFT::Format_Mono) { - result = false; - break; - } - - int n = 0; - int h = glyph->height; - int xp = qRound(positions[i].x); - int yp = qRound(positions[i].y); - - xp += glyph->x; - yp += -glyph->y + glyph->height; - int pitch = ((glyph->width + 31) & ~31) >> 3; - - uchar *src = glyph->data; - while (h--) { - for (int x = 0; x < glyph->width; ++x) { - bool set = src[x >> 3] & (0x80 >> (x & 7)); - if (set) { - QRect r(xp + x, yp - h, 1, 1); - while (x+1 < glyph->width && src[(x+1) >> 3] & (0x80 >> ((x+1) & 7))) { - ++x; - r.setRight(r.right()+1); - } - - path->addRect(r); - ++n; - } - } - src += pitch; - } - ++i; - } - ft->unlockFace(); - return result; -} - -void QX11PaintEngine::drawFreetype(const QPointF &p, const QTextItemInt &ti) -{ - Q_D(QX11PaintEngine); - - if (!ti.glyphs.numGlyphs) - return; - - if (!d->cpen.isSolid()) { - QPaintEngine::drawTextItem(p, ti); - return; - } - - const bool xrenderPath = (X11->use_xrender - && !(d->pdev->devType() == QInternal::Pixmap - && static_cast<const QPixmap *>(d->pdev)->handle()->pixelType() == QPlatformPixmap::BitmapType)); - - if (xrenderPath) { - QTransform transform = d->matrix; - transform.translate(p.x(), p.y()); - - if (drawCachedGlyphs(transform, ti)) - return; - } - - QTransform transform; - transform.translate(p.x(), p.y()); - - QVarLengthArray<QFixedPoint> positions; - QVarLengthArray<glyph_t> glyphs; - ti.fontEngine->getGlyphPositions(ti.glyphs, transform, ti.flags, glyphs, positions); - - if (glyphs.count() == 0) - return; - - QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); - QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform); - QPainterPath path; - - if (!set || set->outline_drawing || !path_for_glyphs(&path, glyphs, positions, ft)) { - QPaintEngine::drawTextItem(p, ti); - return; - } - - if (path.elementCount() <= 1) - return; - - Q_ASSERT((path.elementCount() % 5) == 0); - if (d->txop >= QTransform::TxScale) { - painter()->save(); - painter()->setBrush(d->cpen.brush()); - painter()->setPen(Qt::NoPen); - painter()->drawPath(path); - painter()->restore(); - return; - } - - const int rectcount = 256; - XRectangle rects[rectcount]; - int num_rects = 0; - - QPoint delta(qRound(d->matrix.dx()), qRound(d->matrix.dy())); - QRect clip(d->polygonClipper.boundingRect()); - for (int i=0; i < path.elementCount(); i+=5) { - int x = qRound(path.elementAt(i).x); - int y = qRound(path.elementAt(i).y); - int w = qRound(path.elementAt(i+1).x) - x; - int h = qRound(path.elementAt(i+2).y) - y; - - QRect rect = QRect(x + delta.x(), y + delta.y(), w, h); - rect = rect.intersected(clip); - if (rect.isEmpty()) - continue; - - rects[num_rects].x = short(rect.x()); - rects[num_rects].y = short(rect.y()); - rects[num_rects].width = ushort(rect.width()); - rects[num_rects].height = ushort(rect.height()); - ++num_rects; - if (num_rects == rectcount) { - XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); - num_rects = 0; - } - } - if (num_rects > 0) - XFillRectangles(d->dpy, d->hd, d->gc, rects, num_rects); -} -#endif // QT_CONFIG(fontconfig) - -#if QT_CONFIG(xrender) -QXRenderGlyphCache::QXRenderGlyphCache(QXcbX11Info x, QFontEngine::GlyphFormat format, const QTransform &matrix) - : QFontEngineGlyphCache(format, matrix) - , xinfo(x) - , gset(XNone) -{} - -QXRenderGlyphCache::~QXRenderGlyphCache() -{ - if (gset != XNone) - XRenderFreeGlyphSet(xinfo.display(), gset); -} - -bool QXRenderGlyphCache::addGlyphs(const QTextItemInt &ti, - const QVarLengthArray<glyph_t> &glyphs, - const QVarLengthArray<QFixedPoint> &positions) -{ - Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); - - QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); - QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(transform()); - - XGlyphInfo xglyphinfo; - - for (int i = 0; i < glyphs.size(); ++i) { - const QFixed sppx = ft->subPixelPositionForX(positions[i].x); - const QFixedPoint spp(sppx, 0); - QFontEngineFT::Glyph *glyph = set->getGlyph(glyphs[i], spp); - Glyph xglyphid = qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyphs[i], spp)); - - if (glyph && glyph->format == glyphFormat()) { - if (cachedGlyphs.contains(xglyphid)) { - continue; - } else { - set->setGlyph(glyphs[i], spp, nullptr); - delete glyph; - glyph = 0; - } - } - - glyph = ft->loadGlyphFor(glyphs[i], spp, glyphFormat(), transform(), QColor()); - - if (glyph == 0 || glyph->format != glyphFormat()) - return false; - - if (glyph->format == QFontEngine::Format_Mono) { - // Must convert bitmap from msb to lsb bit order - QImage img(glyph->data, glyph->width, glyph->height, QImage::Format_Mono); - img = img.convertToFormat(QImage::Format_MonoLSB); - memcpy(glyph->data, img.constBits(), static_cast<size_t>(img.sizeInBytes())); - } - - set->setGlyph(glyphs[i], spp, glyph); - Q_ASSERT(glyph->data || glyph->width == 0 || glyph->height == 0); - - xglyphinfo.width = glyph->width; - xglyphinfo.height = glyph->height; - xglyphinfo.x = -glyph->x; - xglyphinfo.y = glyph->y; - xglyphinfo.xOff = glyph->advance; - xglyphinfo.yOff = 0; - - XRenderAddGlyphs(xinfo.display(), glyphSet(), &xglyphid, &xglyphinfo, 1, (const char *) glyph->data, glyphBufferSize(*glyph)); - cachedGlyphs.insert(xglyphid); - } - - return true; -} - -bool QXRenderGlyphCache::draw(Drawable src, Drawable dst, const QTransform &matrix, const QTextItemInt &ti) -{ - Q_ASSERT(ti.fontEngine->type() == QFontEngine::Freetype); - - if (ti.glyphs.numGlyphs == 0) - return true; - - QFontEngineFT *ft = static_cast<QFontEngineFT *>(ti.fontEngine); - QFontEngineFT::QGlyphSet *set = ft->loadGlyphSet(matrix); - - QVarLengthArray<glyph_t> glyphs; - QVarLengthArray<QFixedPoint> positions; - ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); - - if (glyphs.isEmpty()) - return true; - - if (!addGlyphs(ti, glyphs, positions)) - return false; - - QVarLengthArray<unsigned int> chars(glyphs.size()); - - for (int i = 0; i < glyphs.size(); ++i) - chars[i] = glyphId(glyphs[i], ft->subPixelPositionForX(positions[i].x)); - - int i = 0; - while (i < glyphs.size() && !isValidCoordinate(positions[i])) - ++i; - - if (i >= glyphs.size()) - return true; - - QFixed xp = positions[i].x; - QFixed yp = positions[i].y; - QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); - - XGlyphElt32 elt; - elt.glyphset = gset; - elt.chars = &chars[i]; - elt.nchars = 1; - elt.xOff = qRound(xp + offs); - elt.yOff = qRound(yp + offs); - - ++i; - - for (; i < glyphs.size(); ++i) { - if (!isValidCoordinate(positions[i])) - break; - - const QFixed sppx = ft->subPixelPositionForX(positions[i].x); - const QFixedPoint spp(sppx, 0); - QFontEngineFT::Glyph *g = set->getGlyph(glyphs[i], spp); - - if (g - && positions[i].x == xp + g->advance - && positions[i].y == yp - && elt.nchars < 253 // don't draw more than 253 characters as some X servers - // hang with it - ) { - elt.nchars++; - xp += g->advance; - } else { - xp = positions[i].x; - yp = positions[i].y; - - XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, - renderPictFormat(), 0, 0, 0, 0, - &elt, 1); - elt.chars = &chars[i]; - elt.nchars = 1; - elt.xOff = qRound(xp + offs); - elt.yOff = qRound(yp + offs); - } - } - - XRenderCompositeText32(xinfo.display(), PictOpOver, src, dst, - renderPictFormat(), 0, 0, 0, 0, &elt, 1); - - return true; -} - -GlyphSet QXRenderGlyphCache::glyphSet() -{ - if (gset == XNone) - gset = XRenderCreateGlyphSet(xinfo.display(), renderPictFormat()); - - Q_ASSERT(gset != XNone); - return gset; -} - -int QXRenderGlyphCache::glyphBufferSize(const QFontEngineFT::Glyph &glyph) const -{ - int pitch = 0; - - switch (glyphFormat()) { - case QFontEngine::Format_Mono: - pitch = ((glyph.width + 31) & ~31) >> 3; - break; - case QFontEngine::Format_A8: - pitch = (glyph.width + 3) & ~3; - break; - default: - pitch = glyph.width * 4; - break; - } - - return pitch * glyph.height; -} - -QImage::Format QXRenderGlyphCache::imageFormat() const -{ - switch (glyphFormat()) { - case QFontEngine::Format_None: - Q_UNREACHABLE(); - break; - case QFontEngine::Format_Mono: - return QImage::Format_Mono; - break; - case QFontEngine::Format_A8: - return QImage::Format_Alpha8; - break; - case QFontEngine::Format_A32: - case QFontEngine::Format_ARGB: - return QImage::Format_ARGB32_Premultiplied; - break; - } - - Q_UNREACHABLE(); -} - -const XRenderPictFormat *QXRenderGlyphCache::renderPictFormat() const -{ - switch (glyphFormat()) { - case QFontEngine::Format_None: - Q_UNREACHABLE(); - break; - case QFontEngine::Format_Mono: - return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); - break; - case QFontEngine::Format_A8: - return XRenderFindStandardFormat(xinfo.display(), PictStandardA8); - break; - case QFontEngine::Format_A32: - case QFontEngine::Format_ARGB: - return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); - break; - } - - Q_UNREACHABLE(); -} - -QFontEngine::GlyphFormat QXRenderGlyphCache::glyphFormatForDepth(QFontEngine *fontEngine, int depth) -{ - QFontEngine::GlyphFormat glyphFormat = fontEngine->glyphFormat; - - if (glyphFormat == QFontEngine::Format_None) { - switch (depth) { - case 32: - glyphFormat = QFontEngine::Format_ARGB; - break; - case 24: - glyphFormat = QFontEngine::Format_A32; - break; - case 1: - glyphFormat = QFontEngine::Format_Mono; - break; - default: - glyphFormat = QFontEngine::Format_A8; - break; - } - } - - return glyphFormat; -} - -Glyph QXRenderGlyphCache::glyphId(glyph_t glyph, QFixed subPixelPosition) -{ - return qHash(QFontEngineFT::GlyphAndSubPixelPosition(glyph, QFixedPoint(subPixelPosition, 0))); -} - -bool QXRenderGlyphCache::isValidCoordinate(const QFixedPoint &fp) -{ - enum { t_min = SHRT_MIN, t_max = SHRT_MAX }; - return (fp.x < t_min || fp.x > t_max || fp.y < t_min || fp.y > t_max) ? false : true; -} -#endif // QT_CONFIG(xrender) - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h deleted file mode 100644 index bcbf84682c6..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11_p.h +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <QtGui/QPaintEngine> - -typedef unsigned long XID; -typedef XID Drawable; -typedef struct _XGC *GC; - -QT_BEGIN_NAMESPACE - -extern "C" { -Drawable qt_x11Handle(const QPaintDevice *pd); -GC qt_x11_get_pen_gc(QPainter *); -GC qt_x11_get_brush_gc(QPainter *); -} - -class QX11PaintEnginePrivate; -class QX11PaintEngine : public QPaintEngine -{ - Q_DECLARE_PRIVATE(QX11PaintEngine) -public: - QX11PaintEngine(); - ~QX11PaintEngine(); - - bool begin(QPaintDevice *pdev) override; - bool end() override; - - void updateState(const QPaintEngineState &state) override; - - void updatePen(const QPen &pen); - void updateBrush(const QBrush &brush, const QPointF &pt); - void updateRenderHints(QPainter::RenderHints hints); - void updateFont(const QFont &font); - void updateMatrix(const QTransform &matrix); - void updateClipRegion_dev(const QRegion ®ion, Qt::ClipOperation op); - - void drawLines(const QLine *lines, int lineCount) override; - void drawLines(const QLineF *lines, int lineCount) override; - - void drawRects(const QRect *rects, int rectCount) override; - void drawRects(const QRectF *rects, int rectCount) override; - - void drawPoints(const QPoint *points, int pointCount) override; - void drawPoints(const QPointF *points, int pointCount) override; - - void drawEllipse(const QRect &r) override; - void drawEllipse(const QRectF &r) override; - - virtual void drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) override; - inline void drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) override - { QPaintEngine::drawPolygon(points, pointCount, mode); } - - void drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) override; - void drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) override; - void drawPath(const QPainterPath &path) override; - void drawTextItem(const QPointF &p, const QTextItem &textItem) override; - void drawImage(const QRectF &r, const QImage &img, const QRectF &sr, - Qt::ImageConversionFlags flags = Qt::AutoColor) override; - - virtual Drawable handle() const; - inline Type type() const override { return QPaintEngine::X11; } - - QPainter::RenderHints supportedRenderHints() const; - -protected: - QX11PaintEngine(QX11PaintEnginePrivate &dptr); - -#if QT_CONFIG(fontconfig) - void drawFreetype(const QPointF &p, const QTextItemInt &ti); - bool drawCachedGlyphs(const QTransform &transform, const QTextItemInt &ti); -#endif // QT_CONFIG(fontconfig) - - friend class QPixmap; - friend class QFontEngineBox; - friend GC qt_x11_get_pen_gc(QPainter *); - friend GC qt_x11_get_brush_gc(QPainter *); - -private: - Q_DISABLE_COPY_MOVE(QX11PaintEngine) -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp deleted file mode 100644 index b47bd3f5dcc..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp +++ /dev/null @@ -1,2087 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include <QGuiApplication> - -#include <private/qdrawhelper_p.h> -#include <private/qimage_p.h> -#include <private/qimagepixmapcleanuphooks_p.h> - -#include "qxcbnativepainting.h" -#include "qpixmap_x11_p.h" -#include "qcolormap_x11_p.h" -#include "qpaintengine_x11_p.h" - -QT_BEGIN_NAMESPACE - -#if QT_POINTER_SIZE == 8 // 64-bit versions - -Q_ALWAYS_INLINE uint PREMUL(uint x) { - uint a = x >> 24; - quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a; - t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8; - t &= 0x000000ff00ff00ff; - return (uint(t)) | (uint(t >> 24)) | (a << 24); -} - -#else // 32-bit versions - -Q_ALWAYS_INLINE uint PREMUL(uint x) { - uint a = x >> 24; - uint t = (x & 0xff00ff) * a; - t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8; - t &= 0xff00ff; - - x = ((x >> 8) & 0xff) * a; - x = (x + ((x >> 8) & 0xff) + 0x80); - x &= 0xff00; - x |= t | (a << 24); - return x; -} -#endif - - - -struct QXImageWrapper -{ - XImage *xi; -}; - -QPixmap qt_toX11Pixmap(const QImage &image) -{ - QPlatformPixmap *data = - new QX11PlatformPixmap(image.depth() == 1 - ? QPlatformPixmap::BitmapType - : QPlatformPixmap::PixmapType); - - data->fromImage(image, Qt::AutoColor); - - return QPixmap(data); -} - -QPixmap qt_toX11Pixmap(const QPixmap &pixmap) -{ - if (pixmap.isNull()) - return QPixmap(); - - if (QPixmap(pixmap).data_ptr()->classId() == QPlatformPixmap::X11Class) - return pixmap; - - return qt_toX11Pixmap(pixmap.toImage()); -} - -// For thread-safety: -// image->data does not belong to X11, so we must free it ourselves. - -inline static void qSafeXDestroyImage(XImage *x) -{ - if (x->data) { - free(x->data); - x->data = 0; - } - XDestroyImage(x); -} - -QBitmap QX11PlatformPixmap::mask_to_bitmap(int screen) const -{ - if (!x11_mask) - return QBitmap(); - qt_x11SetDefaultScreen(screen); - QBitmap bm(w, h); - QX11PlatformPixmap *that = qt_x11Pixmap(bm); - const QXcbX11Info *x = that->x11_info(); - GC gc = XCreateGC(x->display(), that->handle(), 0, 0); - XCopyArea(x->display(), x11_mask, that->handle(), gc, 0, 0, - that->width(), that->height(), 0, 0); - XFreeGC(x->display(), gc); - return bm; -} - -void QX11PlatformPixmap::bitmapFromImage(const QImage &image) -{ - w = image.width(); - h = image.height(); - d = 1; - is_null = (w <= 0 || h <= 0); - hd = createBitmapFromImage(image); -#if QT_CONFIG(xrender) - if (X11->use_xrender) - picture = XRenderCreatePicture(xinfo.display(), hd, - XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); -#endif // QT_CONFIG(xrender) -} - -bool QX11PlatformPixmap::canTakeQImageFromXImage(const QXImageWrapper &xiWrapper) const -{ - XImage *xi = xiWrapper.xi; - - if (xi->format != ZPixmap) - return false; - - // ARGB32_Premultiplied - if (picture && depth() == 32) - return true; - - // RGB32 - if (depth() == 24 && xi->bits_per_pixel == 32 && xi->red_mask == 0xff0000 - && xi->green_mask == 0xff00 && xi->blue_mask == 0xff) - return true; - - // RGB16 - if (depth() == 16 && xi->bits_per_pixel == 16 && xi->red_mask == 0xf800 - && xi->green_mask == 0x7e0 && xi->blue_mask == 0x1f) - return true; - - return false; -} - -QImage QX11PlatformPixmap::takeQImageFromXImage(const QXImageWrapper &xiWrapper) const -{ - XImage *xi = xiWrapper.xi; - - QImage::Format format = QImage::Format_ARGB32_Premultiplied; - if (depth() == 24) - format = QImage::Format_RGB32; - else if (depth() == 16) - format = QImage::Format_RGB16; - - QImage image((uchar *)xi->data, xi->width, xi->height, xi->bytes_per_line, format); - image.setDevicePixelRatio(devicePixelRatio()); - // take ownership - image.data_ptr()->own_data = true; - xi->data = 0; - - // we may have to swap the byte order - if ((QSysInfo::ByteOrder == QSysInfo::LittleEndian && xi->byte_order == MSBFirst) - || (QSysInfo::ByteOrder == QSysInfo::BigEndian && xi->byte_order == LSBFirst)) - { - for (int i=0; i < image.height(); i++) { - if (depth() == 16) { - ushort *p = (ushort*)image.scanLine(i); - ushort *end = p + image.width(); - while (p < end) { - *p = ((*p << 8) & 0xff00) | ((*p >> 8) & 0x00ff); - p++; - } - } else { - uint *p = (uint*)image.scanLine(i); - uint *end = p + image.width(); - while (p < end) { - *p = ((*p << 24) & 0xff000000) | ((*p << 8) & 0x00ff0000) - | ((*p >> 8) & 0x0000ff00) | ((*p >> 24) & 0x000000ff); - p++; - } - } - } - } - - // fix-up alpha channel - if (format == QImage::Format_RGB32) { - QRgb *p = (QRgb *)image.bits(); - for (int y = 0; y < xi->height; ++y) { - for (int x = 0; x < xi->width; ++x) - p[x] |= 0xff000000; - p += xi->bytes_per_line / 4; - } - } - - XDestroyImage(xi); - return image; -} - -XID QX11PlatformPixmap::bitmap_to_mask(const QBitmap &bitmap, int screen) -{ - if (bitmap.isNull()) - return 0; - QBitmap bm = bitmap; - qt_x11SetScreen(bm, screen); - - QX11PlatformPixmap *that = qt_x11Pixmap(bm); - const QXcbX11Info *x = that->x11_info(); - Pixmap mask = XCreatePixmap(x->display(), RootWindow(x->display(), screen), - that->width(), that->height(), 1); - GC gc = XCreateGC(x->display(), mask, 0, 0); - XCopyArea(x->display(), that->handle(), mask, gc, 0, 0, - that->width(), that->height(), 0, 0); - XFreeGC(x->display(), gc); - return mask; -} - -Drawable qt_x11Handle(const QPixmap &pixmap) -{ - if (pixmap.isNull()) - return XNone; - - if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) - return XNone; - - return static_cast<const QX11PlatformPixmap *>(pixmap.handle())->handle(); -} - - -/***************************************************************************** - Internal functions - *****************************************************************************/ - -//extern const uchar *qt_get_bitflip_array(); // defined in qimage.cpp - -// Returns position of highest bit set or -1 if none -static int highest_bit(uint v) -{ - int i; - uint b = (uint)1 << 31; - for (i=31; ((b & v) == 0) && i>=0; i--) - b >>= 1; - return i; -} - -// Counts the number of bits set in 'v' -static uint n_bits(uint v) -{ - int i = 0; - while (v) { - v = v & (v - 1); - i++; - } - return i; -} - -static uint *red_scale_table = nullptr; -static uint *green_scale_table = nullptr; -static uint *blue_scale_table = nullptr; - -static void cleanup_scale_tables() -{ - delete[] red_scale_table; - delete[] green_scale_table; - delete[] blue_scale_table; -} - -/* - Could do smart bitshifting, but the "obvious" algorithm only works for - nBits >= 4. This is more robust. -*/ -static void build_scale_table(uint **table, uint nBits) -{ - if (nBits > 7) { - qWarning("build_scale_table: internal error, nBits = %i", nBits); - return; - } - if (!*table) { - static bool firstTable = true; - if (firstTable) { - qAddPostRoutine(cleanup_scale_tables); - firstTable = false; - } - *table = new uint[256]; - } - int maxVal = (1 << nBits) - 1; - int valShift = 8 - nBits; - int i; - for (i = 0 ; i < maxVal + 1 ; i++) - (*table)[i << valShift] = i*255/maxVal; -} - -static int defaultScreen = -1; - -int qt_x11SetDefaultScreen(int screen) -{ - int old = defaultScreen; - defaultScreen = screen; - return old; -} - -void qt_x11SetScreen(QPixmap &pixmap, int screen) -{ - if (pixmap.paintingActive()) { - qWarning("qt_x11SetScreen(): Cannot change screens during painting"); - return; - } - - if (pixmap.isNull()) - return; - - if (pixmap.handle()->classId() != QPlatformPixmap::X11Class) - return; - - if (screen < 0) - screen = QXcbX11Info::appScreen(); - - QX11PlatformPixmap *pm = static_cast<QX11PlatformPixmap *>(pixmap.handle()); - if (screen == pm->xinfo.screen()) - return; // nothing to do - - if (pixmap.isNull()) { - pm->xinfo = QXcbX11Info::fromScreen(screen); - return; - } - -#if 0 - qDebug("qt_x11SetScreen for %p from %d to %d. Size is %d/%d", pm, pm->xinfo.screen(), screen, pm->width(), pm->height()); -#endif - - qt_x11SetDefaultScreen(screen); - pixmap = qt_toX11Pixmap(pixmap.toImage()); -} - -/***************************************************************************** - QPixmap member functions - *****************************************************************************/ - -QBasicAtomicInt qt_pixmap_serial = Q_BASIC_ATOMIC_INITIALIZER(0); -int Q_GUI_EXPORT qt_x11_preferred_pixmap_depth = 0; - -QX11PlatformPixmap::QX11PlatformPixmap(PixelType pixelType) - : QPlatformPixmap(pixelType, X11Class), hd(0), - flags(Uninitialized), x11_mask(0), picture(0), mask_picture(0), hd2(0), - dpr(1.0), pengine(0) -{} - -QX11PlatformPixmap::~QX11PlatformPixmap() -{ - // Cleanup hooks have to be called before the handles are freed - if (is_cached) { - QImagePixmapCleanupHooks::executePlatformPixmapDestructionHooks(this); - is_cached = false; - } - - release(); -} - -QPlatformPixmap *QX11PlatformPixmap::createCompatiblePlatformPixmap() const -{ - QX11PlatformPixmap *p = new QX11PlatformPixmap(pixelType()); - p->setDevicePixelRatio(devicePixelRatio()); - return p; -} - -void QX11PlatformPixmap::resize(int width, int height) -{ - setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); - - w = width; - h = height; - is_null = (w <= 0 || h <= 0); - - if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { - xinfo = QXcbX11Info::fromScreen(defaultScreen); - } - - int dd = xinfo.depth(); - - if (qt_x11_preferred_pixmap_depth) - dd = qt_x11_preferred_pixmap_depth; - - bool make_null = w <= 0 || h <= 0; // create null pixmap - d = (pixelType() == BitmapType ? 1 : dd); - if (make_null || d == 0) { - w = 0; - h = 0; - is_null = true; - hd = 0; - picture = 0; - d = 0; - if (!make_null) - qWarning("QPixmap: Invalid pixmap parameters"); - return; - } - hd = XCreatePixmap(xinfo.display(), - RootWindow(xinfo.display(), xinfo.screen()), - w, h, d); -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - XRenderPictFormat *format = d == 1 - ? XRenderFindStandardFormat(xinfo.display(), PictStandardA1) - : XRenderFindVisualFormat(xinfo.display(), (Visual *) xinfo.visual()); - picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); - } -#endif // QT_CONFIG(xrender) -} - -struct QX11AlphaDetector -{ - bool hasAlpha() const { - if (checked) - return has; - // Will implicitly also check format and return quickly for opaque types... - checked = true; - has = image->isNull() ? false : const_cast<QImage *>(image)->data_ptr()->checkForAlphaPixels(); - return has; - } - - bool hasXRenderAndAlpha() const { - if (!X11->use_xrender) - return false; - return hasAlpha(); - } - - QX11AlphaDetector(const QImage *i, Qt::ImageConversionFlags flags) - : image(i), checked(false), has(false) - { - if (flags & Qt::NoOpaqueDetection) { - checked = true; - has = image->hasAlphaChannel(); - } - } - - const QImage *image; - mutable bool checked; - mutable bool has; -}; - -void QX11PlatformPixmap::fromImage(const QImage &img, Qt::ImageConversionFlags flags) -{ - setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); - - w = img.width(); - h = img.height(); - d = img.depth(); - is_null = (w <= 0 || h <= 0); - setDevicePixelRatio(img.devicePixelRatio()); - - if (is_null) { - w = h = 0; - return; - } - - if (defaultScreen >= 0 && defaultScreen != xinfo.screen()) { - xinfo = QXcbX11Info::fromScreen(defaultScreen); - } - - if (pixelType() == BitmapType) { - bitmapFromImage(img); - return; - } - - if (uint(w) >= 32768 || uint(h) >= 32768) { - w = h = 0; - is_null = true; - return; - } - - QX11AlphaDetector alphaCheck(&img, flags); - int dd = alphaCheck.hasXRenderAndAlpha() ? 32 : xinfo.depth(); - - if (qt_x11_preferred_pixmap_depth) - dd = qt_x11_preferred_pixmap_depth; - - QImage image = img; - - // must be monochrome - if (dd == 1 || (flags & Qt::ColorMode_Mask) == Qt::MonoOnly) { - if (d != 1) { - // dither - image = image.convertToFormat(QImage::Format_MonoLSB, flags); - d = 1; - } - } else { // can be both - bool conv8 = false; - if (d > 8 && dd <= 8) { // convert to 8 bit - if ((flags & Qt::DitherMode_Mask) == Qt::AutoDither) - flags = (flags & ~Qt::DitherMode_Mask) - | Qt::PreferDither; - conv8 = true; - } else if ((flags & Qt::ColorMode_Mask) == Qt::ColorOnly) { - conv8 = (d == 1); // native depth wanted - } else if (d == 1) { - if (image.colorCount() == 2) { - QRgb c0 = image.color(0); // Auto: convert to best - QRgb c1 = image.color(1); - conv8 = qMin(c0,c1) != qRgb(0,0,0) || qMax(c0,c1) != qRgb(255,255,255); - } else { - // eg. 1-color monochrome images (they do exist). - conv8 = true; - } - } - if (conv8) { - image = image.convertToFormat(QImage::Format_Indexed8, flags); - d = 8; - } - } - - if (d == 1 || image.format() > QImage::Format_ARGB32_Premultiplied) { - QImage::Format fmt = QImage::Format_RGB32; - if (alphaCheck.hasXRenderAndAlpha() && d > 1) - fmt = QImage::Format_ARGB32_Premultiplied; - image = image.convertToFormat(fmt, flags); - fromImage(image, Qt::AutoColor); - return; - } - - Display *dpy = xinfo.display(); - Visual *visual = (Visual *)xinfo.visual(); - XImage *xi = nullptr; - bool trucol = (visual->c_class >= TrueColor); - size_t nbytes = image.sizeInBytes(); - uchar *newbits= nullptr; - -#if QT_CONFIG(xrender) - if (alphaCheck.hasXRenderAndAlpha()) { - const QImage &cimage = image; - - d = 32; - - if (QXcbX11Info::appDepth() != d) { - xinfo.setDepth(d); - } - - hd = XCreatePixmap(dpy, RootWindow(dpy, xinfo.screen()), w, h, d); - picture = XRenderCreatePicture(dpy, hd, - XRenderFindStandardFormat(dpy, PictStandardARGB32), 0, 0); - - xi = XCreateImage(dpy, visual, d, ZPixmap, 0, 0, w, h, 32, 0); - Q_CHECK_PTR(xi); - newbits = (uchar *)malloc(xi->bytes_per_line*h); - Q_CHECK_PTR(newbits); - xi->data = (char *)newbits; - - switch (cimage.format()) { - case QImage::Format_Indexed8: { - QList<QRgb> colorTable = cimage.colorTable(); - uint *xidata = (uint *)xi->data; - for (int y = 0; y < h; ++y) { - const uchar *p = cimage.scanLine(y); - for (int x = 0; x < w; ++x) { - const QRgb rgb = colorTable[p[x]]; - const int a = qAlpha(rgb); - if (a == 0xff) - *xidata = rgb; - else - // RENDER expects premultiplied alpha - *xidata = qRgba(qt_div_255(qRed(rgb) * a), - qt_div_255(qGreen(rgb) * a), - qt_div_255(qBlue(rgb) * a), - a); - ++xidata; - } - } - } - break; - case QImage::Format_RGB32: { - uint *xidata = (uint *)xi->data; - for (int y = 0; y < h; ++y) { - const QRgb *p = (const QRgb *) cimage.scanLine(y); - for (int x = 0; x < w; ++x) - *xidata++ = p[x] | 0xff000000; - } - } - break; - case QImage::Format_ARGB32: { - uint *xidata = (uint *)xi->data; - for (int y = 0; y < h; ++y) { - const QRgb *p = (const QRgb *) cimage.scanLine(y); - for (int x = 0; x < w; ++x) { - const QRgb rgb = p[x]; - const int a = qAlpha(rgb); - if (a == 0xff) - *xidata = rgb; - else - // RENDER expects premultiplied alpha - *xidata = qRgba(qt_div_255(qRed(rgb) * a), - qt_div_255(qGreen(rgb) * a), - qt_div_255(qBlue(rgb) * a), - a); - ++xidata; - } - } - - } - break; - case QImage::Format_ARGB32_Premultiplied: { - uint *xidata = (uint *)xi->data; - for (int y = 0; y < h; ++y) { - const QRgb *p = (const QRgb *) cimage.scanLine(y); - memcpy(xidata, p, w*sizeof(QRgb)); - xidata += w; - } - } - break; - default: - Q_ASSERT(false); - } - - if ((xi->byte_order == MSBFirst) != (QSysInfo::ByteOrder == QSysInfo::BigEndian)) { - uint *xidata = (uint *)xi->data; - uint *xiend = xidata + w*h; - while (xidata < xiend) { - *xidata = (*xidata >> 24) - | ((*xidata >> 8) & 0xff00) - | ((*xidata << 8) & 0xff0000) - | (*xidata << 24); - ++xidata; - } - } - - GC gc = XCreateGC(dpy, hd, 0, 0); - XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); - XFreeGC(dpy, gc); - - qSafeXDestroyImage(xi); - - return; - } -#endif // QT_CONFIG(xrender) - - if (trucol) { // truecolor display - if (image.format() == QImage::Format_ARGB32_Premultiplied) - image = image.convertToFormat(QImage::Format_ARGB32); - - const QImage &cimage = image; - QRgb pix[256]; // pixel translation table - const bool d8 = (d == 8); - const uint red_mask = (uint)visual->red_mask; - const uint green_mask = (uint)visual->green_mask; - const uint blue_mask = (uint)visual->blue_mask; - const int red_shift = highest_bit(red_mask) - 7; - const int green_shift = highest_bit(green_mask) - 7; - const int blue_shift = highest_bit(blue_mask) - 7; - const uint rbits = highest_bit(red_mask) - lowest_bit(red_mask) + 1; - const uint gbits = highest_bit(green_mask) - lowest_bit(green_mask) + 1; - const uint bbits = highest_bit(blue_mask) - lowest_bit(blue_mask) + 1; - - if (d8) { // setup pixel translation - QList<QRgb> ctable = cimage.colorTable(); - for (int i=0; i < cimage.colorCount(); i++) { - int r = qRed (ctable[i]); - int g = qGreen(ctable[i]); - int b = qBlue (ctable[i]); - r = red_shift > 0 ? r << red_shift : r >> -red_shift; - g = green_shift > 0 ? g << green_shift : g >> -green_shift; - b = blue_shift > 0 ? b << blue_shift : b >> -blue_shift; - pix[i] = (b & blue_mask) | (g & green_mask) | (r & red_mask) - | ~(blue_mask | green_mask | red_mask); - } - } - - xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); - Q_CHECK_PTR(xi); - newbits = (uchar *)malloc(xi->bytes_per_line*h); - Q_CHECK_PTR(newbits); - if (!newbits) // no memory - return; - int bppc = xi->bits_per_pixel; - - bool contig_bits = n_bits(red_mask) == rbits && - n_bits(green_mask) == gbits && - n_bits(blue_mask) == bbits; - bool dither_tc = - // Want it? - (flags & Qt::Dither_Mask) != Qt::ThresholdDither && - (flags & Qt::DitherMode_Mask) != Qt::AvoidDither && - // Need it? - bppc < 24 && !d8 && - // Can do it? (Contiguous bits?) - contig_bits; - - static bool init=false; - static int D[16][16]; - if (dither_tc && !init) { - // I also contributed this code to XV - WWA. - /* - The dither matrix, D, is obtained with this formula: - - D2 = [0 2] - [3 1] - - - D2*n = [4*Dn 4*Dn+2*Un] - [4*Dn+3*Un 4*Dn+1*Un] - */ - int n,i,j; - init=1; - - /* Set D2 */ - D[0][0]=0; - D[1][0]=2; - D[0][1]=3; - D[1][1]=1; - - /* Expand using recursive definition given above */ - for (n=2; n<16; n*=2) { - for (i=0; i<n; i++) { - for (j=0; j<n; j++) { - D[i][j]*=4; - D[i+n][j]=D[i][j]+2; - D[i][j+n]=D[i][j]+3; - D[i+n][j+n]=D[i][j]+1; - } - } - } - init=true; - } - - enum { BPP8, - BPP16_565, BPP16_555, - BPP16_MSB, BPP16_LSB, - BPP24_888, - BPP24_MSB, BPP24_LSB, - BPP32_8888, - BPP32_MSB, BPP32_LSB - } mode = BPP8; - - bool same_msb_lsb = (xi->byte_order == MSBFirst) == (QSysInfo::ByteOrder == QSysInfo::BigEndian); - - if (bppc == 8) // 8 bit - mode = BPP8; - else if (bppc == 16) { // 16 bit MSB/LSB - if (red_shift == 8 && green_shift == 3 && blue_shift == -3 && !d8 && same_msb_lsb) - mode = BPP16_565; - else if (red_shift == 7 && green_shift == 2 && blue_shift == -3 && !d8 && same_msb_lsb) - mode = BPP16_555; - else - mode = (xi->byte_order == LSBFirst) ? BPP16_LSB : BPP16_MSB; - } else if (bppc == 24) { // 24 bit MSB/LSB - if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) - mode = BPP24_888; - else - mode = (xi->byte_order == LSBFirst) ? BPP24_LSB : BPP24_MSB; - } else if (bppc == 32) { // 32 bit MSB/LSB - if (red_shift == 16 && green_shift == 8 && blue_shift == 0 && !d8 && same_msb_lsb) - mode = BPP32_8888; - else - mode = (xi->byte_order == LSBFirst) ? BPP32_LSB : BPP32_MSB; - } else - qFatal("Logic error 3"); - -#define GET_PIXEL \ - uint pixel; \ - if (d8) pixel = pix[*src++]; \ - else { \ - int r = qRed (*p); \ - int g = qGreen(*p); \ - int b = qBlue (*p++); \ - r = red_shift > 0 \ - ? r << red_shift : r >> -red_shift; \ - g = green_shift > 0 \ - ? g << green_shift : g >> -green_shift; \ - b = blue_shift > 0 \ - ? b << blue_shift : b >> -blue_shift; \ - pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask) \ - | ~(blue_mask | green_mask | red_mask); \ - } - -#define GET_PIXEL_DITHER_TC \ - int r = qRed (*p); \ - int g = qGreen(*p); \ - int b = qBlue (*p++); \ - const int thres = D[x%16][y%16]; \ - if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \ - > thres) \ - r += (1<<(8-rbits)); \ - if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ - > thres) \ - g += (1<<(8-gbits)); \ - if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ - > thres) \ - b += (1<<(8-bbits)); \ - r = red_shift > 0 \ - ? r << red_shift : r >> -red_shift; \ - g = green_shift > 0 \ - ? g << green_shift : g >> -green_shift; \ - b = blue_shift > 0 \ - ? b << blue_shift : b >> -blue_shift; \ - uint pixel = (r & red_mask)|(g & green_mask) | (b & blue_mask); - -// again, optimized case -// can't be optimized that much :( -#define GET_PIXEL_DITHER_TC_OPT(red_shift,green_shift,blue_shift,red_mask,green_mask,blue_mask, \ - rbits,gbits,bbits) \ - const int thres = D[x%16][y%16]; \ - int r = qRed (*p); \ - if (r <= (255-(1<<(8-rbits))) && ((r<<rbits) & 255) \ - > thres) \ - r += (1<<(8-rbits)); \ - int g = qGreen(*p); \ - if (g <= (255-(1<<(8-gbits))) && ((g<<gbits) & 255) \ - > thres) \ - g += (1<<(8-gbits)); \ - int b = qBlue (*p++); \ - if (b <= (255-(1<<(8-bbits))) && ((b<<bbits) & 255) \ - > thres) \ - b += (1<<(8-bbits)); \ - uint pixel = ((r red_shift) & red_mask) \ - | ((g green_shift) & green_mask) \ - | ((b blue_shift) & blue_mask); - -#define CYCLE(body) \ - for (int y=0; y<h; y++) { \ - const uchar* src = cimage.scanLine(y); \ - uchar* dst = newbits + xi->bytes_per_line*y; \ - const QRgb* p = (const QRgb *)src; \ - body \ - } - - if (dither_tc) { - switch (mode) { - case BPP16_565: - CYCLE( - quint16* dst16 = (quint16*)dst; - for (int x=0; x<w; x++) { - GET_PIXEL_DITHER_TC_OPT(<<8,<<3,>>3,0xf800,0x7e0,0x1f,5,6,5) - *dst16++ = pixel; - } - ) - break; - case BPP16_555: - CYCLE( - quint16* dst16 = (quint16*)dst; - for (int x=0; x<w; x++) { - GET_PIXEL_DITHER_TC_OPT(<<7,<<2,>>3,0x7c00,0x3e0,0x1f,5,5,5) - *dst16++ = pixel; - } - ) - break; - case BPP16_MSB: // 16 bit MSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL_DITHER_TC - *dst++ = (pixel >> 8); - *dst++ = pixel; - } - ) - break; - case BPP16_LSB: // 16 bit LSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL_DITHER_TC - *dst++ = pixel; - *dst++ = pixel >> 8; - } - ) - break; - default: - qFatal("Logic error"); - } - } else { - switch (mode) { - case BPP8: // 8 bit - CYCLE( - Q_UNUSED(p); - for (int x=0; x<w; x++) - *dst++ = pix[*src++]; - ) - break; - case BPP16_565: - CYCLE( - quint16* dst16 = (quint16*)dst; - for (int x = 0; x < w; x++) { - *dst16++ = ((*p >> 8) & 0xf800) - | ((*p >> 5) & 0x7e0) - | ((*p >> 3) & 0x1f); - ++p; - } - ) - break; - case BPP16_555: - CYCLE( - quint16* dst16 = (quint16*)dst; - for (int x=0; x<w; x++) { - *dst16++ = ((*p >> 9) & 0x7c00) - | ((*p >> 6) & 0x3e0) - | ((*p >> 3) & 0x1f); - ++p; - } - ) - break; - case BPP16_MSB: // 16 bit MSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = (pixel >> 8); - *dst++ = pixel; - } - ) - break; - case BPP16_LSB: // 16 bit LSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = pixel; - *dst++ = pixel >> 8; - } - ) - break; - case BPP24_888: - CYCLE( - if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { - for (int x=0; x<w; x++) { - *dst++ = qRed (*p); - *dst++ = qGreen(*p); - *dst++ = qBlue (*p++); - } - } else { - for (int x=0; x<w; x++) { - *dst++ = qBlue (*p); - *dst++ = qGreen(*p); - *dst++ = qRed (*p++); - } - } - ) - break; - case BPP24_MSB: // 24 bit MSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = pixel >> 16; - *dst++ = pixel >> 8; - *dst++ = pixel; - } - ) - break; - case BPP24_LSB: // 24 bit LSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = pixel; - *dst++ = pixel >> 8; - *dst++ = pixel >> 16; - } - ) - break; - case BPP32_8888: - CYCLE( - memcpy(dst, p, w * 4); - ) - break; - case BPP32_MSB: // 32 bit MSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = pixel >> 24; - *dst++ = pixel >> 16; - *dst++ = pixel >> 8; - *dst++ = pixel; - } - ) - break; - case BPP32_LSB: // 32 bit LSB - CYCLE( - for (int x=0; x<w; x++) { - GET_PIXEL - *dst++ = pixel; - *dst++ = pixel >> 8; - *dst++ = pixel >> 16; - *dst++ = pixel >> 24; - } - ) - break; - default: - qFatal("Logic error 2"); - } - } - xi->data = (char *)newbits; - } - - if (d == 8 && !trucol) { // 8 bit pixmap - int pop[256]; // pixel popularity - - if (image.colorCount() == 0) - image.setColorCount(1); - - const QImage &cimage = image; - memset(pop, 0, sizeof(int)*256); // reset popularity array - for (int i = 0; i < h; i++) { // for each scanline... - const uchar* p = cimage.scanLine(i); - const uchar *end = p + w; - while (p < end) // compute popularity - pop[*p++]++; - } - - newbits = (uchar *)malloc(nbytes); // copy image into newbits - Q_CHECK_PTR(newbits); - if (!newbits) // no memory - return; - uchar* p = newbits; - memcpy(p, cimage.bits(), nbytes); // copy image data into newbits - - /* - * The code below picks the most important colors. It is based on the - * diversity algorithm, implemented in XV 3.10. XV is (C) by John Bradley. - */ - - struct PIX { // pixel sort element - uchar r,g,b,n; // color + pad - int use; // popularity - int index; // index in colormap - int mindist; - }; - int ncols = 0; - for (int i=0; i< cimage.colorCount(); i++) { // compute number of colors - if (pop[i] > 0) - ncols++; - } - for (int i = cimage.colorCount(); i < 256; i++) // ignore out-of-range pixels - pop[i] = 0; - - // works since we make sure above to have at least - // one color in the image - if (ncols == 0) - ncols = 1; - - PIX pixarr[256]; // pixel array - PIX pixarr_sorted[256]; // pixel array (sorted) - memset(pixarr, 0, ncols*sizeof(PIX)); - PIX *px = &pixarr[0]; - int maxpop = 0; - int maxpix = 0; - uint j = 0; - QList<QRgb> ctable = cimage.colorTable(); - for (int i = 0; i < 256; i++) { // init pixel array - if (pop[i] > 0) { - px->r = qRed (ctable[i]); - px->g = qGreen(ctable[i]); - px->b = qBlue (ctable[i]); - px->n = 0; - px->use = pop[i]; - if (pop[i] > maxpop) { // select most popular entry - maxpop = pop[i]; - maxpix = j; - } - px->index = i; - px->mindist = 1000000; - px++; - j++; - } - } - pixarr_sorted[0] = pixarr[maxpix]; - pixarr[maxpix].use = 0; - - for (int i = 1; i < ncols; i++) { // sort pixels - int minpix = -1, mindist = -1; - px = &pixarr_sorted[i-1]; - int r = px->r; - int g = px->g; - int b = px->b; - int dist; - if ((i & 1) || i<10) { // sort on max distance - for (int j=0; j<ncols; j++) { - px = &pixarr[j]; - if (px->use) { - dist = (px->r - r)*(px->r - r) + - (px->g - g)*(px->g - g) + - (px->b - b)*(px->b - b); - if (px->mindist > dist) - px->mindist = dist; - if (px->mindist > mindist) { - mindist = px->mindist; - minpix = j; - } - } - } - } else { // sort on max popularity - for (int j=0; j<ncols; j++) { - px = &pixarr[j]; - if (px->use) { - dist = (px->r - r)*(px->r - r) + - (px->g - g)*(px->g - g) + - (px->b - b)*(px->b - b); - if (px->mindist > dist) - px->mindist = dist; - if (px->use > mindist) { - mindist = px->use; - minpix = j; - } - } - } - } - pixarr_sorted[i] = pixarr[minpix]; - pixarr[minpix].use = 0; - } - - QXcbColormap cmap = QXcbColormap::instance(xinfo.screen()); - uint pix[256]; // pixel translation table - px = &pixarr_sorted[0]; - for (int i = 0; i < ncols; i++) { // allocate colors - QColor c(px->r, px->g, px->b); - pix[px->index] = cmap.pixel(c); - px++; - } - - p = newbits; - for (size_t i = 0; i < nbytes; i++) { // translate pixels - *p = pix[*p]; - p++; - } - } - - if (!xi) { // X image not created - xi = XCreateImage(dpy, visual, dd, ZPixmap, 0, 0, w, h, 32, 0); - if (xi->bits_per_pixel == 16) { // convert 8 bpp ==> 16 bpp - ushort *p2; - int p2inc = xi->bytes_per_line/sizeof(ushort); - ushort *newerbits = (ushort *)malloc(xi->bytes_per_line * h); - Q_CHECK_PTR(newerbits); - if (!newerbits) // no memory - return; - uchar* p = newbits; - for (int y = 0; y < h; y++) { // OOPS: Do right byte order!! - p2 = newerbits + p2inc*y; - for (int x = 0; x < w; x++) - *p2++ = *p++; - } - free(newbits); - newbits = (uchar *)newerbits; - } else if (xi->bits_per_pixel != 8) { - qWarning("QPixmap::fromImage: Display not supported " - "(bpp=%d)", xi->bits_per_pixel); - } - xi->data = (char *)newbits; - } - - hd = XCreatePixmap(dpy, - RootWindow(dpy, xinfo.screen()), - w, h, dd); - - GC gc = XCreateGC(dpy, hd, 0, 0); - XPutImage(dpy, hd, gc, xi, 0, 0, 0, 0, w, h); - XFreeGC(dpy, gc); - - qSafeXDestroyImage(xi); - d = dd; - -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - XRenderPictFormat *format = d == 1 - ? XRenderFindStandardFormat(dpy, PictStandardA1) - : XRenderFindVisualFormat(dpy, (Visual *)xinfo.visual()); - picture = XRenderCreatePicture(dpy, hd, format, 0, 0); - } -#endif - - if (alphaCheck.hasAlpha()) { - QBitmap m = QBitmap::fromImage(image.createAlphaMask(flags)); - setMask(m); - } -} - -void QX11PlatformPixmap::copy(const QPlatformPixmap *data, const QRect &rect) -{ - if (data->pixelType() == BitmapType) { - fromImage(data->toImage().copy(rect), Qt::AutoColor); - return; - } - - const QX11PlatformPixmap *x11Data = static_cast<const QX11PlatformPixmap*>(data); - - setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); - - flags &= ~Uninitialized; - xinfo = x11Data->xinfo; - d = x11Data->d; - w = rect.width(); - h = rect.height(); - is_null = (w <= 0 || h <= 0); - hd = XCreatePixmap(xinfo.display(), - RootWindow(xinfo.display(), x11Data->xinfo.screen()), - w, h, d); -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - XRenderPictFormat *format = d == 32 - ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) - : XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); - picture = XRenderCreatePicture(xinfo.display(), hd, format, 0, 0); - } -#endif // QT_CONFIG(xrender) - if (x11Data->x11_mask) { - x11_mask = XCreatePixmap(xinfo.display(), hd, w, h, 1); -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, - XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); - XRenderPictureAttributes attrs; - attrs.alpha_map = x11Data->mask_picture; - XRenderChangePicture(xinfo.display(), x11Data->picture, CPAlphaMap, &attrs); - } -#endif - } - -#if QT_CONFIG(xrender) - if (x11Data->picture && x11Data->d == 32) { - XRenderComposite(xinfo.display(), PictOpSrc, - x11Data->picture, 0, picture, - rect.x(), rect.y(), 0, 0, 0, 0, w, h); - } else -#endif - { - GC gc = XCreateGC(xinfo.display(), hd, 0, 0); - XCopyArea(xinfo.display(), x11Data->hd, hd, gc, - rect.x(), rect.y(), w, h, 0, 0); - if (x11Data->x11_mask) { - GC monogc = XCreateGC(xinfo.display(), x11_mask, 0, 0); - XCopyArea(xinfo.display(), x11Data->x11_mask, x11_mask, monogc, - rect.x(), rect.y(), w, h, 0, 0); - XFreeGC(xinfo.display(), monogc); - } - XFreeGC(xinfo.display(), gc); - } -} - -bool QX11PlatformPixmap::scroll(int dx, int dy, const QRect &rect) -{ - GC gc = XCreateGC(xinfo.display(), hd, 0, 0); - XCopyArea(xinfo.display(), hd, hd, gc, - rect.left(), rect.top(), rect.width(), rect.height(), - rect.left() + dx, rect.top() + dy); - XFreeGC(xinfo.display(), gc); - return true; -} - -int QX11PlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const -{ - switch (metric) { - case QPaintDevice::PdmDevicePixelRatio: - return devicePixelRatio(); - break; - case QPaintDevice::PdmDevicePixelRatioScaled: - return devicePixelRatio() * QPaintDevice::devicePixelRatioFScale(); - break; - case QPaintDevice::PdmWidth: - return w; - case QPaintDevice::PdmHeight: - return h; - case QPaintDevice::PdmNumColors: - return 1 << d; - case QPaintDevice::PdmDepth: - return d; - case QPaintDevice::PdmWidthMM: { - const int screen = xinfo.screen(); - const int mm = DisplayWidthMM(xinfo.display(), screen) * w - / DisplayWidth(xinfo.display(), screen); - return mm; - } - case QPaintDevice::PdmHeightMM: { - const int screen = xinfo.screen(); - const int mm = (DisplayHeightMM(xinfo.display(), screen) * h) - / DisplayHeight(xinfo.display(), screen); - return mm; - } - case QPaintDevice::PdmDpiX: - case QPaintDevice::PdmPhysicalDpiX: - return QXcbX11Info::appDpiX(xinfo.screen()); - case QPaintDevice::PdmDpiY: - case QPaintDevice::PdmPhysicalDpiY: - return QXcbX11Info::appDpiY(xinfo.screen()); - default: - qWarning("QX11PlatformPixmap::metric(): Invalid metric"); - return 0; - } -} - -void QX11PlatformPixmap::fill(const QColor &fillColor) -{ - if (fillColor.alpha() != 255) { -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - if (!picture || d != 32) - convertToARGB32(/*preserveContents = */false); - - ::Picture src = X11->getSolidFill(xinfo.screen(), fillColor); - XRenderComposite(xinfo.display(), PictOpSrc, src, 0, picture, - 0, 0, width(), height(), - 0, 0, width(), height()); - } else -#endif - { - QImage im(width(), height(), QImage::Format_ARGB32_Premultiplied); - im.fill(PREMUL(fillColor.rgba())); - release(); - fromImage(im, Qt::AutoColor | Qt::OrderedAlphaDither); - } - return; - } - - GC gc = XCreateGC(xinfo.display(), hd, 0, 0); - if (depth() == 1) { - XSetForeground(xinfo.display(), gc, qGray(fillColor.rgb()) > 127 ? 0 : 1); - } else if (X11->use_xrender && d >= 24) { - XSetForeground(xinfo.display(), gc, fillColor.rgba()); - } else { - XSetForeground(xinfo.display(), gc, - QXcbColormap::instance(xinfo.screen()).pixel(fillColor)); - } - XFillRectangle(xinfo.display(), hd, gc, 0, 0, width(), height()); - XFreeGC(xinfo.display(), gc); -} - -QBitmap QX11PlatformPixmap::mask() const -{ - QBitmap mask; -#if QT_CONFIG(xrender) - if (picture && d == 32) { - // #### slow - there must be a better way.. - mask = QBitmap::fromImage(toImage().createAlphaMask()); - } else -#endif - if (d == 1) { - QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this); - mask = QBitmap::fromPixmap(QPixmap(that)); - } else { - mask = mask_to_bitmap(xinfo.screen()); - } - return mask; -} - -void QX11PlatformPixmap::setMask(const QBitmap &newmask) -{ - if (newmask.isNull()) { // clear mask -#if QT_CONFIG(xrender) - if (picture && d == 32) { - QX11PlatformPixmap newData(pixelType()); - newData.resize(w, h); - newData.fill(Qt::black); - XRenderComposite(xinfo.display(), PictOpOver, - picture, 0, newData.picture, - 0, 0, 0, 0, 0, 0, w, h); - release(); - *this = newData; - // the new QX11PlatformPixmap object isn't referenced yet, so - // ref it - ref.ref(); - - // the below is to make sure the QX11PlatformPixmap destructor - // doesn't delete our newly created render picture - newData.hd = 0; - newData.x11_mask = 0; - newData.picture = 0; - newData.mask_picture = 0; - newData.hd2 = 0; - } else -#endif - if (x11_mask) { -#if QT_CONFIG(xrender) - if (picture) { - XRenderPictureAttributes attrs; - attrs.alpha_map = 0; - XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, - &attrs); - } - if (mask_picture) - XRenderFreePicture(xinfo.display(), mask_picture); - mask_picture = 0; -#endif - XFreePixmap(xinfo.display(), x11_mask); - x11_mask = 0; - } - return; - } - -#if QT_CONFIG(xrender) - if (picture && d == 32) { - XRenderComposite(xinfo.display(), PictOpSrc, - picture, qt_x11Pixmap(newmask)->x11PictureHandle(), - picture, 0, 0, 0, 0, 0, 0, w, h); - } else -#endif - if (depth() == 1) { - XGCValues vals; - vals.function = GXand; - GC gc = XCreateGC(xinfo.display(), hd, GCFunction, &vals); - XCopyArea(xinfo.display(), qt_x11Pixmap(newmask)->handle(), hd, gc, 0, 0, - width(), height(), 0, 0); - XFreeGC(xinfo.display(), gc); - } else { - // ##### should or the masks together - if (x11_mask) { - XFreePixmap(xinfo.display(), x11_mask); -#if QT_CONFIG(xrender) - if (mask_picture) - XRenderFreePicture(xinfo.display(), mask_picture); -#endif - } - x11_mask = QX11PlatformPixmap::bitmap_to_mask(newmask, xinfo.screen()); -#if QT_CONFIG(xrender) - if (picture) { - mask_picture = XRenderCreatePicture(xinfo.display(), x11_mask, - XRenderFindStandardFormat(xinfo.display(), PictStandardA1), 0, 0); - XRenderPictureAttributes attrs; - attrs.alpha_map = mask_picture; - XRenderChangePicture(xinfo.display(), picture, CPAlphaMap, &attrs); - } -#endif - } -} - -bool QX11PlatformPixmap::hasAlphaChannel() const -{ - if (picture && d == 32) - return true; - - if (x11_mask && d == 1) - return true; - - return false; -} - -QPixmap QX11PlatformPixmap::transformed(const QTransform &transform, Qt::TransformationMode mode) const -{ - if (mode == Qt::SmoothTransformation || transform.type() >= QTransform::TxProject) { - QImage image = toImage(); - return QPixmap::fromImage(image.transformed(transform, mode)); - } - - uint w = 0; - uint h = 0; // size of target pixmap - uint ws, hs; // size of source pixmap - uchar *dptr; // data in target pixmap - uint dbpl, dbytes; // bytes per line/bytes total - uchar *sptr; // data in original pixmap - int sbpl; // bytes per line in original - int bpp; // bits per pixel - bool depth1 = depth() == 1; - Display *dpy = xinfo.display(); - - ws = width(); - hs = height(); - - QTransform mat(transform.m11(), transform.m12(), transform.m13(), - transform.m21(), transform.m22(), transform.m23(), - 0., 0., 1); - bool complex_xform = false; - - if (mat.type() <= QTransform::TxScale) { - h = qRound(qAbs(mat.m22()) * hs); - w = qRound(qAbs(mat.m11()) * ws); - } else { // rotation or shearing - QPolygonF a(QRectF(0, 0, ws, hs)); - a = mat.map(a); - QRect r = a.boundingRect().toAlignedRect(); - w = r.width(); - h = r.height(); - complex_xform = true; - } - mat = QPixmap::trueMatrix(mat, ws, hs); // true matrix - - bool invertible; - mat = mat.inverted(&invertible); // invert matrix - - if (h == 0 || w == 0 || !invertible - || qAbs(h) >= 32768 || qAbs(w) >= 32768 ) - // error, return null pixmap - return QPixmap(); - - XImage *xi = XGetImage(xinfo.display(), handle(), 0, 0, ws, hs, AllPlanes, - depth1 ? XYPixmap : ZPixmap); - - if (!xi) - return QPixmap(); - - sbpl = xi->bytes_per_line; - sptr = (uchar *)xi->data; - bpp = xi->bits_per_pixel; - - if (depth1) - dbpl = (w+7)/8; - else - dbpl = ((w*bpp+31)/32)*4; - dbytes = dbpl*h; - - dptr = (uchar *)malloc(dbytes); // create buffer for bits - Q_CHECK_PTR(dptr); - if (depth1) // fill with zeros - memset(dptr, 0, dbytes); - else if (bpp == 8) // fill with background color - memset(dptr, WhitePixel(xinfo.display(), xinfo.screen()), dbytes); - else - memset(dptr, 0, dbytes); - - // #define QT_DEBUG_XIMAGE -#if defined(QT_DEBUG_XIMAGE) - qDebug("----IMAGE--INFO--------------"); - qDebug("width............. %d", xi->width); - qDebug("height............ %d", xi->height); - qDebug("xoffset........... %d", xi->xoffset); - qDebug("format............ %d", xi->format); - qDebug("byte order........ %d", xi->byte_order); - qDebug("bitmap unit....... %d", xi->bitmap_unit); - qDebug("bitmap bit order.. %d", xi->bitmap_bit_order); - qDebug("depth............. %d", xi->depth); - qDebug("bytes per line.... %d", xi->bytes_per_line); - qDebug("bits per pixel.... %d", xi->bits_per_pixel); -#endif - - int type; - if (xi->bitmap_bit_order == MSBFirst) - type = QT_XFORM_TYPE_MSBFIRST; - else - type = QT_XFORM_TYPE_LSBFIRST; - int xbpl, p_inc; - if (depth1) { - xbpl = (w+7)/8; - p_inc = dbpl - xbpl; - } else { - xbpl = (w*bpp)/8; - p_inc = dbpl - xbpl; - } - - if (!qt_xForm_helper(mat, xi->xoffset, type, bpp, dptr, xbpl, p_inc, h, sptr, sbpl, ws, hs)){ - qWarning("QPixmap::transform: display not supported (bpp=%d)",bpp); - QPixmap pm; - free(dptr); - return pm; - } - - qSafeXDestroyImage(xi); - - if (depth1) { // mono bitmap - QBitmap bm = QBitmap::fromData(QSize(w, h), dptr, - BitmapBitOrder(xinfo.display()) == MSBFirst - ? QImage::Format_Mono - : QImage::Format_MonoLSB); - free(dptr); - return bm; - } else { // color pixmap - QX11PlatformPixmap *x11Data = new QX11PlatformPixmap(QPlatformPixmap::PixmapType); - QPixmap pm(x11Data); - x11Data->flags &= ~QX11PlatformPixmap::Uninitialized; - x11Data->xinfo = xinfo; - x11Data->d = d; - x11Data->w = w; - x11Data->h = h; - x11Data->is_null = (w <= 0 || h <= 0); - x11Data->hd = XCreatePixmap(xinfo.display(), - RootWindow(xinfo.display(), xinfo.screen()), - w, h, d); - x11Data->setSerialNumber(qt_pixmap_serial.fetchAndAddRelaxed(1)); - -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - XRenderPictFormat *format = x11Data->d == 32 - ? XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32) - : XRenderFindVisualFormat(xinfo.display(), (Visual *) x11Data->xinfo.visual()); - x11Data->picture = XRenderCreatePicture(xinfo.display(), x11Data->hd, format, 0, 0); - } -#endif // QT_CONFIG(xrender) - - GC gc = XCreateGC(xinfo.display(), x11Data->hd, 0, 0); - xi = XCreateImage(dpy, (Visual*)x11Data->xinfo.visual(), - x11Data->d, - ZPixmap, 0, (char *)dptr, w, h, 32, 0); - XPutImage(dpy, qt_x11Pixmap(pm)->handle(), gc, xi, 0, 0, 0, 0, w, h); - qSafeXDestroyImage(xi); - XFreeGC(xinfo.display(), gc); - - if (x11_mask) { // xform mask, too - pm.setMask(mask_to_bitmap(xinfo.screen()).transformed(transform)); - } else if (d != 32 && complex_xform) { // need a mask! - QBitmap mask(ws, hs); - mask.fill(Qt::color1); - pm.setMask(mask.transformed(transform)); - } - return pm; - } -} - -QImage QX11PlatformPixmap::toImage() const -{ - return toImage(QRect(0, 0, w, h)); -} - -QImage QX11PlatformPixmap::toImage(const QRect &rect) const -{ - Window root_return; - int x_return; - int y_return; - unsigned int width_return; - unsigned int height_return; - unsigned int border_width_return; - unsigned int depth_return; - - XGetGeometry(xinfo.display(), hd, &root_return, &x_return, &y_return, &width_return, &height_return, &border_width_return, &depth_return); - - QXImageWrapper xiWrapper; - xiWrapper.xi = XGetImage(xinfo.display(), hd, rect.x(), rect.y(), rect.width(), rect.height(), - AllPlanes, (depth() == 1) ? XYPixmap : ZPixmap); - - Q_CHECK_PTR(xiWrapper.xi); - if (!xiWrapper.xi) - return QImage(); - - if (!x11_mask && canTakeQImageFromXImage(xiWrapper)) - return takeQImageFromXImage(xiWrapper); - - QImage image = toImage(xiWrapper, rect); - qSafeXDestroyImage(xiWrapper.xi); - return image; -} - -#if QT_CONFIG(xrender) -static XRenderPictFormat *qt_renderformat_for_depth(const QXcbX11Info &xinfo, int depth) -{ - if (depth == 1) - return XRenderFindStandardFormat(xinfo.display(), PictStandardA1); - else if (depth == 32) - return XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32); - else - return XRenderFindVisualFormat(xinfo.display(), (Visual *)xinfo.visual()); -} -#endif - -Q_GLOBAL_STATIC(QX11PaintEngine, qt_x11_paintengine) - -QPaintEngine *QX11PlatformPixmap::paintEngine() const -{ - QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this); - - if ((flags & Readonly)/* && share_mode == QPixmap::ImplicitlyShared*/) { - // if someone wants to draw onto us, copy the shared contents - // and turn it into a fully fledged QPixmap - ::Pixmap hd_copy = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), - w, h, d); -#if QT_CONFIG(xrender) - if (picture && d == 32) { - XRenderPictFormat *format = qt_renderformat_for_depth(xinfo, d); - ::Picture picture_copy = XRenderCreatePicture(xinfo.display(), - hd_copy, format, - 0, 0); - - XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, picture_copy, - 0, 0, 0, 0, 0, 0, w, h); - XRenderFreePicture(xinfo.display(), picture); - that->picture = picture_copy; - } else -#endif - { - GC gc = XCreateGC(xinfo.display(), hd_copy, 0, 0); - XCopyArea(xinfo.display(), hd, hd_copy, gc, 0, 0, w, h, 0, 0); - XFreeGC(xinfo.display(), gc); - } - that->hd = hd_copy; - that->flags &= ~QX11PlatformPixmap::Readonly; - } - - if (qt_x11_paintengine->isActive()) { - if (!that->pengine) - that->pengine = new QX11PaintEngine; - - return that->pengine; - } - - return qt_x11_paintengine(); -} - -qreal QX11PlatformPixmap::devicePixelRatio() const -{ - return dpr; -} - -void QX11PlatformPixmap::setDevicePixelRatio(qreal scaleFactor) -{ - dpr = scaleFactor; -} - -Pixmap QX11PlatformPixmap::x11ConvertToDefaultDepth() -{ -#if QT_CONFIG(xrender) - if (d == xinfo.appDepth() || !X11->use_xrender) - return hd; - if (!hd2) { - hd2 = XCreatePixmap(xinfo.display(), hd, w, h, xinfo.appDepth()); - XRenderPictFormat *format = XRenderFindVisualFormat(xinfo.display(), - (Visual*) xinfo.visual()); - Picture pic = XRenderCreatePicture(xinfo.display(), hd2, format, 0, 0); - XRenderComposite(xinfo.display(), PictOpSrc, picture, - XNone, pic, 0, 0, 0, 0, 0, 0, w, h); - XRenderFreePicture(xinfo.display(), pic); - } - return hd2; -#else - return hd; -#endif -} - -XID QX11PlatformPixmap::createBitmapFromImage(const QImage &image) -{ - QImage img = image.convertToFormat(QImage::Format_MonoLSB); - const QRgb c0 = QColor(Qt::black).rgb(); - const QRgb c1 = QColor(Qt::white).rgb(); - if (img.color(0) == c0 && img.color(1) == c1) { - img.invertPixels(); - img.setColor(0, c1); - img.setColor(1, c0); - } - - char *bits; - uchar *tmp_bits; - int w = img.width(); - int h = img.height(); - int bpl = (w + 7) / 8; - qsizetype ibpl = img.bytesPerLine(); - if (bpl != ibpl) { - tmp_bits = new uchar[bpl*h]; - bits = (char *)tmp_bits; - uchar *p, *b; - int y; - b = tmp_bits; - p = img.scanLine(0); - for (y = 0; y < h; y++) { - memcpy(b, p, bpl); - b += bpl; - p += ibpl; - } - } else { - bits = (char *)img.bits(); - tmp_bits = 0; - } - XID hd = XCreateBitmapFromData(QXcbX11Info::display(), - QXcbX11Info::appRootWindow(), - bits, w, h); - if (tmp_bits) // Avoid purify complaint - delete [] tmp_bits; - return hd; -} - -bool QX11PlatformPixmap::isBackingStore() const -{ - return (flags & IsBackingStore); -} - -void QX11PlatformPixmap::setIsBackingStore(bool on) -{ - if (on) - flags |= IsBackingStore; - else { - flags &= ~IsBackingStore; - } -} - -#if QT_CONFIG(xrender) -void QX11PlatformPixmap::convertToARGB32(bool preserveContents) -{ - if (!X11->use_xrender) - return; - - // Q_ASSERT(count == 1); - if ((flags & Readonly)/* && share_mode == QPixmap::ExplicitlyShared*/) - return; - - Pixmap pm = XCreatePixmap(xinfo.display(), RootWindow(xinfo.display(), xinfo.screen()), - w, h, 32); - Picture p = XRenderCreatePicture(xinfo.display(), pm, - XRenderFindStandardFormat(xinfo.display(), PictStandardARGB32), 0, 0); - if (picture) { - if (preserveContents) - XRenderComposite(xinfo.display(), PictOpSrc, picture, 0, p, 0, 0, 0, 0, 0, 0, w, h); - if (!(flags & Readonly)) - XRenderFreePicture(xinfo.display(), picture); - } - if (hd && !(flags & Readonly)) - XFreePixmap(xinfo.display(), hd); - if (x11_mask) { - XFreePixmap(xinfo.display(), x11_mask); - if (mask_picture) - XRenderFreePicture(xinfo.display(), mask_picture); - x11_mask = 0; - mask_picture = 0; - } - hd = pm; - picture = p; - - d = 32; - xinfo.setDepth(32); - - XVisualInfo visinfo; - if (XMatchVisualInfo(xinfo.display(), xinfo.screen(), 32, TrueColor, &visinfo)) - xinfo.setVisual(visinfo.visual); -} -#endif - -void QX11PlatformPixmap::release() -{ - delete pengine; - pengine = 0; - - if (/*!X11*/ QCoreApplication::closingDown()) { - // At this point, the X server will already have freed our resources, - // so there is nothing to do. - return; - } - - if (x11_mask) { -#if QT_CONFIG(xrender) - if (mask_picture) - XRenderFreePicture(xinfo.display(), mask_picture); - mask_picture = 0; -#endif - XFreePixmap(xinfo.display(), x11_mask); - x11_mask = 0; - } - - if (hd) { -#if QT_CONFIG(xrender) - if (picture) { - XRenderFreePicture(xinfo.display(), picture); - picture = 0; - } -#endif // QT_CONFIG(xrender) - - if (hd2) { - XFreePixmap(xinfo.display(), hd2); - hd2 = 0; - } - if (!(flags & Readonly)) - XFreePixmap(xinfo.display(), hd); - hd = 0; - } -} - -QImage QX11PlatformPixmap::toImage(const QXImageWrapper &xiWrapper, const QRect &rect) const -{ - XImage *xi = xiWrapper.xi; - - int d = depth(); - Visual *visual = (Visual *)xinfo.visual(); - bool trucol = (visual->c_class >= TrueColor) && d > 1; - - QImage::Format format = QImage::Format_Mono; - if (d > 1 && d <= 8) { - d = 8; - format = QImage::Format_Indexed8; - } - // we could run into the situation where d == 8 AND trucol is true, which can - // cause problems when converting to and from images. in this case, always treat - // the depth as 32... - if (d > 8 || trucol) { - d = 32; - format = QImage::Format_RGB32; - } - - if (d == 1 && xi->bitmap_bit_order == LSBFirst) - format = QImage::Format_MonoLSB; - if (x11_mask && format == QImage::Format_RGB32) - format = QImage::Format_ARGB32; - - QImage image(xi->width, xi->height, format); - image.setDevicePixelRatio(devicePixelRatio()); - if (image.isNull()) // could not create image - return image; - - QImage alpha; - if (x11_mask) { - if (rect.contains(QRect(0, 0, w, h))) - alpha = mask().toImage(); - else - alpha = mask().toImage().copy(rect); - } - bool ale = alpha.format() == QImage::Format_MonoLSB; - - if (trucol) { // truecolor - const uint red_mask = (uint)visual->red_mask; - const uint green_mask = (uint)visual->green_mask; - const uint blue_mask = (uint)visual->blue_mask; - const int red_shift = highest_bit(red_mask) - 7; - const int green_shift = highest_bit(green_mask) - 7; - const int blue_shift = highest_bit(blue_mask) - 7; - - const uint red_bits = n_bits(red_mask); - const uint green_bits = n_bits(green_mask); - const uint blue_bits = n_bits(blue_mask); - - static uint red_table_bits = 0; - static uint green_table_bits = 0; - static uint blue_table_bits = 0; - - if (red_bits < 8 && red_table_bits != red_bits) { - build_scale_table(&red_scale_table, red_bits); - red_table_bits = red_bits; - } - if (blue_bits < 8 && blue_table_bits != blue_bits) { - build_scale_table(&blue_scale_table, blue_bits); - blue_table_bits = blue_bits; - } - if (green_bits < 8 && green_table_bits != green_bits) { - build_scale_table(&green_scale_table, green_bits); - green_table_bits = green_bits; - } - - int r, g, b; - - QRgb *dst; - uchar *src; - uint pixel; - int bppc = xi->bits_per_pixel; - - if (bppc > 8 && xi->byte_order == LSBFirst) - bppc++; - - for (int y = 0; y < xi->height; ++y) { - uchar* asrc = x11_mask ? alpha.scanLine(y) : 0; - dst = (QRgb *)image.scanLine(y); - src = (uchar *)xi->data + xi->bytes_per_line*y; - for (int x = 0; x < xi->width; x++) { - switch (bppc) { - case 8: - pixel = *src++; - break; - case 16: // 16 bit MSB - pixel = src[1] | (uint)src[0] << 8; - src += 2; - break; - case 17: // 16 bit LSB - pixel = src[0] | (uint)src[1] << 8; - src += 2; - break; - case 24: // 24 bit MSB - pixel = src[2] | (uint)src[1] << 8 | (uint)src[0] << 16; - src += 3; - break; - case 25: // 24 bit LSB - pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16; - src += 3; - break; - case 32: // 32 bit MSB - pixel = src[3] | (uint)src[2] << 8 | (uint)src[1] << 16 | (uint)src[0] << 24; - src += 4; - break; - case 33: // 32 bit LSB - pixel = src[0] | (uint)src[1] << 8 | (uint)src[2] << 16 | (uint)src[3] << 24; - src += 4; - break; - default: // should not really happen - x = xi->width; // leave loop - y = xi->height; - pixel = 0; // eliminate compiler warning - qWarning("QPixmap::convertToImage: Invalid depth %d", bppc); - } - if (red_shift > 0) - r = (pixel & red_mask) >> red_shift; - else - r = (pixel & red_mask) << -red_shift; - if (green_shift > 0) - g = (pixel & green_mask) >> green_shift; - else - g = (pixel & green_mask) << -green_shift; - if (blue_shift > 0) - b = (pixel & blue_mask) >> blue_shift; - else - b = (pixel & blue_mask) << -blue_shift; - - if (red_bits < 8) - r = red_scale_table[r]; - if (green_bits < 8) - g = green_scale_table[g]; - if (blue_bits < 8) - b = blue_scale_table[b]; - - if (x11_mask) { - if (ale) { - *dst++ = (asrc[x >> 3] & (1 << (x & 7))) ? qRgba(r, g, b, 0xff) : 0; - } else { - *dst++ = (asrc[x >> 3] & (0x80 >> (x & 7))) ? qRgba(r, g, b, 0xff) : 0; - } - } else { - *dst++ = qRgb(r, g, b); - } - } - } - } else if (xi->bits_per_pixel == d) { // compatible depth - char *xidata = xi->data; // copy each scanline - qsizetype bpl = qMin(image.bytesPerLine(),xi->bytes_per_line); - for (int y=0; y<xi->height; y++) { - memcpy(image.scanLine(y), xidata, bpl); - xidata += xi->bytes_per_line; - } - } else { - /* Typically 2 or 4 bits display depth */ - qWarning("QPixmap::convertToImage: Display not supported (bpp=%d)", - xi->bits_per_pixel); - return QImage(); - } - - if (d == 1) { // bitmap - image.setColorCount(2); - image.setColor(0, qRgb(255,255,255)); - image.setColor(1, qRgb(0,0,0)); - } else if (!trucol) { // pixmap with colormap - uchar *p; - uchar *end; - uchar use[256]; // pixel-in-use table - uchar pix[256]; // pixel translation table - int ncols; - memset(use, 0, 256); - memset(pix, 0, 256); - qsizetype bpl = image.bytesPerLine(); - - if (x11_mask) { // which pixels are used? - for (int i = 0; i < xi->height; i++) { - uchar* asrc = alpha.scanLine(i); - p = image.scanLine(i); - if (ale) { - for (int x = 0; x < xi->width; x++) { - if (asrc[x >> 3] & (1 << (x & 7))) - use[*p] = 1; - ++p; - } - } else { - for (int x = 0; x < xi->width; x++) { - if (asrc[x >> 3] & (0x80 >> (x & 7))) - use[*p] = 1; - ++p; - } - } - } - } else { - for (int i = 0; i < xi->height; i++) { - p = image.scanLine(i); - end = p + bpl; - while (p < end) - use[*p++] = 1; - } - } - ncols = 0; - for (int i = 0; i < 256; i++) { // build translation table - if (use[i]) - pix[i] = ncols++; - } - for (int i = 0; i < xi->height; i++) { // translate pixels - p = image.scanLine(i); - end = p + bpl; - while (p < end) { - *p = pix[*p]; - p++; - } - } - if (x11_mask) { - int trans; - if (ncols < 256) { - trans = ncols++; - image.setColorCount(ncols); // create color table - image.setColor(trans, 0x00000000); - } else { - image.setColorCount(ncols); // create color table - // oh dear... no spare "transparent" pixel. - // use first pixel in image (as good as any). - trans = image.scanLine(0)[0]; - } - for (int i = 0; i < xi->height; i++) { - uchar* asrc = alpha.scanLine(i); - p = image.scanLine(i); - if (ale) { - for (int x = 0; x < xi->width; x++) { - if (!(asrc[x >> 3] & (1 << (x & 7)))) - *p = trans; - ++p; - } - } else { - for (int x = 0; x < xi->width; x++) { - if (!(asrc[x >> 3] & (1 << (7 -(x & 7))))) - *p = trans; - ++p; - } - } - } - } else { - image.setColorCount(ncols); // create color table - } - QList<QColor> colors = QXcbColormap::instance(xinfo.screen()).colormap(); - int j = 0; - for (int i=0; i<colors.size(); i++) { // translate pixels - if (use[i]) - image.setColor(j++, 0xff000000 | colors.at(i).rgb()); - } - } - - return image; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h deleted file mode 100644 index 0755a34b4a8..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11_p.h +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <QBitmap> -#include <QPixmap> - -#include <qpa/qplatformpixmap.h> -#include "qxcbnativepainting.h" - -typedef unsigned long XID; -typedef XID Drawable; -typedef XID Picture; -typedef XID Pixmap; - -QT_BEGIN_NAMESPACE - -class QX11PaintEngine; -struct QXImageWrapper; - -class QX11PlatformPixmap : public QPlatformPixmap -{ -public: - QX11PlatformPixmap(PixelType pixelType); - ~QX11PlatformPixmap(); - - QPlatformPixmap *createCompatiblePlatformPixmap() const override; - void resize(int width, int height) override; - void fromImage(const QImage &img, Qt::ImageConversionFlags flags) override; - void copy(const QPlatformPixmap *data, const QRect &rect) override; - bool scroll(int dx, int dy, const QRect &rect) override; - int metric(QPaintDevice::PaintDeviceMetric metric) const override; - void fill(const QColor &fillColor) override; - QBitmap mask() const override; - void setMask(const QBitmap &mask) override; - bool hasAlphaChannel() const override; - QPixmap transformed(const QTransform &matrix, Qt::TransformationMode mode) const override; - QImage toImage() const override; - QImage toImage(const QRect &rect) const override; - QPaintEngine *paintEngine() const override; - qreal devicePixelRatio() const override; - void setDevicePixelRatio(qreal scaleFactor) override; - - inline Drawable handle() const { return hd; } - inline Picture x11PictureHandle() const { return picture; } - inline const QXcbX11Info *x11_info() const { return &xinfo; } - - Pixmap x11ConvertToDefaultDepth(); - static XID createBitmapFromImage(const QImage &image); - -#if QT_CONFIG(xrender) - void convertToARGB32(bool preserveContents = true); -#endif - - bool isBackingStore() const; - void setIsBackingStore(bool on); -private: - friend class QX11PaintEngine; - friend const QXcbX11Info &qt_x11Info(const QPixmap &pixmap); - friend void qt_x11SetScreen(QPixmap &pixmap, int screen); - - void release(); - QImage toImage(const QXImageWrapper &xi, const QRect &rect) const; - QBitmap mask_to_bitmap(int screen) const; - static Pixmap bitmap_to_mask(const QBitmap &, int screen); - void bitmapFromImage(const QImage &image); - bool canTakeQImageFromXImage(const QXImageWrapper &xi) const; - QImage takeQImageFromXImage(const QXImageWrapper &xi) const; - - Pixmap hd = 0; - - enum Flag { - NoFlags = 0x0, - Uninitialized = 0x1, - Readonly = 0x2, - InvertedWhenBoundToTexture = 0x4, - GlSurfaceCreatedWithAlpha = 0x8, - IsBackingStore = 0x10 - }; - uint flags; - - QXcbX11Info xinfo; - Pixmap x11_mask; - Picture picture; - Picture mask_picture; - Pixmap hd2; // sorted in the default display depth - //QPixmap::ShareMode share_mode; - qreal dpr; - - QX11PaintEngine *pengine; -}; - -inline QX11PlatformPixmap *qt_x11Pixmap(const QPixmap &pixmap) -{ - return (pixmap.handle() && pixmap.handle()->classId() == QPlatformPixmap::X11Class) - ? static_cast<QX11PlatformPixmap *>(pixmap.handle()) - : nullptr; -} - -inline Picture qt_x11PictureHandle(const QPixmap &pixmap) -{ - if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) - return pm->x11PictureHandle(); - - return 0; -} - -inline Pixmap qt_x11PixmapHandle(const QPixmap &pixmap) -{ - if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) - return pm->handle(); - - return 0; -} - -inline const QXcbX11Info &qt_x11Info(const QPixmap &pixmap) -{ - if (QX11PlatformPixmap *pm = qt_x11Pixmap(pixmap)) { - return pm->xinfo; - } else { - static QXcbX11Info nullX11Info; - return nullX11Info; - } -} - -int qt_x11SetDefaultScreen(int screen); -void qt_x11SetScreen(QPixmap &pixmap, int screen); - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h b/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h deleted file mode 100644 index e1e31722d76..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qpolygonclipper_p.h +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright (C) 2016 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists for the convenience -// of other Qt classes. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtCore/qrect.h> -#include <QtGui/private/qdatabuffer_p.h> - -QT_BEGIN_NAMESPACE - -/* based on sutherland-hodgman line-by-line clipping, as described in - Computer Graphics and Principles */ -template <typename InType, typename OutType, typename CastType> class QPolygonClipper -{ -public: - QPolygonClipper() : - buffer1(0), buffer2(0) - { - x1 = y1 = x2 = y2 = 0; - } - - ~QPolygonClipper() - { - } - - void setBoundingRect(const QRect bounds) - { - x1 = bounds.x(); - x2 = bounds.x() + bounds.width(); - y1 = bounds.y(); - y2 = bounds.y() + bounds.height(); - } - - QRect boundingRect() - { - return QRect(QPoint(x1, y1), QPoint(x2, y2)); - } - - inline OutType intersectLeft(const OutType &p1, const OutType &p2) - { - OutType t; - qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x); - t.x = x1; - t.y = static_cast<CastType>(p2.y + (x1 - p2.x) * dy); - return t; - } - - - inline OutType intersectRight(const OutType &p1, const OutType &p2) - { - OutType t; - qreal dy = (p1.y - p2.y) / qreal(p1.x - p2.x); - t.x = x2; - t.y = static_cast<CastType>(p2.y + (x2 - p2.x) * dy); - return t; - } - - - inline OutType intersectTop(const OutType &p1, const OutType &p2) - { - OutType t; - qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y); - t.x = static_cast<CastType>(p2.x + (y1 - p2.y) * dx); - t.y = y1; - return t; - } - - - inline OutType intersectBottom(const OutType &p1, const OutType &p2) - { - OutType t; - qreal dx = (p1.x - p2.x) / qreal(p1.y - p2.y); - t.x = static_cast<CastType>(p2.x + (y2 - p2.y) * dx); - t.y = y2; - return t; - } - - - void clipPolygon(const InType *inPoints, int inCount, OutType **outPoints, int *outCount, - bool closePolygon = true) - { - Q_ASSERT(outPoints); - Q_ASSERT(outCount); - - if (inCount < 2) { - *outCount = 0; - return; - } - - buffer1.reset(); - buffer2.reset(); - - QDataBuffer<OutType> *source = &buffer1; - QDataBuffer<OutType> *clipped = &buffer2; - - // Gather some info since we are iterating through the points anyway.. - bool doLeft = false, doRight = false, doTop = false, doBottom = false; - OutType ot; - for (int i=0; i<inCount; ++i) { - ot = inPoints[i]; - clipped->add(ot); - - if (ot.x < x1) - doLeft = true; - else if (ot.x > x2) - doRight = true; - if (ot.y < y1) - doTop = true; - else if (ot.y > y2) - doBottom = true; - } - - if (doLeft && clipped->size() > 1) { - QDataBuffer<OutType> *tmp = source; - source = clipped; - clipped = tmp; - clipped->reset(); - int lastPos, start; - if (closePolygon) { - lastPos = source->size() - 1; - start = 0; - } else { - lastPos = 0; - start = 1; - if (source->at(0).x >= x1) - clipped->add(source->at(0)); - } - for (int i=start; i<inCount; ++i) { - const OutType &cpt = source->at(i); - const OutType &ppt = source->at(lastPos); - - if (cpt.x >= x1) { - if (ppt.x >= x1) { - clipped->add(cpt); - } else { - clipped->add(intersectLeft(cpt, ppt)); - clipped->add(cpt); - } - } else if (ppt.x >= x1) { - clipped->add(intersectLeft(cpt, ppt)); - } - lastPos = i; - } - } - - if (doRight && clipped->size() > 1) { - QDataBuffer<OutType> *tmp = source; - source = clipped; - clipped = tmp; - clipped->reset(); - int lastPos, start; - if (closePolygon) { - lastPos = source->size() - 1; - start = 0; - } else { - lastPos = 0; - start = 1; - if (source->at(0).x <= x2) - clipped->add(source->at(0)); - } - for (int i=start; i<source->size(); ++i) { - const OutType &cpt = source->at(i); - const OutType &ppt = source->at(lastPos); - - if (cpt.x <= x2) { - if (ppt.x <= x2) { - clipped->add(cpt); - } else { - clipped->add(intersectRight(cpt, ppt)); - clipped->add(cpt); - } - } else if (ppt.x <= x2) { - clipped->add(intersectRight(cpt, ppt)); - } - - lastPos = i; - } - - } - - if (doTop && clipped->size() > 1) { - QDataBuffer<OutType> *tmp = source; - source = clipped; - clipped = tmp; - clipped->reset(); - int lastPos, start; - if (closePolygon) { - lastPos = source->size() - 1; - start = 0; - } else { - lastPos = 0; - start = 1; - if (source->at(0).y >= y1) - clipped->add(source->at(0)); - } - for (int i=start; i<source->size(); ++i) { - const OutType &cpt = source->at(i); - const OutType &ppt = source->at(lastPos); - - if (cpt.y >= y1) { - if (ppt.y >= y1) { - clipped->add(cpt); - } else { - clipped->add(intersectTop(cpt, ppt)); - clipped->add(cpt); - } - } else if (ppt.y >= y1) { - clipped->add(intersectTop(cpt, ppt)); - } - - lastPos = i; - } - } - - if (doBottom && clipped->size() > 1) { - QDataBuffer<OutType> *tmp = source; - source = clipped; - clipped = tmp; - clipped->reset(); - int lastPos, start; - if (closePolygon) { - lastPos = source->size() - 1; - start = 0; - } else { - lastPos = 0; - start = 1; - if (source->at(0).y <= y2) - clipped->add(source->at(0)); - } - for (int i=start; i<source->size(); ++i) { - const OutType &cpt = source->at(i); - const OutType &ppt = source->at(lastPos); - - if (cpt.y <= y2) { - if (ppt.y <= y2) { - clipped->add(cpt); - } else { - clipped->add(intersectBottom(cpt, ppt)); - clipped->add(cpt); - } - } else if (ppt.y <= y2) { - clipped->add(intersectBottom(cpt, ppt)); - } - lastPos = i; - } - } - - if (closePolygon && clipped->size() > 0) { - // close clipped polygon - if (clipped->at(0).x != clipped->at(clipped->size()-1).x || - clipped->at(0).y != clipped->at(clipped->size()-1).y) { - OutType ot = clipped->at(0); - clipped->add(ot); - } - } - *outCount = clipped->size(); - *outPoints = clipped->data(); - } - -private: - int x1, x2, y1, y2; - QDataBuffer<OutType> buffer1; - QDataBuffer<OutType> buffer2; -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h b/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h deleted file mode 100644 index 2986b8f1453..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qt_x11_p.h +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#define register /* C++17 deprecated register */ -#include <X11/Xlib.h> -#include <X11/Xatom.h> -#undef register - -#if QT_CONFIG(xrender) -# include "qtessellator_p.h" -# include <X11/extensions/Xrender.h> -#endif - -#if QT_CONFIG(fontconfig) -#include <fontconfig/fontconfig.h> -#endif - -#if defined(FT_LCD_FILTER_H) -#include FT_LCD_FILTER_H -#endif - -#if defined(FC_LCD_FILTER) - -#ifndef FC_LCD_FILTER_NONE -#define FC_LCD_FILTER_NONE FC_LCD_NONE -#endif - -#ifndef FC_LCD_FILTER_DEFAULT -#define FC_LCD_FILTER_DEFAULT FC_LCD_DEFAULT -#endif - -#ifndef FC_LCD_FILTER_LIGHT -#define FC_LCD_FILTER_LIGHT FC_LCD_LIGHT -#endif - -#ifndef FC_LCD_FILTER_LEGACY -#define FC_LCD_FILTER_LEGACY FC_LCD_LEGACY -#endif - -#endif - -QT_BEGIN_NAMESPACE - -// rename a couple of X defines to get rid of name clashes -// resolve the conflict between X11's FocusIn and QEvent::FocusIn -enum { - XFocusOut = FocusOut, - XFocusIn = FocusIn, - XKeyPress = KeyPress, - XKeyRelease = KeyRelease, - XNone = None, - XRevertToParent = RevertToParent, - XGrayScale = GrayScale, - XCursorShape = CursorShape, -}; -#undef FocusOut -#undef FocusIn -#undef KeyPress -#undef KeyRelease -#undef None -#undef RevertToParent -#undef GrayScale -#undef CursorShape - -#ifdef FontChange -#undef FontChange -#endif - -Q_DECLARE_TYPEINFO(XPoint, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(XRectangle, Q_PRIMITIVE_TYPE); -Q_DECLARE_TYPEINFO(XChar2b, Q_PRIMITIVE_TYPE); -#if QT_CONFIG(xrender) -Q_DECLARE_TYPEINFO(XGlyphElt32, Q_PRIMITIVE_TYPE); -#endif - -struct QX11InfoData; - -enum DesktopEnvironment { - DE_UNKNOWN, - DE_KDE, - DE_GNOME, - DE_CDE, - DE_MEEGO_COMPOSITOR, - DE_4DWM -}; - -struct QXcbX11Data { - Display *display = nullptr; - - // true if Qt is compiled w/ RENDER support and RENDER is supported on the connected Display - bool use_xrender = false; - int xrender_major = 0; - int xrender_version = 0; - - QX11InfoData *screens = nullptr; - Visual **argbVisuals = nullptr; - Colormap *argbColormaps = nullptr; - int screenCount = 0; - int defaultScreen = 0; - - // options - int visual_class = 0; - int visual_id = 0; - int color_count = 0; - bool custom_cmap = false; - - // outside visual/colormap - Visual *visual = nullptr; - Colormap colormap = 0; - -#if QT_CONFIG(xrender) - enum { solid_fill_count = 16 }; - struct SolidFills { - XRenderColor color; - int screen; - Picture picture; - } solid_fills[solid_fill_count]; - enum { pattern_fill_count = 16 }; - struct PatternFills { - XRenderColor color; - XRenderColor bg_color; - int screen; - int style; - bool opaque; - Picture picture; - } pattern_fills[pattern_fill_count]; - Picture getSolidFill(int screen, const QColor &c); - XRenderColor preMultiply(const QColor &c); -#endif - - bool fc_antialias = true; - int fc_hint_style = 0; - - DesktopEnvironment desktopEnvironment = DE_GNOME; -}; - -extern QXcbX11Data *qt_x11Data; -#define X11 qt_x11Data - -struct QX11InfoData { - int screen; - int dpiX; - int dpiY; - int depth; - int cells; - Colormap colormap; - Visual *visual; - bool defaultColormap; - bool defaultVisual; - int subpixel = 0; -}; - -template <class T> -constexpr inline int lowest_bit(T v) noexcept -{ - int result = qCountTrailingZeroBits(v); - return ((result >> 3) == sizeof(T)) ? -1 : result; -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp deleted file mode 100644 index dd83f8852b7..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp +++ /dev/null @@ -1,1466 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include "qtessellator_p.h" - -#include <QRect> -#include <QList> -#include <QMap> -#include <QDebug> - -#include <qmath.h> -#include <limits.h> -#include <algorithm> - -QT_BEGIN_NAMESPACE - -//#define DEBUG -#ifdef DEBUG -#define QDEBUG qDebug -#else -#define QDEBUG if (1){} else qDebug -#endif - -static const bool emit_clever = true; -static const bool mark_clever = false; - -enum VertexFlags { - LineBeforeStarts = 0x1, - LineBeforeEnds = 0x2, - LineBeforeHorizontal = 0x4, - LineAfterStarts = 0x8, - LineAfterEnds = 0x10, - LineAfterHorizontal = 0x20 -}; - - - -class QTessellatorPrivate { -public: - struct Vertices; - - QTessellatorPrivate() {} - - QRectF collectAndSortVertices(const QPointF *points, int *maxActiveEdges); - void cancelCoincidingEdges(); - - void emitEdges(QTessellator *tessellator); - void processIntersections(); - void removeEdges(); - void addEdges(); - void addIntersections(); - - struct Vertex : public QTessellator::Vertex - { - int flags; - }; - - struct Intersection - { - Q27Dot5 y; - int edge; - bool operator <(const Intersection &other) const { - if (y != other.y) - return y < other.y; - return edge < other.edge; - } - }; - struct IntersectionLink - { - int next; - int prev; - }; - typedef QMap<Intersection, IntersectionLink> Intersections; - - struct Edge { - Edge(const Vertices &v, int _edge); - int edge; - const Vertex *v0; - const Vertex *v1; - Q27Dot5 y_left; - Q27Dot5 y_right; - signed int winding : 8; - bool mark; - bool free; - bool intersect_left; - bool intersect_right; - bool isLeftOf(const Edge &other, Q27Dot5 y) const; - Q27Dot5 positionAt(Q27Dot5 y) const; - bool intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const; - - }; - - class EdgeSorter - { - public: - EdgeSorter(int _y) : y(_y) {} - bool operator() (const Edge *e1, const Edge *e2); - int y; - }; - - class Scanline { - public: - Scanline(); - ~Scanline(); - - void init(int maxActiveEdges); - void done(); - - int findEdgePosition(Q27Dot5 x, Q27Dot5 y) const; - int findEdgePosition(const Edge &e) const; - int findEdge(int edge) const; - void clearMarks(); - - void swap(int p1, int p2) { - Edge *tmp = edges[p1]; - edges[p1] = edges[p2]; - edges[p2] = tmp; - } - void insert(int pos, const Edge &e); - void removeAt(int pos); - void markEdges(int pos1, int pos2); - - void prepareLine(); - void lineDone(); - - Edge **old; - int old_size; - - Edge **edges; - int size; - - private: - Edge *edge_table; - int first_unused; - int max_edges; - enum { default_alloc = 32 }; - }; - - struct Vertices { - enum { default_alloc = 128 }; - Vertices(); - ~Vertices(); - void init(int maxVertices); - void done(); - Vertex *storage; - Vertex **sorted; - - Vertex *operator[] (int i) { return storage + i; } - const Vertex *operator[] (int i) const { return storage + i; } - int position(const Vertex *v) const { - return v - storage; - } - Vertex *next(Vertex *v) { - ++v; - if (v == storage + nPoints) - v = storage; - return v; - } - const Vertex *next(const Vertex *v) const { - ++v; - if (v == storage + nPoints) - v = storage; - return v; - } - int nextPos(const Vertex *v) const { - ++v; - if (v == storage + nPoints) - return 0; - return v - storage; - } - Vertex *prev(Vertex *v) { - if (v == storage) - v = storage + nPoints; - --v; - return v; - } - const Vertex *prev(const Vertex *v) const { - if (v == storage) - v = storage + nPoints; - --v; - return v; - } - int prevPos(const Vertex *v) const { - if (v == storage) - v = storage + nPoints; - --v; - return v - storage; - } - int nPoints; - int allocated; - }; - Vertices vertices; - Intersections intersections; - Scanline scanline; - bool winding; - Q27Dot5 y; - int currentVertex; - -private: - void addIntersection(const Edge *e1, const Edge *e2); - bool edgeInChain(Intersection i, int edge); -}; - - -QTessellatorPrivate::Edge::Edge(const QTessellatorPrivate::Vertices &vertices, int edge) -{ - this->edge = edge; - intersect_left = intersect_right = true; - mark = false; - free = false; - - v0 = vertices[edge]; - v1 = vertices.next(v0); - - Q_ASSERT(v0->y != v1->y); - - if (v0->y > v1->y) { - qSwap(v0, v1); - winding = -1; - } else { - winding = 1; - } - y_left = y_right = v0->y; -} - -// This is basically the algorithm from graphics gems. The algorithm -// is cubic in the coordinates at one place. Since we use 64bit -// integers, this implies, that the allowed range for our coordinates -// is limited to 21 bits. With 5 bits behind the decimal, this -// implies that differences in coordaintes can range from 2*SHORT_MIN -// to 2*SHORT_MAX, giving us efficiently a coordinate system from -// SHORT_MIN to SHORT_MAX. -// - -// WARNING: It's absolutely critical that the intersect() and isLeftOf() methods use -// exactly the same algorithm to calculate yi. It's also important to be sure the algorithms -// are transitive (ie. the conditions below are true for all input data): -// -// a.intersect(b) == b.intersect(a) -// a.isLeftOf(b) != b.isLeftOf(a) -// -// This is tricky to get right, so be very careful when changing anything in here! - -static inline bool sameSign(qint64 a, qint64 b) { - return (((qint64) ((quint64) a ^ (quint64) b)) >= 0 ); -} - -bool QTessellatorPrivate::Edge::intersect(const Edge &other, Q27Dot5 *y, bool *det_positive) const -{ - qint64 a1 = v1->y - v0->y; - qint64 b1 = v0->x - v1->x; - - qint64 a2 = other.v1->y - other.v0->y; - qint64 b2 = other.v0->x - other.v1->x; - - qint64 det = a1 * b2 - a2 * b1; - if (det == 0) - return false; - - qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; - - qint64 r3 = a1 * other.v0->x + b1 * other.v0->y + c1; - qint64 r4 = a1 * other.v1->x + b1 * other.v1->y + c1; - - // Check signs of r3 and r4. If both point 3 and point 4 lie on - // same side of line 1, the line segments do not intersect. - QDEBUG() << " " << r3 << r4; - if (r3 != 0 && r4 != 0 && sameSign( r3, r4 )) - return false; - - qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; - - qint64 r1 = a2 * v0->x + b2 * v0->y + c2; - qint64 r2 = a2 * v1->x + b2 * v1->y + c2; - - // Check signs of r1 and r2. If both point 1 and point 2 lie - // on same side of second line segment, the line segments do not intersect. - QDEBUG() << " " << r1 << r2; - if (r1 != 0 && r2 != 0 && sameSign( r1, r2 )) - return false; - - // The det/2 is to get rounding instead of truncating. It - // is added or subtracted to the numerator, depending upon the - // sign of the numerator. - qint64 offset = det < 0 ? -det : det; - offset >>= 1; - - qint64 num = a2 * c1 - a1 * c2; - *y = ( num < 0 ? num - offset : num + offset ) / det; - - *det_positive = (det > 0); - - return true; -} - -#undef SAME_SIGNS - -bool QTessellatorPrivate::Edge::isLeftOf(const Edge &other, Q27Dot5 y) const -{ -// QDEBUG() << "isLeftOf" << edge << other.edge << y; - qint64 a1 = v1->y - v0->y; - qint64 b1 = v0->x - v1->x; - qint64 a2 = other.v1->y - other.v0->y; - qint64 b2 = other.v0->x - other.v1->x; - - qint64 c2 = qint64(other.v1->x) * other.v0->y - qint64(other.v0->x) * other.v1->y; - - qint64 det = a1 * b2 - a2 * b1; - if (det == 0) { - // lines are parallel. Only need to check side of one point - // fixed ordering for coincident edges - qint64 r1 = a2 * v0->x + b2 * v0->y + c2; -// QDEBUG() << "det = 0" << r1; - if (r1 == 0) - return edge < other.edge; - return (r1 < 0); - } - - // not parallel, need to find the y coordinate of the intersection point - qint64 c1 = qint64(v1->x) * v0->y - qint64(v0->x) * v1->y; - - qint64 offset = det < 0 ? -det : det; - offset >>= 1; - - qint64 num = a2 * c1 - a1 * c2; - qint64 yi = ( num < 0 ? num - offset : num + offset ) / det; -// QDEBUG() << " num=" << num << "offset=" << offset << "det=" << det; - - return ((yi > y) ^ (det < 0)); -} - -static inline bool compareVertex(const QTessellatorPrivate::Vertex *p1, - const QTessellatorPrivate::Vertex *p2) -{ - if (p1->y == p2->y) { - if (p1->x == p2->x) - return p1 < p2; - return p1->x < p2->x; - } - return p1->y < p2->y; -} - -Q27Dot5 QTessellatorPrivate::Edge::positionAt(Q27Dot5 y) const -{ - if (y == v0->y) - return v0->x; - else if (y == v1->y) - return v1->x; - - qint64 d = v1->x - v0->x; - return (v0->x + d*(y - v0->y)/(v1->y-v0->y)); -} - -bool QTessellatorPrivate::EdgeSorter::operator() (const Edge *e1, const Edge *e2) -{ - return e1->isLeftOf(*e2, y); -} - - -QTessellatorPrivate::Scanline::Scanline() -{ - edges = 0; - edge_table = 0; - old = 0; -} - -void QTessellatorPrivate::Scanline::init(int maxActiveEdges) -{ - maxActiveEdges *= 2; - if (!edges || maxActiveEdges > default_alloc) { - max_edges = maxActiveEdges; - int s = qMax(maxActiveEdges + 1, default_alloc + 1); - edges = q_check_ptr((Edge **)realloc(edges, s*sizeof(Edge *))); - edge_table = q_check_ptr((Edge *)realloc(edge_table, s*sizeof(Edge))); - old = q_check_ptr((Edge **)realloc(old, s*sizeof(Edge *))); - } - size = 0; - old_size = 0; - first_unused = 0; - for (int i = 0; i < maxActiveEdges; ++i) - edge_table[i].edge = i+1; - edge_table[maxActiveEdges].edge = -1; -} - -void QTessellatorPrivate::Scanline::done() -{ - if (max_edges > default_alloc) { - free(edges); - free(old); - free(edge_table); - edges = 0; - old = 0; - edge_table = 0; - } -} - -QTessellatorPrivate::Scanline::~Scanline() -{ - free(edges); - free(old); - free(edge_table); -} - -int QTessellatorPrivate::Scanline::findEdgePosition(Q27Dot5 x, Q27Dot5 y) const -{ - int min = 0; - int max = size - 1; - while (min < max) { - int pos = min + ((max - min + 1) >> 1); - Q27Dot5 ax = edges[pos]->positionAt(y); - if (ax > x) { - max = pos - 1; - } else { - min = pos; - } - } - return min; -} - -int QTessellatorPrivate::Scanline::findEdgePosition(const Edge &e) const -{ -// qDebug() << ">> findEdgePosition"; - int min = 0; - int max = size; - while (min < max) { - int pos = min + ((max - min) >> 1); -// qDebug() << " " << min << max << pos << edges[pos]->isLeftOf(e, e.y0); - if (edges[pos]->isLeftOf(e, e.v0->y)) { - min = pos + 1; - } else { - max = pos; - } - } -// qDebug() << "<< findEdgePosition got" << min; - return min; -} - -int QTessellatorPrivate::Scanline::findEdge(int edge) const -{ - for (int i = 0; i < size; ++i) { - int item_edge = edges[i]->edge; - if (item_edge == edge) - return i; - } - //Q_ASSERT(false); - return -1; -} - -void QTessellatorPrivate::Scanline::clearMarks() -{ - for (int i = 0; i < size; ++i) { - edges[i]->mark = false; - edges[i]->intersect_left = false; - edges[i]->intersect_right = false; - } -} - -void QTessellatorPrivate::Scanline::prepareLine() -{ - Edge **end = edges + size; - Edge **e = edges; - Edge **o = old; - while (e < end) { - *o = *e; - ++o; - ++e; - } - old_size = size; -} - -void QTessellatorPrivate::Scanline::lineDone() -{ - Edge **end = old + old_size; - Edge **e = old; - while (e < end) { - if ((*e)->free) { - (*e)->edge = first_unused; - first_unused = (*e - edge_table); - } - ++e; - } -} - -void QTessellatorPrivate::Scanline::insert(int pos, const Edge &e) -{ - Edge *edge = edge_table + first_unused; - first_unused = edge->edge; - Q_ASSERT(first_unused != -1); - *edge = e; - memmove(edges + pos + 1, edges + pos, (size - pos)*sizeof(Edge *)); - edges[pos] = edge; - ++size; -} - -void QTessellatorPrivate::Scanline::removeAt(int pos) -{ - Edge *e = edges[pos]; - e->free = true; - --size; - memmove(edges + pos, edges + pos + 1, (size - pos)*sizeof(Edge *)); -} - -void QTessellatorPrivate::Scanline::markEdges(int pos1, int pos2) -{ - if (pos2 < pos1) - return; - - for (int i = pos1; i <= pos2; ++i) - edges[i]->mark = true; -} - - -QTessellatorPrivate::Vertices::Vertices() -{ - storage = 0; - sorted = 0; - allocated = 0; - nPoints = 0; -} - -QTessellatorPrivate::Vertices::~Vertices() -{ - if (storage) { - free(storage); - free(sorted); - } -} - -void QTessellatorPrivate::Vertices::init(int maxVertices) -{ - if (!storage || maxVertices > allocated) { - int size = qMax((int)default_alloc, maxVertices); - storage = q_check_ptr((Vertex *)realloc(storage, size*sizeof(Vertex))); - sorted = q_check_ptr((Vertex **)realloc(sorted, size*sizeof(Vertex *))); - allocated = maxVertices; - } -} - -void QTessellatorPrivate::Vertices::done() -{ - if (allocated > default_alloc) { - free(storage); - free(sorted); - storage = 0; - sorted = 0; - allocated = 0; - } -} - - - -static inline void fillTrapezoid(Q27Dot5 y1, Q27Dot5 y2, int left, int right, - const QTessellatorPrivate::Vertices &vertices, - QTessellator::Trapezoid *trap) -{ - trap->top = y1; - trap->bottom = y2; - const QTessellatorPrivate::Vertex *v = vertices[left]; - trap->topLeft = v; - trap->bottomLeft = vertices.next(v); - if (trap->topLeft->y > trap->bottomLeft->y) - qSwap(trap->topLeft,trap->bottomLeft); - v = vertices[right]; - trap->topRight = v; - trap->bottomRight = vertices.next(v); - if (trap->topRight->y > trap->bottomRight->y) - qSwap(trap->topRight, trap->bottomRight); -} - -QRectF QTessellatorPrivate::collectAndSortVertices(const QPointF *points, int *maxActiveEdges) -{ - *maxActiveEdges = 0; - Vertex *v = vertices.storage; - Vertex **vv = vertices.sorted; - - qreal xmin(points[0].x()); - qreal xmax(points[0].x()); - qreal ymin(points[0].y()); - qreal ymax(points[0].y()); - - // collect vertex data - Q27Dot5 y_prev = FloatToQ27Dot5(points[vertices.nPoints-1].y()); - Q27Dot5 x_next = FloatToQ27Dot5(points[0].x()); - Q27Dot5 y_next = FloatToQ27Dot5(points[0].y()); - int j = 0; - int i = 0; - while (i < vertices.nPoints) { - Q27Dot5 y_curr = y_next; - - *vv = v; - - v->x = x_next; - v->y = y_next; - v->flags = 0; - - next_point: - - xmin = qMin(xmin, points[i+1].x()); - xmax = qMax(xmax, points[i+1].x()); - ymin = qMin(ymin, points[i+1].y()); - ymax = qMax(ymax, points[i+1].y()); - - y_next = FloatToQ27Dot5(points[i+1].y()); - x_next = FloatToQ27Dot5(points[i+1].x()); - - // skip vertices on top of each other - if (v->x == x_next && v->y == y_next) { - ++i; - if (i < vertices.nPoints) - goto next_point; - Vertex *v0 = vertices.storage; - v0->flags &= ~(LineBeforeStarts|LineBeforeEnds|LineBeforeHorizontal); - if (y_prev < y_curr) - v0->flags |= LineBeforeEnds; - else if (y_prev > y_curr) - v0->flags |= LineBeforeStarts; - else - v0->flags |= LineBeforeHorizontal; - if ((v0->flags & (LineBeforeStarts|LineAfterStarts)) - && !(v0->flags & (LineAfterEnds|LineBeforeEnds))) - *maxActiveEdges += 2; - break; - } - - if (y_prev < y_curr) - v->flags |= LineBeforeEnds; - else if (y_prev > y_curr) - v->flags |= LineBeforeStarts; - else - v->flags |= LineBeforeHorizontal; - - - if (y_curr < y_next) - v->flags |= LineAfterStarts; - else if (y_curr > y_next) - v->flags |= LineAfterEnds; - else - v->flags |= LineAfterHorizontal; - // ### could probably get better limit by looping over sorted list and counting down on ending edges - if ((v->flags & (LineBeforeStarts|LineAfterStarts)) - && !(v->flags & (LineAfterEnds|LineBeforeEnds))) - *maxActiveEdges += 2; - y_prev = y_curr; - ++v; - ++vv; - ++j; - ++i; - } - vertices.nPoints = j; - - QDEBUG() << "maxActiveEdges=" << *maxActiveEdges; - vv = vertices.sorted; - std::sort(vv, vv + vertices.nPoints, compareVertex); - - return QRectF(xmin, ymin, xmax-xmin, ymax-ymin); -} - -struct QCoincidingEdge { - QTessellatorPrivate::Vertex *start; - QTessellatorPrivate::Vertex *end; - bool used; - bool before; - - inline bool operator<(const QCoincidingEdge &e2) const - { - return end->y == e2.end->y ? end->x < e2.end->x : end->y < e2.end->y; - } -}; - -static void cancelEdges(QCoincidingEdge &e1, QCoincidingEdge &e2) -{ - if (e1.before) { - e1.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); - e1.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); - } else { - e1.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); - e1.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); - } - if (e2.before) { - e2.start->flags &= ~(LineBeforeStarts|LineBeforeHorizontal); - e2.end->flags &= ~(LineAfterEnds|LineAfterHorizontal); - } else { - e2.start->flags &= ~(LineAfterStarts|LineAfterHorizontal); - e2.end->flags &= ~(LineBeforeEnds|LineBeforeHorizontal); - } - e1.used = e2.used = true; -} - -void QTessellatorPrivate::cancelCoincidingEdges() -{ - Vertex **vv = vertices.sorted; - - QCoincidingEdge *tl = nullptr; - int tlSize = 0; - - for (int i = 0; i < vertices.nPoints - 1; ++i) { - Vertex *v = vv[i]; - int testListSize = 0; - while (i < vertices.nPoints - 1) { - Vertex *n = vv[i]; - if (v->x != n->x || v->y != n->y) - break; - - if (testListSize > tlSize - 2) { - tlSize = qMax(tlSize*2, 16); - tl = q_check_ptr((QCoincidingEdge *)realloc(tl, tlSize*sizeof(QCoincidingEdge))); - } - if (n->flags & (LineBeforeStarts|LineBeforeHorizontal)) { - tl[testListSize].start = n; - tl[testListSize].end = vertices.prev(n); - tl[testListSize].used = false; - tl[testListSize].before = true; - ++testListSize; - } - if (n->flags & (LineAfterStarts|LineAfterHorizontal)) { - tl[testListSize].start = n; - tl[testListSize].end = vertices.next(n); - tl[testListSize].used = false; - tl[testListSize].before = false; - ++testListSize; - } - ++i; - } - if (!testListSize) - continue; - - std::sort(tl, tl + testListSize); - -QT_WARNING_PUSH -QT_WARNING_DISABLE_CLANG("-Wfor-loop-analysis") - for (int j = 0; j < testListSize; ++j) { - if (tl[j].used) - continue; - - for (int k = j + 1; k < testListSize; ++k) { - if (tl[j].end->x != tl[k].end->x - || tl[j].end->y != tl[k].end->y - || tl[k].used) - break; - - if (!winding || tl[j].before != tl[k].before) { - cancelEdges(tl[j], tl[k]); - break; - } - ++k; - } - ++j; - } -QT_WARNING_POP - } - free(tl); -} - - -void QTessellatorPrivate::emitEdges(QTessellator *tessellator) -{ - //QDEBUG() << "TRAPS:"; - if (!scanline.old_size) - return; - - // emit edges - if (winding) { - // winding fill rule - int w = 0; - - scanline.old[0]->y_left = y; - - for (int i = 0; i < scanline.old_size - 1; ++i) { - Edge *left = scanline.old[i]; - Edge *right = scanline.old[i+1]; - w += left->winding; -// qDebug() << "i=" << i << "edge->winding=" << left->winding << "winding=" << winding; - if (w == 0) { - left->y_right = y; - right->y_left = y; - } else if (!emit_clever || left->mark || right->mark) { - Q27Dot5 top = qMax(left->y_right, right->y_left); - if (top != y) { - QTessellator::Trapezoid trap; - fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); - tessellator->addTrap(trap); -// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; - } - right->y_left = y; - left->y_right = y; - } - left->mark = false; - } - if (scanline.old[scanline.old_size - 1]->mark) { - scanline.old[scanline.old_size - 1]->y_right = y; - scanline.old[scanline.old_size - 1]->mark = false; - } - } else { - // odd-even fill rule - for (int i = 0; i < scanline.old_size; i += 2) { - Edge *left = scanline.old[i]; - Edge *right = scanline.old[i+1]; - if (!emit_clever || left->mark || right->mark) { - Q27Dot5 top = qMax(left->y_right, right->y_left); - if (top != y) { - QTessellator::Trapezoid trap; - fillTrapezoid(top, y, left->edge, right->edge, vertices, &trap); - tessellator->addTrap(trap); - } -// QDEBUG() << " top=" << Q27Dot5ToDouble(top) << "left=" << left->edge << "right=" << right->edge; - left->y_left = y; - left->y_right = y; - right->y_left = y; - right->y_right = y; - left->mark = right->mark = false; - } - } - } -} - - -void QTessellatorPrivate::processIntersections() -{ - QDEBUG() << "PROCESS INTERSECTIONS"; - // process intersections - while (!intersections.isEmpty()) { - Intersections::iterator it = intersections.begin(); - if (it.key().y != y) - break; - - // swap edges - QDEBUG() << " swapping intersecting edges "; - int min = scanline.size; - int max = 0; - Q27Dot5 xmin = INT_MAX; - Q27Dot5 xmax = INT_MIN; - int num = 0; - while (1) { - const Intersection i = it.key(); - int next = it->next; - - int edgePos = scanline.findEdge(i.edge); - if (edgePos >= 0) { - ++num; - min = qMin(edgePos, min); - max = qMax(edgePos, max); - Edge *edge = scanline.edges[edgePos]; - xmin = qMin(xmin, edge->positionAt(y)); - xmax = qMax(xmax, edge->positionAt(y)); - } - Intersection key; - key.y = y; - key.edge = next; - it = intersections.find(key); - intersections.remove(i); - if (it == intersections.end()) - break; - } - if (num < 2) - continue; - - Q_ASSERT(min != max); - QDEBUG() << "sorting between" << min << "and" << max << "xpos=" << xmin << xmax; - while (min > 0 && scanline.edges[min - 1]->positionAt(y) >= xmin) { - QDEBUG() << " adding edge on left"; - --min; - } - while (max < scanline.size - 1 && scanline.edges[max + 1]->positionAt(y) <= xmax) { - QDEBUG() << " adding edge on right"; - ++max; - } - - std::sort(scanline.edges + min, scanline.edges + max + 1, EdgeSorter(y)); -#ifdef DEBUG - for (int i = min; i <= max; ++i) - QDEBUG() << " " << scanline.edges[i]->edge << "at pos" << i; -#endif - for (int i = min; i <= max; ++i) { - Edge *edge = scanline.edges[i]; - edge->intersect_left = true; - edge->intersect_right = true; - edge->mark = true; - } - } -} - -void QTessellatorPrivate::removeEdges() -{ - int cv = currentVertex; - while (cv < vertices.nPoints) { - const Vertex *v = vertices.sorted[cv]; - if (v->y > y) - break; - if (v->flags & LineBeforeEnds) { - QDEBUG() << " removing edge" << vertices.prevPos(v); - int pos = scanline.findEdge(vertices.prevPos(v)); - if (pos == -1) - continue; - scanline.edges[pos]->mark = true; - if (pos > 0) - scanline.edges[pos - 1]->intersect_right = true; - if (pos < scanline.size - 1) - scanline.edges[pos + 1]->intersect_left = true; - scanline.removeAt(pos); - } - if (v->flags & LineAfterEnds) { - QDEBUG() << " removing edge" << vertices.position(v); - int pos = scanline.findEdge(vertices.position(v)); - if (pos == -1) - continue; - scanline.edges[pos]->mark = true; - if (pos > 0) - scanline.edges[pos - 1]->intersect_right = true; - if (pos < scanline.size - 1) - scanline.edges[pos + 1]->intersect_left = true; - scanline.removeAt(pos); - } - ++cv; - } -} - -void QTessellatorPrivate::addEdges() -{ - while (currentVertex < vertices.nPoints) { - const Vertex *v = vertices.sorted[currentVertex]; - if (v->y > y) - break; - if (v->flags & LineBeforeStarts) { - // add new edge - int start = vertices.prevPos(v); - Edge e(vertices, start); - int pos = scanline.findEdgePosition(e); - QDEBUG() << " adding edge" << start << "at position" << pos; - scanline.insert(pos, e); - if (!mark_clever || !(v->flags & LineAfterEnds)) { - if (pos > 0) - scanline.edges[pos - 1]->mark = true; - if (pos < scanline.size - 1) - scanline.edges[pos + 1]->mark = true; - } - } - if (v->flags & LineAfterStarts) { - Edge e(vertices, vertices.position(v)); - int pos = scanline.findEdgePosition(e); - QDEBUG() << " adding edge" << vertices.position(v) << "at position" << pos; - scanline.insert(pos, e); - if (!mark_clever || !(v->flags & LineBeforeEnds)) { - if (pos > 0) - scanline.edges[pos - 1]->mark = true; - if (pos < scanline.size - 1) - scanline.edges[pos + 1]->mark = true; - } - } - if (v->flags & LineAfterHorizontal) { - int pos1 = scanline.findEdgePosition(v->x, v->y); - const Vertex *next = vertices.next(v); - Q_ASSERT(v->y == next->y); - int pos2 = scanline.findEdgePosition(next->x, next->y); - if (pos2 < pos1) - qSwap(pos1, pos2); - if (pos1 > 0) - --pos1; - if (pos2 == scanline.size) - --pos2; - //QDEBUG() << "marking horizontal edge from " << pos1 << "to" << pos2; - scanline.markEdges(pos1, pos2); - } - ++currentVertex; - } -} - -#ifdef DEBUG -static void checkLinkChain(const QTessellatorPrivate::Intersections &intersections, - QTessellatorPrivate::Intersection i) -{ -// qDebug() << " Link chain: "; - int end = i.edge; - while (1) { - QTessellatorPrivate::IntersectionLink l = intersections.value(i); -// qDebug() << " " << i.edge << "next=" << l.next << "prev=" << l.prev; - if (l.next == end) - break; - Q_ASSERT(l.next != -1); - Q_ASSERT(l.prev != -1); - - QTessellatorPrivate::Intersection i2 = i; - i2.edge = l.next; - QTessellatorPrivate::IntersectionLink l2 = intersections.value(i2); - - Q_ASSERT(l2.next != -1); - Q_ASSERT(l2.prev != -1); - Q_ASSERT(l.next == i2.edge); - Q_ASSERT(l2.prev == i.edge); - i = i2; - } -} -#endif - -bool QTessellatorPrivate::edgeInChain(Intersection i, int edge) -{ - int end = i.edge; - while (1) { - if (i.edge == edge) - return true; - IntersectionLink l = intersections.value(i); - if (l.next == end) - break; - Q_ASSERT(l.next != -1); - Q_ASSERT(l.prev != -1); - - Intersection i2 = i; - i2.edge = l.next; - -#ifndef QT_NO_DEBUG - IntersectionLink l2 = intersections.value(i2); - Q_ASSERT(l2.next != -1); - Q_ASSERT(l2.prev != -1); - Q_ASSERT(l.next == i2.edge); - Q_ASSERT(l2.prev == i.edge); -#endif - i = i2; - } - return false; -} - - -void QTessellatorPrivate::addIntersection(const Edge *e1, const Edge *e2) -{ - const IntersectionLink emptyLink = {-1, -1}; - - int next = vertices.nextPos(vertices[e1->edge]); - if (e2->edge == next) - return; - int prev = vertices.prevPos(vertices[e1->edge]); - if (e2->edge == prev) - return; - - Q27Dot5 yi; - bool det_positive; - bool isect = e1->intersect(*e2, &yi, &det_positive); - QDEBUG("checking edges %d and %d", e1->edge, e2->edge); - if (!isect) { - QDEBUG() << " no intersection"; - return; - } - - // don't emit an intersection if it's at the start of a line segment or above us - if (yi <= y) { - if (!det_positive) - return; - QDEBUG() << " ----->>>>>> WRONG ORDER!"; - yi = y; - } - QDEBUG() << " between edges " << e1->edge << "and" << e2->edge << "at point (" - << Q27Dot5ToDouble(yi) << ')'; - - Intersection i1; - i1.y = yi; - i1.edge = e1->edge; - IntersectionLink link1 = intersections.value(i1, emptyLink); - Intersection i2; - i2.y = yi; - i2.edge = e2->edge; - IntersectionLink link2 = intersections.value(i2, emptyLink); - - // new pair of edges - if (link1.next == -1 && link2.next == -1) { - link1.next = link1.prev = i2.edge; - link2.next = link2.prev = i1.edge; - } else if (link1.next == i2.edge || link1.prev == i2.edge - || link2.next == i1.edge || link2.prev == i1.edge) { -#ifdef DEBUG - checkLinkChain(intersections, i1); - checkLinkChain(intersections, i2); - Q_ASSERT(edgeInChain(i1, i2.edge)); -#endif - return; - } else if (link1.next == -1 || link2.next == -1) { - if (link2.next == -1) { - qSwap(i1, i2); - qSwap(link1, link2); - } - Q_ASSERT(link1.next == -1); -#ifdef DEBUG - checkLinkChain(intersections, i2); -#endif - // only i2 in list - link1.next = i2.edge; - link1.prev = link2.prev; - link2.prev = i1.edge; - Intersection other; - other.y = yi; - other.edge = link1.prev; - IntersectionLink link = intersections.value(other, emptyLink); - Q_ASSERT(link.next == i2.edge); - Q_ASSERT(link.prev != -1); - link.next = i1.edge; - intersections.insert(other, link); - } else { - bool connected = edgeInChain(i1, i2.edge); - if (connected) - return; -#ifdef DEBUG - checkLinkChain(intersections, i1); - checkLinkChain(intersections, i2); -#endif - // both already in some list. Have to make sure they are connected - // this can be done by cutting open the ring(s) after the two eges and - // connecting them again - Intersection other1; - other1.y = yi; - other1.edge = link1.next; - IntersectionLink linko1 = intersections.value(other1, emptyLink); - Intersection other2; - other2.y = yi; - other2.edge = link2.next; - IntersectionLink linko2 = intersections.value(other2, emptyLink); - - linko1.prev = i2.edge; - link2.next = other1.edge; - - linko2.prev = i1.edge; - link1.next = other2.edge; - intersections.insert(other1, linko1); - intersections.insert(other2, linko2); - } - intersections.insert(i1, link1); - intersections.insert(i2, link2); -#ifdef DEBUG - checkLinkChain(intersections, i1); - checkLinkChain(intersections, i2); - Q_ASSERT(edgeInChain(i1, i2.edge)); -#endif - return; - -} - - -void QTessellatorPrivate::addIntersections() -{ - if (scanline.size) { - QDEBUG() << "INTERSECTIONS"; - // check marked edges for intersections -#ifdef DEBUG - for (int i = 0; i < scanline.size; ++i) { - Edge *e = scanline.edges[i]; - QDEBUG() << " " << i << e->edge << "isect=(" << e->intersect_left << e->intersect_right - << ')'; - } -#endif - - for (int i = 0; i < scanline.size - 1; ++i) { - Edge *e1 = scanline.edges[i]; - Edge *e2 = scanline.edges[i + 1]; - // check for intersection - if (e1->intersect_right || e2->intersect_left) - addIntersection(e1, e2); - } - } -#if 0 - if (intersections.constBegin().key().y == y) { - QDEBUG() << "----------------> intersection on same line"; - scanline.clearMarks(); - scanline.processIntersections(y, &intersections); - goto redo; - } -#endif -} - - -QTessellator::QTessellator() -{ - d = new QTessellatorPrivate; -} - -QTessellator::~QTessellator() -{ - delete d; -} - -void QTessellator::setWinding(bool w) -{ - d->winding = w; -} - - -QRectF QTessellator::tessellate(const QPointF *points, int nPoints) -{ - Q_ASSERT(points[0] == points[nPoints-1]); - --nPoints; - -#ifdef DEBUG - QDEBUG()<< "POINTS:"; - for (int i = 0; i < nPoints; ++i) { - QDEBUG() << points[i]; - } -#endif - - // collect edges and calculate bounds - d->vertices.nPoints = nPoints; - d->vertices.init(nPoints); - - int maxActiveEdges = 0; - QRectF br = d->collectAndSortVertices(points, &maxActiveEdges); - d->cancelCoincidingEdges(); - -#ifdef DEBUG - QDEBUG() << "nPoints = " << nPoints << "using " << d->vertices.nPoints; - QDEBUG()<< "VERTICES:"; - for (int i = 0; i < d->vertices.nPoints; ++i) { - QDEBUG() << " " << i << ": " - << "point=" << d->vertices.position(d->vertices.sorted[i]) - << "flags=" << d->vertices.sorted[i]->flags - << "pos=(" << Q27Dot5ToDouble(d->vertices.sorted[i]->x) << '/' - << Q27Dot5ToDouble(d->vertices.sorted[i]->y) << ')'; - } -#endif - - d->scanline.init(maxActiveEdges); - d->y = INT_MIN/256; - d->currentVertex = 0; - - while (d->currentVertex < d->vertices.nPoints) { - d->scanline.clearMarks(); - - d->y = d->vertices.sorted[d->currentVertex]->y; - if (!d->intersections.isEmpty()) - d->y = qMin(d->y, d->intersections.constBegin().key().y); - - QDEBUG()<< "===== SCANLINE: y =" << Q27Dot5ToDouble(d->y) << " ====="; - - d->scanline.prepareLine(); - d->processIntersections(); - d->removeEdges(); - d->addEdges(); - d->addIntersections(); - d->emitEdges(this); - d->scanline.lineDone(); - -#ifdef DEBUG - QDEBUG()<< "===== edges:"; - for (int i = 0; i < d->scanline.size; ++i) { - QDEBUG() << " " << d->scanline.edges[i]->edge - << "p0= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v0->x) - << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v0->y) - << ") p1= (" << Q27Dot5ToDouble(d->scanline.edges[i]->v1->x) - << '/' << Q27Dot5ToDouble(d->scanline.edges[i]->v1->y) << ')' - << "x=" << Q27Dot5ToDouble(d->scanline.edges[i]->positionAt(d->y)) - << "isLeftOfNext=" - << ((i < d->scanline.size - 1) - ? d->scanline.edges[i]->isLeftOf(*d->scanline.edges[i+1], d->y) - : true); - } -#endif -} - - d->scanline.done(); - d->intersections.clear(); - return br; -} - -// tessellates the given convex polygon -void QTessellator::tessellateConvex(const QPointF *points, int nPoints) -{ - Q_ASSERT(points[0] == points[nPoints-1]); - --nPoints; - - d->vertices.nPoints = nPoints; - d->vertices.init(nPoints); - - for (int i = 0; i < nPoints; ++i) { - d->vertices[i]->x = FloatToQ27Dot5(points[i].x()); - d->vertices[i]->y = FloatToQ27Dot5(points[i].y()); - } - - int left = 0, right = 0; - - int top = 0; - for (int i = 1; i < nPoints; ++i) { - if (d->vertices[i]->y < d->vertices[top]->y) - top = i; - } - - left = (top + nPoints - 1) % nPoints; - right = (top + 1) % nPoints; - - while (d->vertices[left]->x == d->vertices[top]->x && d->vertices[left]->y == d->vertices[top]->y && left != right) - left = (left + nPoints - 1) % nPoints; - - while (d->vertices[right]->x == d->vertices[top]->x && d->vertices[right]->y == d->vertices[top]->y && left != right) - right = (right + 1) % nPoints; - - if (left == right) - return; - - int dir = 1; - - Vertex dLeft = { d->vertices[top]->x - d->vertices[left]->x, - d->vertices[top]->y - d->vertices[left]->y }; - - Vertex dRight = { d->vertices[right]->x - d->vertices[top]->x, - d->vertices[right]->y - d->vertices[top]->y }; - - Q27Dot5 cross = dLeft.x * dRight.y - dLeft.y * dRight.x; - - // flip direction if polygon is clockwise - if (cross < 0 || (cross == 0 && dLeft.x > 0)) { - qSwap(left, right); - dir = -1; - } - - Vertex *lastLeft = d->vertices[top]; - Vertex *lastRight = d->vertices[top]; - - QTessellator::Trapezoid trap; - - while (lastLeft->y == d->vertices[left]->y && left != right) { - lastLeft = d->vertices[left]; - left = (left + nPoints - dir) % nPoints; - } - - while (lastRight->y == d->vertices[right]->y && left != right) { - lastRight = d->vertices[right]; - right = (right + nPoints + dir) % nPoints; - } - - while (true) { - trap.top = qMax(lastRight->y, lastLeft->y); - trap.bottom = qMin(d->vertices[left]->y, d->vertices[right]->y); - trap.topLeft = lastLeft; - trap.topRight = lastRight; - trap.bottomLeft = d->vertices[left]; - trap.bottomRight = d->vertices[right]; - - if (trap.bottom > trap.top) - addTrap(trap); - - if (left == right) - break; - - if (d->vertices[right]->y < d->vertices[left]->y) { - do { - lastRight = d->vertices[right]; - right = (right + nPoints + dir) % nPoints; - } - while (lastRight->y == d->vertices[right]->y && left != right); - } else { - do { - lastLeft = d->vertices[left]; - left = (left + nPoints - dir) % nPoints; - } - while (lastLeft->y == d->vertices[left]->y && left != right); - } - } -} - -// tessellates the stroke of the line from a_ to b_ with the given width and a flat cap -void QTessellator::tessellateRect(const QPointF &a_, const QPointF &b_, qreal width) -{ - Vertex a = { FloatToQ27Dot5(a_.x()), FloatToQ27Dot5(a_.y()) }; - Vertex b = { FloatToQ27Dot5(b_.x()), FloatToQ27Dot5(b_.y()) }; - - QPointF pa = a_, pb = b_; - - if (a.y > b.y) { - qSwap(a, b); - qSwap(pa, pb); - } - - Vertex delta = { b.x - a.x, b.y - a.y }; - - if (delta.x == 0 && delta.y == 0) - return; - - qreal hw = 0.5 * width; - - if (delta.x == 0) { - Q27Dot5 halfWidth = FloatToQ27Dot5(hw); - - if (halfWidth == 0) - return; - - Vertex topLeft = { a.x - halfWidth, a.y }; - Vertex topRight = { a.x + halfWidth, a.y }; - Vertex bottomLeft = { a.x - halfWidth, b.y }; - Vertex bottomRight = { a.x + halfWidth, b.y }; - - QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; - addTrap(trap); - } else if (delta.y == 0) { - Q27Dot5 halfWidth = FloatToQ27Dot5(hw); - - if (halfWidth == 0) - return; - - if (a.x > b.x) - qSwap(a.x, b.x); - - Vertex topLeft = { a.x, a.y - halfWidth }; - Vertex topRight = { b.x, a.y - halfWidth }; - Vertex bottomLeft = { a.x, a.y + halfWidth }; - Vertex bottomRight = { b.x, a.y + halfWidth }; - - QTessellator::Trapezoid trap = { topLeft.y, bottomLeft.y, &topLeft, &bottomLeft, &topRight, &bottomRight }; - addTrap(trap); - } else { - QPointF perp(pb.y() - pa.y(), pa.x() - pb.x()); - qreal length = qSqrt(perp.x() * perp.x() + perp.y() * perp.y()); - - if (qFuzzyIsNull(length)) - return; - - // need the half of the width - perp *= hw / length; - - QPointF pta = pa + perp; - QPointF ptb = pa - perp; - QPointF ptc = pb - perp; - QPointF ptd = pb + perp; - - Vertex ta = { FloatToQ27Dot5(pta.x()), FloatToQ27Dot5(pta.y()) }; - Vertex tb = { FloatToQ27Dot5(ptb.x()), FloatToQ27Dot5(ptb.y()) }; - Vertex tc = { FloatToQ27Dot5(ptc.x()), FloatToQ27Dot5(ptc.y()) }; - Vertex td = { FloatToQ27Dot5(ptd.x()), FloatToQ27Dot5(ptd.y()) }; - - if (ta.y < tb.y) { - if (tb.y < td.y) { - QTessellator::Trapezoid top = { ta.y, tb.y, &ta, &tb, &ta, &td }; - QTessellator::Trapezoid bottom = { td.y, tc.y, &tb, &tc, &td, &tc }; - addTrap(top); - addTrap(bottom); - - QTessellator::Trapezoid middle = { tb.y, td.y, &tb, &tc, &ta, &td }; - addTrap(middle); - } else { - QTessellator::Trapezoid top = { ta.y, td.y, &ta, &tb, &ta, &td }; - QTessellator::Trapezoid bottom = { tb.y, tc.y, &tb, &tc, &td, &tc }; - addTrap(top); - addTrap(bottom); - - if (tb.y != td.y) { - QTessellator::Trapezoid middle = { td.y, tb.y, &ta, &tb, &td, &tc }; - addTrap(middle); - } - } - } else { - if (ta.y < tc.y) { - QTessellator::Trapezoid top = { tb.y, ta.y, &tb, &tc, &tb, &ta }; - QTessellator::Trapezoid bottom = { tc.y, td.y, &tc, &td, &ta, &td }; - addTrap(top); - addTrap(bottom); - - QTessellator::Trapezoid middle = { ta.y, tc.y, &tb, &tc, &ta, &td }; - addTrap(middle); - } else { - QTessellator::Trapezoid top = { tb.y, tc.y, &tb, &tc, &tb, &ta }; - QTessellator::Trapezoid bottom = { ta.y, td.y, &tc, &td, &ta, &td }; - addTrap(top); - addTrap(bottom); - - if (ta.y != tc.y) { - QTessellator::Trapezoid middle = { tc.y, ta.y, &tc, &td, &tb, &ta }; - addTrap(middle); - } - } - } - } -} - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h b/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h deleted file mode 100644 index ba1d3a971a3..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qtessellator_p.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <QPoint> -#include <QRect> - -QT_BEGIN_NAMESPACE - -class QTessellatorPrivate; - -typedef int Q27Dot5; -#define Q27Dot5ToDouble(i) ((i)/32.) -#define FloatToQ27Dot5(i) (int)((i) * 32) -#define IntToQ27Dot5(i) ((i) << 5) -#define Q27Dot5ToXFixed(i) ((i) << 11) -#define Q27Dot5Factor 32 - -class QTessellator { -public: - QTessellator(); - virtual ~QTessellator(); - - QRectF tessellate(const QPointF *points, int nPoints); - void tessellateConvex(const QPointF *points, int nPoints); - void tessellateRect(const QPointF &a, const QPointF &b, qreal width); - - void setWinding(bool w); - - struct Vertex { - Q27Dot5 x; - Q27Dot5 y; - }; - struct Trapezoid { - Q27Dot5 top; - Q27Dot5 bottom; - const Vertex *topLeft; - const Vertex *bottomLeft; - const Vertex *topRight; - const Vertex *bottomRight; - }; - virtual void addTrap(const Trapezoid &trap) = 0; - -private: - friend class QTessellatorPrivate; - QTessellatorPrivate *d; -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp deleted file mode 100644 index 23155ef2e62..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.cpp +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include <QtCore/qrandom.h> - -#include "qxcbconnection.h" -#include "qcolormap_x11_p.h" -#include "qxcbnativepainting.h" -#include "qt_x11_p.h" - -QT_BEGIN_NAMESPACE - -QXcbX11Data *qt_x11Data = nullptr; - -void qt_xcb_native_x11_info_init(QXcbConnection *conn) -{ - qt_x11Data = new QXcbX11Data; - X11->display = static_cast<Display *>(conn->xlib_display()); - X11->defaultScreen = DefaultScreen(X11->display); - X11->screenCount = ScreenCount(X11->display); - - X11->screens = new QX11InfoData[X11->screenCount]; - X11->argbVisuals = new Visual *[X11->screenCount]; - X11->argbColormaps = new Colormap[X11->screenCount]; - - for (int s = 0; s < X11->screenCount; s++) { - QX11InfoData *screen = X11->screens + s; - //screen->ref = 1; // ensures it doesn't get deleted - screen->screen = s; - - int widthMM = DisplayWidthMM(X11->display, s); - if (widthMM != 0) { - screen->dpiX = (DisplayWidth(X11->display, s) * 254 + widthMM * 5) / (widthMM * 10); - } else { - screen->dpiX = 72; - } - - int heightMM = DisplayHeightMM(X11->display, s); - if (heightMM != 0) { - screen->dpiY = (DisplayHeight(X11->display, s) * 254 + heightMM * 5) / (heightMM * 10); - } else { - screen->dpiY = 72; - } - - X11->argbVisuals[s] = 0; - X11->argbColormaps[s] = 0; - } - - X11->use_xrender = conn->hasXRender() && !qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING_NO_XRENDER"); - -#if QT_CONFIG(xrender) - memset(X11->solid_fills, 0, sizeof(X11->solid_fills)); - for (int i = 0; i < X11->solid_fill_count; ++i) - X11->solid_fills[i].screen = -1; - memset(X11->pattern_fills, 0, sizeof(X11->pattern_fills)); - for (int i = 0; i < X11->pattern_fill_count; ++i) - X11->pattern_fills[i].screen = -1; -#endif - - QXcbColormap::initialize(); - -#if QT_CONFIG(xrender) - if (X11->use_xrender) { - // XRender is supported, let's see if we have a PictFormat for the - // default visual - XRenderPictFormat *format = - XRenderFindVisualFormat(X11->display, - (Visual *) QXcbX11Info::appVisual(X11->defaultScreen)); - - if (!format) { - X11->use_xrender = false; - } - } -#endif // QT_CONFIG(xrender) -} - -QList<XRectangle> qt_region_to_xrectangles(const QRegion &r) -{ - const int numRects = r.rectCount(); - const auto input = r.begin(); - QList<XRectangle> output(numRects); - for (int i = 0; i < numRects; ++i) { - const QRect &in = input[i]; - XRectangle &out = output[i]; - out.x = qMax(SHRT_MIN, in.x()); - out.y = qMax(SHRT_MIN, in.y()); - out.width = qMin((int)USHRT_MAX, in.width()); - out.height = qMin((int)USHRT_MAX, in.height()); - } - return output; -} - -class QXcbX11InfoData : public QSharedData, public QX11InfoData -{}; - -QXcbX11Info::QXcbX11Info() - : d(nullptr) -{} - -QXcbX11Info::~QXcbX11Info() -{} - -QXcbX11Info::QXcbX11Info(const QXcbX11Info &other) - : d(other.d) -{} - -QXcbX11Info &QXcbX11Info::operator=(const QXcbX11Info &other) -{ - d = other.d; - return *this; -} - -QXcbX11Info QXcbX11Info::fromScreen(int screen) -{ - QXcbX11InfoData *xd = new QXcbX11InfoData; - xd->screen = screen; - xd->depth = QXcbX11Info::appDepth(screen); - xd->cells = QXcbX11Info::appCells(screen); - xd->colormap = QXcbX11Info::appColormap(screen); - xd->defaultColormap = QXcbX11Info::appDefaultColormap(screen); - xd->visual = (Visual *)QXcbX11Info::appVisual(screen); - xd->defaultVisual = QXcbX11Info::appDefaultVisual(screen); - - QXcbX11Info info; - info.d = xd; - return info; -} - -void QXcbX11Info::setDepth(int depth) -{ - if (!d) - *this = fromScreen(appScreen()); - - d->depth = depth; -} - -Display *QXcbX11Info::display() -{ - return X11 ? X11->display : 0; -} - -int QXcbX11Info::screen() const -{ - return d ? d->screen : QXcbX11Info::appScreen(); -} - -int QXcbX11Info::depth() const -{ - return d ? d->depth : QXcbX11Info::appDepth(); -} - -Colormap QXcbX11Info::colormap() const -{ - return d ? d->colormap : QXcbX11Info::appColormap(); -} - -void *QXcbX11Info::visual() const -{ - return d ? d->visual : QXcbX11Info::appVisual(); -} - -void QXcbX11Info::setVisual(void *visual) -{ - if (!d) - *this = fromScreen(appScreen()); - - d->visual = (Visual *) visual; -} - -int QXcbX11Info::appScreen() -{ - return X11 ? X11->defaultScreen : 0; -} - -int QXcbX11Info::appDepth(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].depth : 32; -} - -int QXcbX11Info::appCells(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].cells : 0; -} - -Colormap QXcbX11Info::appColormap(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].colormap : 0; -} - -void *QXcbX11Info::appVisual(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].visual : 0; -} - -Window QXcbX11Info::appRootWindow(int screen) -{ - return X11 ? RootWindow(X11->display, screen == -1 ? X11->defaultScreen : screen) : 0; -} - -bool QXcbX11Info::appDefaultColormap(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultColormap : true; -} - -bool QXcbX11Info::appDefaultVisual(int screen) -{ - return X11 ? X11->screens[screen == -1 ? X11->defaultScreen : screen].defaultVisual : true; -} - -int QXcbX11Info::appDpiX(int screen) -{ - if (!X11) - return 75; - if (screen < 0) - screen = X11->defaultScreen; - if (screen > X11->screenCount) - return 0; - return X11->screens[screen].dpiX; -} - -int QXcbX11Info::appDpiY(int screen) -{ - if (!X11) - return 75; - if (screen < 0) - screen = X11->defaultScreen; - if (screen > X11->screenCount) - return 0; - return X11->screens[screen].dpiY; -} - -#if QT_CONFIG(xrender) -Picture QXcbX11Data::getSolidFill(int screen, const QColor &c) -{ - if (!X11->use_xrender) - return XNone; - - XRenderColor color = preMultiply(c); - for (int i = 0; i < X11->solid_fill_count; ++i) { - if (X11->solid_fills[i].screen == screen - && X11->solid_fills[i].color.alpha == color.alpha - && X11->solid_fills[i].color.red == color.red - && X11->solid_fills[i].color.green == color.green - && X11->solid_fills[i].color.blue == color.blue) - return X11->solid_fills[i].picture; - } - // none found, replace one - int i = QRandomGenerator::global()->generate() % 16; - - if (X11->solid_fills[i].screen != screen && X11->solid_fills[i].picture) { - XRenderFreePicture (X11->display, X11->solid_fills[i].picture); - X11->solid_fills[i].picture = 0; - } - - if (!X11->solid_fills[i].picture) { - Pixmap pixmap = XCreatePixmap (X11->display, RootWindow (X11->display, screen), 1, 1, 32); - XRenderPictureAttributes attrs; - attrs.repeat = True; - X11->solid_fills[i].picture = XRenderCreatePicture (X11->display, pixmap, - XRenderFindStandardFormat(X11->display, PictStandardARGB32), - CPRepeat, &attrs); - XFreePixmap (X11->display, pixmap); - } - - X11->solid_fills[i].color = color; - X11->solid_fills[i].screen = screen; - XRenderFillRectangle (X11->display, PictOpSrc, X11->solid_fills[i].picture, &color, 0, 0, 1, 1); - return X11->solid_fills[i].picture; -} - -XRenderColor QXcbX11Data::preMultiply(const QColor &c) -{ - XRenderColor color; - const uint A = c.alpha(), - R = c.red(), - G = c.green(), - B = c.blue(); - color.alpha = (A | A << 8); - color.red = (R | R << 8) * color.alpha / 0x10000; - color.green = (G | G << 8) * color.alpha / 0x10000; - color.blue = (B | B << 8) * color.alpha / 0x10000; - return color; -} -#endif // QT_CONFIG(xrender) - - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h b/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h deleted file mode 100644 index dac9b101c43..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qxcbnativepainting.h +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#pragma once - -#include <QSharedDataPointer> -#include "qt_x11_p.h" - -typedef struct _FcPattern FcPattern; -typedef unsigned long XID; -typedef XID Colormap; -typedef XID Window; -typedef struct _XDisplay Display; - -QT_BEGIN_NAMESPACE - -class QXcbConnection; -class QPixmap; - -void qt_xcb_native_x11_info_init(QXcbConnection *conn); -QList<XRectangle> qt_region_to_xrectangles(const QRegion &r); - -class QXcbX11InfoData; -class QXcbX11Info -{ -public: - QXcbX11Info(); - ~QXcbX11Info(); - QXcbX11Info(const QXcbX11Info &other); - QXcbX11Info &operator=(const QXcbX11Info &other); - - static QXcbX11Info fromScreen(int screen); - static Display *display(); - - int depth() const; - void setDepth(int depth); - - int screen() const; - Colormap colormap() const; - - void *visual() const; - void setVisual(void *visual); - - static int appScreen(); - static int appDepth(int screen = -1); - static int appCells(int screen = -1); - static Colormap appColormap(int screen = -1); - static void *appVisual(int screen = -1); - static Window appRootWindow(int screen = -1); - static bool appDefaultColormap(int screen = -1); - static bool appDefaultVisual(int screen = -1); - static int appDpiX(int screen = -1); - static int appDpiY(int screen = -1); - -private: - QSharedDataPointer<QXcbX11InfoData> d; - - friend class QX11PaintEngine; - friend class QX11PlatformPixmap; - friend void qt_x11SetScreen(QPixmap &pixmap, int screen); -}; - -QT_END_NAMESPACE diff --git a/src/plugins/platforms/xcb/qxcbimage.cpp b/src/plugins/platforms/xcb/qxcbimage.cpp index 5b78c2c08ab..4c978152975 100644 --- a/src/plugins/platforms/xcb/qxcbimage.cpp +++ b/src/plugins/platforms/xcb/qxcbimage.cpp @@ -80,12 +80,6 @@ bool qt_xcb_imageFormatForVisual(QXcbConnection *connection, uint8_t depth, cons *imageFormat = QImage::Format_Grayscale8; return true; } -#if QT_CONFIG(xcb_native_painting) - if (QXcbIntegration::instance() && QXcbIntegration::instance()->nativePaintingEnabled()) { - *imageFormat = QImage::Format_Indexed8; - return true; - } -#endif return false; } diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp index c5c1fb1e638..d3a31dd35c7 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.cpp +++ b/src/plugins/platforms/xcb/qxcbintegration.cpp @@ -37,11 +37,6 @@ #include <X11/Xlib.h> #undef register #endif -#if QT_CONFIG(xcb_native_painting) -#include "qxcbnativepainting.h" -#include "qpixmap_x11_p.h" -#include "qbackingstore_x11_p.h" -#endif #include <qpa/qplatforminputcontextfactory_p.h> #include <private/qgenericunixtheme_p.h> @@ -180,13 +175,6 @@ QXcbIntegration::QXcbIntegration(const QStringList ¶meters, int &argc, char m_services->setConnection(m_connection); m_fontDatabase.reset(new QGenericUnixFontDatabase()); - -#if QT_CONFIG(xcb_native_painting) - if (nativePaintingEnabled()) { - qCDebug(lcQpaXcb, "QXCB USING NATIVE PAINTING"); - qt_xcb_native_x11_info_init(connection()); - } -#endif } QXcbIntegration::~QXcbIntegration() @@ -198,11 +186,6 @@ QXcbIntegration::~QXcbIntegration() QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelType type) const { -#if QT_CONFIG(xcb_native_painting) - if (nativePaintingEnabled()) - return new QX11PlatformPixmap(type); -#endif - return QPlatformIntegration::createPlatformPixmap(type); } @@ -281,10 +264,6 @@ QPlatformBackingStore *QXcbIntegration::createPlatformBackingStore(QWindow *wind const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window); if (isTrayIconWindow) { backingStore = new QXcbSystemTrayBackingStore(window); -#if QT_CONFIG(xcb_native_painting) - } else if (nativePaintingEnabled()) { - backingStore = new QXcbNativeBackingStore(window); -#endif } else { backingStore = new QXcbBackingStore(window); } @@ -576,16 +555,6 @@ void QXcbIntegration::beep() const xcb_flush(connection); } -bool QXcbIntegration::nativePaintingEnabled() const -{ -#if QT_CONFIG(xcb_native_painting) - static bool enabled = qEnvironmentVariableIsSet("QT_XCB_NATIVE_PAINTING"); - return enabled; -#else - return false; -#endif -} - #if QT_CONFIG(vulkan) QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const { diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h index 10cc67af55f..041908e4552 100644 --- a/src/plugins/platforms/xcb/qxcbintegration.h +++ b/src/plugins/platforms/xcb/qxcbintegration.h @@ -94,8 +94,6 @@ public: void beep() const override; - bool nativePaintingEnabled() const; - #if QT_CONFIG(vulkan) QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override; #endif diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt index 6ca4b120ccf..827cae9530b 100644 --- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt +++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt @@ -74,12 +74,12 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_largefile SQLITE_DISABLE_LFS ) -qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_r +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_r AND NOT QT_FEATURE_system_sqlite DEFINES HAVE_LOCALTIME_R=1 ) -qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_s +qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_localtime_s AND NOT QT_FEATURE_system_sqlite DEFINES HAVE_LOCALTIME_S=1 ) diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index f79dea194e8..c8cd7c26f61 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -109,6 +109,7 @@ inline ControlState calcControlState(const QStyleOption *option) #define More u"\uE712"_s #define Help u"\uE897"_s +#define Clear u"\uE894"_s template <typename R, typename P, typename B> static inline void drawRoundedRect(QPainter *p, R &&rect, P &&pen, B &&brush) @@ -1045,12 +1046,15 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption if (rect.width() <= 0) break; - painter->setPen(Qt::NoPen); - if (vopt->features & QStyleOptionViewItem::Alternate) - painter->setBrush(vopt->palette.alternateBase()); - else - painter->setBrush(vopt->palette.base()); - painter->drawRect(rect); + if (vopt->features & QStyleOptionViewItem::Alternate) { + QPalette::ColorGroup cg = + (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)) + ? QPalette::Normal + : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + painter->fillRect(rect, option->palette.brush(cg, QPalette::AlternateBase)); + } if (option->state & State_Selected && !highContrastTheme) { // keep in sync with CE_ItemViewItem QListView indicator painting @@ -1717,9 +1721,10 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op } case CE_ItemViewItem: { if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) { - QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget); - QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, widget); - QRect textRect = proxy()->subElementRect(SE_ItemViewItemText, vopt, widget); + const auto p = proxy(); + QRect checkRect = p->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget); + QRect iconRect = p->subElementRect(SE_ItemViewItemDecoration, vopt, widget); + QRect textRect = p->subElementRect(SE_ItemViewItemText, vopt, widget); // draw the background proxy()->drawPrimitive(PE_PanelItemViewItem, option, painter, widget); @@ -1820,16 +1825,17 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op d->viewItemDrawText(painter, vopt, textRect); // paint a vertical marker for QListView - if (vopt->state & State_Selected) { + if (vopt->state & State_Selected && !highContrastTheme) { if (const QListView *lv = qobject_cast<const QListView *>(widget); - lv && lv->viewMode() != QListView::IconMode && !highContrastTheme) { - painter->setPen(vopt->palette.accent().color()); - const auto xPos = isRtl ? rect.right() - 1 : rect.left(); - const QLineF lines[2] = { - QLineF(xPos, rect.y() + 2, xPos, rect.y() + rect.height() - 2), - QLineF(xPos + 1, rect.y() + 2, xPos + 1, rect.y() + rect.height() - 2), - }; - painter->drawLines(lines, 2); + lv && lv->viewMode() != QListView::IconMode) { + const auto col = vopt->palette.accent().color(); + painter->setBrush(col); + painter->setPen(col); + const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f; + const auto yOfs = rect.height() / 4.; + QRectF r(QPointF(xPos, rect.y() + yOfs), + QPointF(xPos + 1, rect.y() + rect.height() - yOfs)); + painter->drawRoundedRect(r, 1, 1); } } } @@ -1869,7 +1875,7 @@ QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOp case QStyle::SE_RadioButtonIndicator: case QStyle::SE_CheckBoxIndicator: ret = QWindowsVistaStyle::subElementRect(element, option, widget); - ret.moveLeft(contentItemHMargin); + ret.moveLeft(ret.left() + contentItemHMargin); break; case QStyle::SE_ComboBoxFocusRect: case QStyle::SE_CheckBoxFocusRect: @@ -1880,22 +1886,29 @@ QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOp case QStyle::SE_LineEditContents: ret = option->rect.adjusted(4,0,-4,0); break; - case QStyle::SE_ItemViewItemText: - if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(option)) { - const int decorationOffset = item->features.testFlag(QStyleOptionViewItem::HasDecoration) ? item->decorationSize.width() : 0; - const int checkboxOffset = item->features.testFlag(QStyleOptionViewItem::HasCheckIndicator) ? 16 : 0; - if (widget && qobject_cast<QComboBoxPrivateContainer *>(widget->parentWidget())) { - if (option->direction == Qt::LeftToRight) - ret = option->rect.adjusted(decorationOffset + checkboxOffset + 5, 0, -5, 0); - else - ret = option->rect.adjusted(5, 0, decorationOffset - checkboxOffset - 5, 0); + case SE_ItemViewItemCheckIndicator: + case SE_ItemViewItemDecoration: + case SE_ItemViewItemText: { + ret = QWindowsVistaStyle::subElementRect(element, option, widget); + if (!ret.isValid() || highContrastTheme) + return ret; + + if (const QListView *lv = qobject_cast<const QListView *>(widget); + lv && lv->viewMode() != QListView::IconMode) { + const int xOfs = contentHMargin; + const bool isRtl = option->direction == Qt::RightToLeft; + if (isRtl) { + ret.moveRight(ret.right() - xOfs); + if (ret.left() < option->rect.left()) + ret.setLeft(option->rect.left()); } else { - ret = QWindowsVistaStyle::subElementRect(element, option, widget); + ret.moveLeft(ret.left() + xOfs); + if (ret.right() > option->rect.right()) + ret.setRight(option->rect.right()); } - } else { - ret = QWindowsVistaStyle::subElementRect(element, option, widget); } break; + } #if QT_CONFIG(progressbar) case SE_ProgressBarGroove: case SE_ProgressBarContents: @@ -2088,6 +2101,19 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption } break; } +#if QT_CONFIG(groupbox) + case CC_GroupBox: { + ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget); + switch (subControl) { + case SC_GroupBoxCheckBox: + ret.moveTop(1); + break; + default: + break; + } + break; + } +#endif // QT_CONFIG(groupbox) default: ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget); } @@ -2231,6 +2257,25 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o contentSize.rwidth() += 2 * contentHMargin - oldMargin; break; } + case CT_ItemViewItem: { + if (const auto *viewItemOpt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) { + if (const QListView *lv = qobject_cast<const QListView *>(widget); + lv && lv->viewMode() != QListView::IconMode) { + QStyleOptionViewItem vOpt(*viewItemOpt); + // viewItemSize only takes PM_FocusFrameHMargin into account but no additional + // margin, therefore adjust it here for a correct width during layouting when + // WrapText is enabled + vOpt.rect.setRight(vOpt.rect.right() - contentHMargin); + contentSize = QWindowsVistaStyle::sizeFromContents(type, &vOpt, size, widget); + contentSize.rwidth() += contentHMargin; + contentSize.rheight() += 2 * contentHMargin; + + } else { + contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget); + } + } + break; + } default: contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget); break; @@ -2541,6 +2586,7 @@ void QWindows11Style::polish(QPalette& result) d->m_titleBarCloseIcon = QIcon(); d->m_titleBarNormalIcon = QIcon(); d->m_toolbarExtensionButton = QIcon(); + d->m_lineEditClearButton = QIcon(); } QPixmap QWindows11Style::standardPixmap(StandardPixmap standardPixmap, @@ -2565,10 +2611,17 @@ QIcon QWindows11Style::standardIcon(StandardPixmap standardIcon, { auto *d = const_cast<QWindows11StylePrivate*>(d_func()); switch (standardIcon) { + case SP_LineEditClearButton: { + if (d->m_lineEditClearButton.isNull()) { + auto e = new WinFontIconEngine(Clear.at(0), d->assetFont); + d->m_lineEditClearButton = QIcon(e); + } + return d->m_lineEditClearButton; + } case SP_ToolBarHorizontalExtensionButton: case SP_ToolBarVerticalExtensionButton: { if (d->m_toolbarExtensionButton.isNull()) { - auto e = new WinFontIconEngine(More.at(0), d->assetFont); + auto e = new WinFontIconEngine(More, d->assetFont); e->setScale(1.0); d->m_toolbarExtensionButton = QIcon(e); } diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h index 736caae956c..a51a93ddd9b 100644 --- a/src/plugins/styles/modernwindows/qwindows11style_p.h +++ b/src/plugins/styles/modernwindows/qwindows11style_p.h @@ -123,6 +123,7 @@ class QWindows11StylePrivate : public QWindowsVistaStylePrivate { protected: QIcon m_toolbarExtensionButton; + QIcon m_lineEditClearButton; }; QT_END_NAMESPACE diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp index 22ca18b10bf..7389635108b 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -1716,7 +1716,9 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt QWindowsThemeData popupbackgroundTheme(widget, painter, QWindowsVistaStylePrivate::MenuTheme, MENU_POPUPBACKGROUND, stateId, option->rect); d->drawBackground(popupbackgroundTheme); + return; } + break; case PE_PanelMenuBar: break; @@ -4959,8 +4961,7 @@ QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon, return QWindowsStyle::standardIcon(standardIcon, option, widget); } - -WinFontIconEngine::WinFontIconEngine(const QChar &glyph, const QFont &font) +WinFontIconEngine::WinFontIconEngine(const QString &glyph, const QFont &font) : QFontIconEngine({}, font) , m_font(font) , m_glyph(glyph) diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h index cf982ceb133..23b34547faa 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h @@ -184,7 +184,7 @@ private: class WinFontIconEngine : public QFontIconEngine { public: - WinFontIconEngine(const QChar &glyph, const QFont &font); + WinFontIconEngine(const QString &glyph, const QFont &font); QString key() const override; QIconEngine *clone() const override; @@ -194,7 +194,7 @@ public: protected: QFont m_font; - QChar m_glyph; + QString m_glyph; double m_scale = 0.7; }; diff --git a/src/testlib/qtestcase.cpp b/src/testlib/qtestcase.cpp index 784e69d2486..a98f5da2389 100644 --- a/src/testlib/qtestcase.cpp +++ b/src/testlib/qtestcase.cpp @@ -410,7 +410,7 @@ public: static QMetaMethod findMethod(const QObject *obj, const char *signature); private: - bool invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const; + void invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const; void invokeTestOnData(int index) const; QMetaMethod m_initTestCaseMethod; // might not exist, check isValid(). @@ -1315,11 +1315,8 @@ static void printUnknownDataTagError(QLatin1StringView name, QLatin1StringView t Call slot_data(), init(), slot(), cleanup(), init(), slot(), cleanup(), ... If data is set then it is the only test that is performed - - If the function was successfully called, true is returned, otherwise - false. */ -bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const +void TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<WatchDog> &watchDog) const { QBenchmarkTestMethodData benchmarkData; QBenchmarkTestMethodData::current = &benchmarkData; @@ -1422,8 +1419,6 @@ bool TestMethods::invokeTest(int index, QLatin1StringView tag, std::optional<Wat QTestResult::finishedCurrentTestFunction(); QTestResult::setSkipCurrentTest(false); QTestResult::setBlacklistCurrentTest(false); - - return true; } void *fetchData(QTestData *data, const char *tagName, int typeId) @@ -1743,10 +1738,8 @@ void TestMethods::invokeTests(QObject *testObject) const const char *data = nullptr; if (i < QTest::testTags.size() && !QTest::testTags.at(i).isEmpty()) data = qstrdup(QTest::testTags.at(i).toLatin1().constData()); - const bool ok = invokeTest(i, QLatin1StringView(data), watchDog); + invokeTest(i, QLatin1StringView(data), watchDog); delete [] data; - if (!ok) - break; } } diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp index e35330ebeb3..6cb935ef47d 100644 --- a/src/tools/windeployqt/main.cpp +++ b/src/tools/windeployqt/main.cpp @@ -592,15 +592,17 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse } // default to deployment of compiler runtime for windows desktop configurations - if (options->platform == WindowsDesktopMinGW || options->platform.testFlags(WindowsDesktopMsvc) - || parser->isSet(compilerRunTimeOption)) + if (options->platform == WindowsDesktopMinGW || options->platform == WindowsDesktopClangMinGW + || options->platform.testFlags(WindowsDesktopMsvc) || parser->isSet(compilerRunTimeOption)) options->compilerRunTime = true; if (parser->isSet(noCompilerRunTimeOption)) options->compilerRunTime = false; if (options->compilerRunTime && options->platform != WindowsDesktopMinGW + && options->platform != WindowsDesktopClangMinGW && !options->platform.testFlags(WindowsDesktopMsvc)) { - *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only."); + *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for " + "Desktop MSVC and MinGW (g++ and Clang) only."); return CommandLineParseError; } diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp index fc969e17380..7e3dfbd68db 100644 --- a/src/widgets/accessible/itemviews.cpp +++ b/src/widgets/accessible/itemviews.cpp @@ -60,7 +60,7 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const } QAccessibleTable::QAccessibleTable(QWidget *w) - : QAccessibleObject(w) + : QAccessibleWidgetV2(w) { Q_ASSERT(view()); @@ -589,13 +589,6 @@ int QAccessibleTable::indexOfChild(const QAccessibleInterface *iface) const return -1; } -QString QAccessibleTable::text(QAccessible::Text t) const -{ - if (t == QAccessible::Description) - return view()->accessibleDescription(); - return view()->accessibleName(); -} - QRect QAccessibleTable::rect() const { if (!view()->isVisible()) @@ -677,7 +670,7 @@ void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t) return static_cast<QAccessibleSelectionInterface*>(this); if (t == QAccessible::TableInterface) return static_cast<QAccessibleTableInterface*>(this); - return nullptr; + return QAccessibleWidgetV2::interface_cast(t); } void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event) diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h index 9b30f36ced3..e74be151115 100644 --- a/src/widgets/accessible/itemviews_p.h +++ b/src/widgets/accessible/itemviews_p.h @@ -31,7 +31,9 @@ QT_BEGIN_NAMESPACE class QAccessibleTableCell; class QAccessibleTableHeaderCell; -class QAccessibleTable :public QAccessibleTableInterface, public QAccessibleSelectionInterface, public QAccessibleObject +class QAccessibleTable : public QAccessibleTableInterface, + public QAccessibleSelectionInterface, + public QAccessibleWidgetV2 { public: explicit QAccessibleTable(QWidget *w); @@ -39,7 +41,6 @@ public: QAccessible::Role role() const override; QAccessible::State state() const override; - QString text(QAccessible::Text t) const override; QRect rect() const override; QAccessibleInterface *childAt(int x, int y) const override; diff --git a/src/widgets/accessible/rangecontrols.cpp b/src/widgets/accessible/rangecontrols.cpp index c0de5357c9a..1f7b20833dd 100644 --- a/src/widgets/accessible/rangecontrols.cpp +++ b/src/widgets/accessible/rangecontrols.cpp @@ -65,6 +65,16 @@ QAccessibleInterface *QAccessibleAbstractSpinBox::lineEditIface() const #endif } +QAccessible::State QAccessibleAbstractSpinBox::state() const +{ + QAccessible::State state = QAccessibleWidgetV2::state(); + if (abstractSpinBox()->isReadOnly()) + state.readOnly = true; + else + state.editable = true; + return state; +} + QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const { if (t == QAccessible::Value) diff --git a/src/widgets/accessible/rangecontrols_p.h b/src/widgets/accessible/rangecontrols_p.h index dd5a6a4531c..5a023d2f00b 100644 --- a/src/widgets/accessible/rangecontrols_p.h +++ b/src/widgets/accessible/rangecontrols_p.h @@ -42,6 +42,7 @@ public: explicit QAccessibleAbstractSpinBox(QWidget *w); virtual ~QAccessibleAbstractSpinBox(); + QAccessible::State state() const override; QString text(QAccessible::Text t) const override; void *interface_cast(QAccessible::InterfaceType t) override; diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp index 25d742039c5..f8125204045 100644 --- a/src/widgets/dialogs/qcolordialog.cpp +++ b/src/widgets/dialogs/qcolordialog.cpp @@ -585,18 +585,21 @@ signals: protected: QSize sizeHint() const override; void paintEvent(QPaintEvent*) override; + void keyPressEvent(QKeyEvent *event) override; void mouseMoveEvent(QMouseEvent *) override; void mousePressEvent(QMouseEvent *) override; void resizeEvent(QResizeEvent *) override; private: - int hue; - int sat; + QPoint m_pos; - QPoint colPt(); - int huePt(const QPoint &pt); - int satPt(const QPoint &pt); - void setCol(const QPoint &pt); + QPixmap createColorsPixmap(); + QPoint colPt(int hue, int sat); + int huePt(const QPoint &pt, const QSize &widgetSize); + int huePt(const QPoint &pt) { return huePt(pt, size()); } + int satPt(const QPoint &pt, const QSize &widgetSize); + int satPt(const QPoint &pt) { return satPt(pt, size()); } + void setCol(const QPoint &pt, bool notify = true); QPixmap pix; bool crossVisible; @@ -625,6 +628,7 @@ signals: protected: void paintEvent(QPaintEvent*) override; + void keyPressEvent(QKeyEvent *event) override; void mouseMoveEvent(QMouseEvent *) override; void mousePressEvent(QMouseEvent *) override; @@ -660,6 +664,7 @@ QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) hue = 100; val = 100; sat = 100; pix = nullptr; // setAttribute(WA_NoErase, true); + setFocusPolicy(Qt::StrongFocus); } QColorLuminancePicker::~QColorLuminancePicker() @@ -667,6 +672,21 @@ QColorLuminancePicker::~QColorLuminancePicker() delete pix; } +void QColorLuminancePicker::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Down: + setVal(std::clamp(val - 1, 0, 255)); + break; + case Qt::Key_Up: + setVal(std::clamp(val + 1, 0, 255)); + break; + default: + QWidget::keyPressEvent(event); + break; + } +} + void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) { if (m->buttons() == Qt::NoButton) { @@ -737,38 +757,53 @@ void QColorLuminancePicker::setCol(int h, int s , int v) repaint(); } -QPoint QColorPicker::colPt() +QPoint QColorPicker::colPt(int hue, int sat) { QRect r = contentsRect(); return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255); } -int QColorPicker::huePt(const QPoint &pt) +int QColorPicker::huePt(const QPoint &pt, const QSize &widgetSize) { - QRect r = contentsRect(); - return 360 - pt.x() * 360 / (r.width() - 1); + QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins(); + return std::clamp(360 - pt.x() * 360 / (r.width() - 1), 0, 359); } -int QColorPicker::satPt(const QPoint &pt) +int QColorPicker::satPt(const QPoint &pt, const QSize &widgetSize) { - QRect r = contentsRect(); - return 255 - pt.y() * 255 / (r.height() - 1); + QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins(); + return std::clamp(255 - pt.y() * 255 / (r.height() - 1), 0, 255); } -void QColorPicker::setCol(const QPoint &pt) +void QColorPicker::setCol(const QPoint &pt, bool notify) { - setCol(huePt(pt), satPt(pt)); + if (pt == m_pos) + return; + + QRect r(m_pos, QSize(20, 20)); + m_pos.setX(std::clamp(pt.x(), 0, pix.width() - 1)); + m_pos.setY(std::clamp(pt.y(), 0, pix.height() - 1)); + r = r.united(QRect(m_pos, QSize(20, 20))); + r.translate(contentsRect().x() - 9, contentsRect().y() - 9); + // update(r); + repaint(r); + + if (notify) + emit newCol(huePt(m_pos), satPt(m_pos)); } QColorPicker::QColorPicker(QWidget* parent) : QFrame(parent) , crossVisible(true) { - hue = 0; sat = 0; - setCol(150, 255); - setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) ); + adjustSize(); + + pix = createColorsPixmap(); + + setCol(150, 255); } QColorPicker::~QColorPicker() @@ -792,15 +827,31 @@ void QColorPicker::setCol(int h, int s) { int nhue = qMin(qMax(0,h), 359); int nsat = qMin(qMax(0,s), 255); - if (nhue == hue && nsat == sat) + if (nhue == huePt(m_pos) && nsat == satPt(m_pos)) return; - QRect r(colPt(), QSize(20,20)); - hue = nhue; sat = nsat; - r = r.united(QRect(colPt(), QSize(20,20))); - r.translate(contentsRect().x()-9, contentsRect().y()-9); - // update(r); - repaint(r); + setCol(colPt(nhue, nsat), false); +} + +void QColorPicker::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Down: + setCol(m_pos + QPoint(0, 1)); + break; + case Qt::Key_Left: + setCol(m_pos + QPoint(-1, 0)); + break; + case Qt::Key_Right: + setCol(m_pos + QPoint(1, 0)); + break; + case Qt::Key_Up: + setCol(m_pos + QPoint(0, -1)); + break; + default: + QFrame::keyPressEvent(event); + break; + } } void QColorPicker::mouseMoveEvent(QMouseEvent *m) @@ -811,14 +862,12 @@ void QColorPicker::mouseMoveEvent(QMouseEvent *m) return; } setCol(p); - emit newCol(hue, sat); } void QColorPicker::mousePressEvent(QMouseEvent *m) { QPoint p = m->position().toPoint() - contentsRect().topLeft(); setCol(p); - emit newCol(hue, sat); } void QColorPicker::paintEvent(QPaintEvent* ) @@ -830,7 +879,7 @@ void QColorPicker::paintEvent(QPaintEvent* ) p.drawPixmap(r.topLeft(), pix); if (crossVisible) { - QPoint pt = colPt() + r.topLeft(); + QPoint pt = m_pos + r.topLeft(); p.setPen(Qt::black); p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black); p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black); @@ -841,6 +890,21 @@ void QColorPicker::resizeEvent(QResizeEvent *ev) { QFrame::resizeEvent(ev); + pix = createColorsPixmap(); + + const QSize &oldSize = ev->oldSize(); + if (!oldSize.isValid()) + return; + + // calculate hue/saturation based on previous widget size + // and update position accordingly + const int hue = huePt(m_pos, oldSize); + const int sat = satPt(m_pos, oldSize); + setCol(hue, sat); +} + +QPixmap QColorPicker::createColorsPixmap() +{ int w = width() - frameWidth() * 2; int h = height() - frameWidth() * 2; QImage img(w, h, QImage::Format_RGB32); @@ -858,10 +922,9 @@ void QColorPicker::resizeEvent(QResizeEvent *ev) ++x; } } - pix = QPixmap::fromImage(img); + return QPixmap::fromImage(img); } - class QColSpinBox : public QSpinBox { public: diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index 1ed9dd06e3c..11cbbabe25a 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -3764,6 +3764,9 @@ void QFileDialogPrivate::enterDirectory(const QModelIndex &index) QModelIndex sourceIndex = index.model() == proxyModel ? mapToSource(index) : index; QString path = sourceIndex.data(QFileSystemModel::FilePathRole).toString(); if (path.isEmpty() || model->isDir(sourceIndex)) { + if (q->directory().path() == path) + return; + const QFileDialog::FileMode fileMode = q->fileMode(); q->setDirectory(path); emit q->directoryEntered(path); diff --git a/src/widgets/dialogs/qfiledialog.ui b/src/widgets/dialogs/qfiledialog.ui index f275e20c633..97f39fa6194 100644 --- a/src/widgets/dialogs/qfiledialog.ui +++ b/src/widgets/dialogs/qfiledialog.ui @@ -20,7 +20,10 @@ <item row="0" column="0"> <widget class="QLabel" name="lookInLabel"> <property name="text"> - <string>Look in:</string> + <string>&Look in:</string> + </property> + <property name="buddy"> + <cstring>lookInCombo</cstring> </property> </widget> </item> @@ -284,7 +287,10 @@ </sizepolicy> </property> <property name="text"> - <string>Files of type:</string> + <string>Files of &type:</string> + </property> + <property name="buddy"> + <cstring>fileTypeCombo</cstring> </property> </widget> </item> diff --git a/src/widgets/dialogs/qfontdialog.cpp b/src/widgets/dialogs/qfontdialog.cpp index 870b0c40faf..c4f8af1a639 100644 --- a/src/widgets/dialogs/qfontdialog.cpp +++ b/src/widgets/dialogs/qfontdialog.cpp @@ -172,7 +172,7 @@ void QFontDialogPrivate::init() sizeAccel = new QLabel(q); #ifndef QT_NO_SHORTCUT - sizeAccel->setBuddy(sizeEdit); + sizeAccel->setBuddy(sizeList); #endif sizeAccel->setIndent(2); diff --git a/src/widgets/dialogs/qsidebar.cpp b/src/widgets/dialogs/qsidebar.cpp index c8498bc56bf..a357d34f327 100644 --- a/src/widgets/dialogs/qsidebar.cpp +++ b/src/widgets/dialogs/qsidebar.cpp @@ -413,7 +413,9 @@ void QSidebar::selectUrl(const QUrl &url) selectionModel()->clear(); for (int i = 0; i < model()->rowCount(); ++i) { if (model()->index(i, 0).data(QUrlModel::UrlRole).toUrl() == url) { - selectionModel()->select(model()->index(i, 0), QItemSelectionModel::Select); + emit goToUrl(url); + selectionModel()->setCurrentIndex(model()->index(i, 0), + QItemSelectionModel::SelectCurrent); break; } } @@ -468,7 +470,6 @@ void QSidebar::removeEntry() void QSidebar::clicked(const QModelIndex &index) { QUrl url = model()->index(index.row(), 0).data(QUrlModel::UrlRole).toUrl(); - emit goToUrl(url); selectUrl(url); } diff --git a/src/widgets/kernel/qlayout.cpp b/src/widgets/kernel/qlayout.cpp index 00f9766af29..5ba92714f6c 100644 --- a/src/widgets/kernel/qlayout.cpp +++ b/src/widgets/kernel/qlayout.cpp @@ -345,6 +345,17 @@ void QLayout::getContentsMargins(int *left, int *top, int *right, int *bottom) c } /*! + \property QLayout::contentsMargins + \since 4.6 + \brief the margins used around the layout + + By default, QLayout uses the values provided by the style. On + most platforms, the margin is 11 pixels in all directions. + + \sa setContentsMargins(), getContentsMargins() +*/ + +/*! \since 4.6 Returns the margins used around the layout. diff --git a/src/widgets/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index c4b78539114..bb9f7ab27fc 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -5,15 +5,12 @@ #include "qcommonstyle.h" #include "qcommonstyle_p.h" -#include <qfile.h> #if QT_CONFIG(itemviews) #include <qabstractitemview.h> #endif #include <qapplication.h> #include <private/qguiapplication_p.h> #include <qpa/qplatformtheme.h> -#include <qbitmap.h> -#include <qcache.h> #if QT_CONFIG(dockwidget) #include <qdockwidget.h> #endif @@ -61,7 +58,6 @@ #endif #include <private/qcommonstylepixmaps_p.h> #include <private/qmath_p.h> -#include <qdebug.h> #include <qtextformat.h> #if QT_CONFIG(wizard) #include <qwizard.h> @@ -69,11 +65,6 @@ #if QT_CONFIG(filedialog) #include <qsidebar_p.h> #endif -#include <qfileinfo.h> -#include <qdir.h> -#if QT_CONFIG(settings) -#include <qsettings.h> -#endif #include <qvariant.h> #include <qpixmapcache.h> #if QT_CONFIG(animation) @@ -6199,17 +6190,17 @@ QPixmap QCommonStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &p return QPixmap::fromImage(std::move(im)); } case QIcon::Selected: { - QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); QColor color = opt->palette.color(QPalette::Normal, QPalette::Highlight); color.setAlphaF(0.3f); - QPainter painter(&img); + QPixmap ret(pixmap); + QPainter painter(&ret); painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); - painter.fillRect(0, 0, img.width(), img.height(), color); + painter.fillRect(0, 0, pixmap.width(), pixmap.height(), color); painter.end(); - return QPixmap::fromImage(std::move(img)); } + return ret; + } case QIcon::Active: - return pixmap; - default: + case QIcon::Normal: break; } return pixmap; diff --git a/src/widgets/styles/qfusionstyle.cpp b/src/widgets/styles/qfusionstyle.cpp index 33777933495..ae5894661a2 100644 --- a/src/widgets/styles/qfusionstyle.cpp +++ b/src/widgets/styles/qfusionstyle.cpp @@ -2984,8 +2984,6 @@ void QFusionStyle::polish(QWidget *widget) #if QT_CONFIG(spinbox) || qobject_cast<QAbstractSpinBox *>(widget) #endif - || (widget->inherits("QDockSeparator")) - || (widget->inherits("QDockWidgetSeparator")) ) { widget->setAttribute(Qt::WA_Hover, true); widget->setAttribute(Qt::WA_OpaquePaintEvent, false); @@ -3028,8 +3026,6 @@ void QFusionStyle::unpolish(QWidget *widget) #if QT_CONFIG(spinbox) || qobject_cast<QAbstractSpinBox *>(widget) #endif - || (widget->inherits("QDockSeparator")) - || (widget->inherits("QDockWidgetSeparator")) ) { widget->setAttribute(Qt::WA_Hover, false); } diff --git a/src/widgets/styles/qstylesheetstyle_default.cpp b/src/widgets/styles/qstylesheetstyle_default.cpp index 73e7c9524d6..ae6b10560fe 100644 --- a/src/widgets/styles/qstylesheetstyle_default.cpp +++ b/src/widgets/styles/qstylesheetstyle_default.cpp @@ -306,7 +306,7 @@ StyleSheet QStyleSheetStyle::getDefaultStyleSheet() const { -qt-background-role: button; }*/ - if (baseStyle()->inherits("QPlastiqueStyle") || baseStyle()->inherits("QCleanlooksStyle") || baseStyle()->inherits("QFusionStyle")) + if (baseStyle()->inherits("QFusionStyle")) { SET_ELEMENT_NAME("QComboBox"_L1); ADD_ATTRIBUTE_SELECTOR("readOnly"_L1, "true"_L1, AttributeSelector::MatchEqual); diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp index b9143a59ee7..bea7593ef77 100644 --- a/src/widgets/styles/qwindowsstyle.cpp +++ b/src/widgets/styles/qwindowsstyle.cpp @@ -857,6 +857,7 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, proxy()->drawPrimitive(PE_PanelMenu, ©, p, w); break; } + Q_FALLTHROUGH(); case PE_FrameMenu: if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { if (frame->lineWidth == 2 || pe == PE_Frame) { @@ -911,7 +912,10 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, const QBrush menuBackground = opt->palette.base().color(); QColor borderColor = opt->palette.window().color(); qDrawPlainRect(p, opt->rect, borderColor, 1, &menuBackground); + } else { + QCommonStyle::drawPrimitive(pe, opt, p, w); } + break; case PE_FrameWindow: { QPalette popupPal = opt->palette; popupPal.setColor(QPalette::Light, opt->palette.window().color()); diff --git a/src/widgets/widgets/qlineedit_p.cpp b/src/widgets/widgets/qlineedit_p.cpp index 55e6137dba9..ee80cca649c 100644 --- a/src/widgets/widgets/qlineedit_p.cpp +++ b/src/widgets/widgets/qlineedit_p.cpp @@ -353,17 +353,16 @@ QLineEditPrivate *QLineEditIconButton::lineEditPrivate() const void QLineEditIconButton::paintEvent(QPaintEvent *) { QPainter painter(this); - QIcon::Mode state = QIcon::Disabled; + QIcon::Mode mode = QIcon::Disabled; if (isEnabled()) - state = isDown() ? QIcon::Active : QIcon::Normal; + mode = isDown() ? QIcon::Active : QIcon::Normal; const QLineEditPrivate *lep = lineEditPrivate(); const int iconWidth = lep ? lep->sideWidgetParameters().iconSize : 16; const QSize iconSize(iconWidth, iconWidth); - const QPixmap iconPixmap = icon().pixmap(iconSize, devicePixelRatio(), state, QIcon::Off); QRect pixmapRect = QRect(QPoint(0, 0), iconSize); pixmapRect.moveCenter(rect().center()); painter.setOpacity(m_opacity); - painter.drawPixmap(pixmapRect, iconPixmap); + icon().paint(&painter, pixmapRect, Qt::AlignCenter, mode, QIcon::Off); } void QLineEditIconButton::actionEvent(QActionEvent *e) diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp index 0b562a47879..8e6f497d7f5 100644 --- a/src/widgets/widgets/qtabbar.cpp +++ b/src/widgets/widgets/qtabbar.cpp @@ -1003,7 +1003,7 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text) ++tab->lastTab; } - if (tabAt(d->mousePosition) == index) { + if (isVisible() && tabAt(d->mousePosition) == index) { d->hoverIndex = index; d->hoverRect = tabRect(index); } |
