diff options
Diffstat (limited to 'src')
119 files changed, 1306 insertions, 15447 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/CMakeLists.txt b/src/CMakeLists.txt index babf5bc31d2..2920c743243 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,7 +71,6 @@ add_subdirectory(tools) if(QT_FEATURE_gui) add_subdirectory(gui) - add_subdirectory(assets) if(QT_FEATURE_opengl) add_subdirectory(opengl) diff --git a/src/assets/CMakeLists.txt b/src/assets/CMakeLists.txt deleted file mode 100644 index ab07d27696e..00000000000 --- a/src/assets/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -if (NOT INTEGRITY AND TARGET Qt6::Network AND TARGET Qt6::Concurrent) - add_subdirectory(downloader) -endif() diff --git a/src/assets/downloader/CMakeLists.txt b/src/assets/downloader/CMakeLists.txt deleted file mode 100644 index 6b0564e72af..00000000000 --- a/src/assets/downloader/CMakeLists.txt +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2024 The Qt Company Ltd. -# SPDX-License-Identifier: BSD-3-Clause - -qt_internal_add_module(ExamplesAssetDownloaderPrivate - CONFIG_MODULE_NAME examples_asset_downloader - STATIC - INTERNAL_MODULE - SOURCES - assetdownloader.cpp assetdownloader.h - tasking/barrier.cpp tasking/barrier.h - tasking/concurrentcall.h - tasking/conditional.cpp tasking/conditional.h - tasking/networkquery.cpp tasking/networkquery.h - tasking/qprocesstask.cpp tasking/qprocesstask.h - tasking/tasking_global.h - tasking/tasktree.cpp tasking/tasktree.h - tasking/tasktreerunner.cpp tasking/tasktreerunner.h - tasking/tcpsocket.cpp tasking/tcpsocket.h - DEFINES - QT_NO_CAST_FROM_ASCII - LIBRARIES - Qt6::CorePrivate - PUBLIC_LIBRARIES - Qt6::Concurrent - Qt6::Core - Qt6::Network - NO_GENERATE_CPP_EXPORTS -) - -if (NOT QT_FEATURE_process) - set_source_files_properties(tasking/qprocesstask.h PROPERTIES SKIP_AUTOMOC TRUE) -endif() diff --git a/src/assets/downloader/assetdownloader.cpp b/src/assets/downloader/assetdownloader.cpp deleted file mode 100644 index 7c2f525a66d..00000000000 --- a/src/assets/downloader/assetdownloader.cpp +++ /dev/null @@ -1,592 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "assetdownloader.h" - -#include "tasking/concurrentcall.h" -#include "tasking/networkquery.h" -#include "tasking/tasktreerunner.h" - -#include <QtCore/private/qzipreader_p.h> - -#include <QtCore/QDir> -#include <QtCore/QFile> -#include <QtCore/QJsonArray> -#include <QtCore/QJsonDocument> -#include <QtCore/QJsonObject> -#include <QtCore/QStandardPaths> -#include <QtCore/QTemporaryDir> -#include <QtCore/QTemporaryFile> - -using namespace Tasking; - -QT_BEGIN_NAMESPACE - -namespace Assets::Downloader { - -struct DownloadableAssets -{ - QUrl remoteUrl; - QList<QUrl> files; -}; - -class AssetDownloaderPrivate -{ -public: - AssetDownloaderPrivate(AssetDownloader *q) : m_q(q) {} - AssetDownloader *m_q = nullptr; - - std::unique_ptr<QNetworkAccessManager> m_manager; - std::unique_ptr<QTemporaryDir> m_temporaryDir; - TaskTreeRunner m_taskTreeRunner; - QString m_lastProgressText; - QDir m_localDownloadDir; - - QString m_jsonFileName; - QString m_zipFileName; - QDir m_preferredLocalDownloadDir = - QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - QUrl m_offlineAssetsFilePath; - QUrl m_downloadBase; - QStringList m_networkErrors; - QStringList m_sslErrors; - - void setLocalDownloadDir(const QDir &dir) - { - if (m_localDownloadDir != dir) { - m_localDownloadDir = dir; - emit m_q->localDownloadDirChanged(QUrl::fromLocalFile(m_localDownloadDir.absolutePath())); - } - } - void setProgress(int progressValue, int progressMaximum, const QString &progressText) - { - m_lastProgressText = progressText; - emit m_q->progressChanged(progressValue, progressMaximum, progressText); - } - void updateProgress(int progressValue, int progressMaximum) - { - setProgress(progressValue, progressMaximum, m_lastProgressText); - } - void clearProgress(const QString &progressText) - { - setProgress(0, 0, progressText); - } - - void setupDownload(NetworkQuery *query, const QString &progressText) - { - query->setNetworkAccessManager(m_manager.get()); - clearProgress(progressText); - QObject::connect(query, &NetworkQuery::started, query, [this, query] { - QNetworkReply *reply = query->reply(); - QObject::connect(reply, &QNetworkReply::downloadProgress, - query, [this](qint64 bytesReceived, qint64 totalBytes) { - updateProgress((totalBytes > 0) ? 100.0 * bytesReceived / totalBytes : 0, 100); - }); - QObject::connect(reply, &QNetworkReply::errorOccurred, query, [this, reply] { - m_networkErrors << reply->errorString(); - }); -#if QT_CONFIG(ssl) - QObject::connect(reply, &QNetworkReply::sslErrors, - query, [this](const QList<QSslError> &sslErrors) { - for (const QSslError &sslError : sslErrors) - m_sslErrors << sslError.errorString(); - }); -#endif - }); - } -}; - -static bool isWritableDir(const QDir &dir) -{ - if (dir.exists()) { - QTemporaryFile file(dir.filePath(QString::fromLatin1("tmp"))); - return file.open(); - } - return false; -} - -static bool sameFileContent(const QFileInfo &first, const QFileInfo &second) -{ - if (first.exists() ^ second.exists()) - return false; - - if (first.size() != second.size()) - return false; - - QFile firstFile(first.absoluteFilePath()); - QFile secondFile(second.absoluteFilePath()); - - if (firstFile.open(QFile::ReadOnly) && secondFile.open(QFile::ReadOnly)) { - char char1; - char char2; - int readBytes1 = 0; - int readBytes2 = 0; - while (!firstFile.atEnd()) { - readBytes1 = firstFile.read(&char1, 1); - readBytes2 = secondFile.read(&char2, 1); - if (readBytes1 != readBytes2 || readBytes1 != 1) - return false; - if (char1 != char2) - return false; - } - return true; - } - - return false; -} - -static bool createDirectory(const QDir &dir) -{ - if (dir.exists()) - return true; - - if (!createDirectory(dir.absoluteFilePath(QString::fromUtf8("..")))) - return false; - - return dir.mkpath(QString::fromUtf8(".")); -} - -static bool canBeALocalBaseDir(const QDir &dir) -{ - if (dir.exists()) - return !dir.isEmpty() || isWritableDir(dir); - return createDirectory(dir) && isWritableDir(dir); -} - -static QDir baseLocalDir(const QDir &preferredLocalDir) -{ - if (canBeALocalBaseDir(preferredLocalDir)) - return preferredLocalDir; - - qWarning().noquote() << "AssetDownloader: Cannot set \"" << preferredLocalDir - << "\" as a local download directory!"; - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); -} - -static QString pathFromUrl(const QUrl &url) -{ - if (url.isLocalFile()) - return url.toLocalFile(); - - if (url.scheme() == u"qrc") - return u":" + url.path(); - - return url.toString(); -} - -static QList<QUrl> filterDownloadableAssets(const QList<QUrl> &assetFiles, const QDir &expectedDir) -{ - QList<QUrl> downloadList; - std::copy_if(assetFiles.begin(), assetFiles.end(), std::back_inserter(downloadList), - [&](const QUrl &assetPath) { - return !QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString())); - }); - return downloadList; -} - -static bool allAssetsPresent(const QList<QUrl> &assetFiles, const QDir &expectedDir) -{ - return std::all_of(assetFiles.begin(), assetFiles.end(), [&](const QUrl &assetPath) { - return QFileInfo::exists(expectedDir.absoluteFilePath(assetPath.toString())); - }); -} - -AssetDownloader::AssetDownloader(QObject *parent) - : QObject(parent) - , d(new AssetDownloaderPrivate(this)) -{} - -AssetDownloader::~AssetDownloader() = default; - -QUrl AssetDownloader::downloadBase() const -{ - return d->m_downloadBase; -} - -void AssetDownloader::setDownloadBase(const QUrl &downloadBase) -{ - if (d->m_downloadBase != downloadBase) { - d->m_downloadBase = downloadBase; - emit downloadBaseChanged(d->m_downloadBase); - } -} - -QUrl AssetDownloader::preferredLocalDownloadDir() const -{ - return QUrl::fromLocalFile(d->m_preferredLocalDownloadDir.absolutePath()); -} - -void AssetDownloader::setPreferredLocalDownloadDir(const QUrl &localDir) -{ - if (localDir.scheme() == u"qrc") { - qWarning() << "Cannot set a qrc as preferredLocalDownloadDir"; - return; - } - - const QString path = pathFromUrl(localDir); - if (d->m_preferredLocalDownloadDir != path) { - d->m_preferredLocalDownloadDir.setPath(path); - emit preferredLocalDownloadDirChanged(preferredLocalDownloadDir()); - } -} - -QUrl AssetDownloader::offlineAssetsFilePath() const -{ - return d->m_offlineAssetsFilePath; -} - -void AssetDownloader::setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath) -{ - if (d->m_offlineAssetsFilePath != offlineAssetsFilePath) { - d->m_offlineAssetsFilePath = offlineAssetsFilePath; - emit offlineAssetsFilePathChanged(d->m_offlineAssetsFilePath); - } -} - -QString AssetDownloader::jsonFileName() const -{ - return d->m_jsonFileName; -} - -void AssetDownloader::setJsonFileName(const QString &jsonFileName) -{ - if (d->m_jsonFileName != jsonFileName) { - d->m_jsonFileName = jsonFileName; - emit jsonFileNameChanged(d->m_jsonFileName); - } -} - -QString AssetDownloader::zipFileName() const -{ - return d->m_zipFileName; -} - -void AssetDownloader::setZipFileName(const QString &zipFileName) -{ - if (d->m_zipFileName != zipFileName) { - d->m_zipFileName = zipFileName; - emit zipFileNameChanged(d->m_zipFileName); - } -} - -QUrl AssetDownloader::localDownloadDir() const -{ - return QUrl::fromLocalFile(d->m_localDownloadDir.absolutePath()); -} - -QStringList AssetDownloader::networkErrors() const -{ - return d->m_networkErrors; -} - -QStringList AssetDownloader::sslErrors() const -{ - return d->m_sslErrors; -} - -static void precheckLocalFile(const QUrl &url) -{ - if (url.isEmpty()) - return; - QFile file(pathFromUrl(url)); - if (!file.open(QIODevice::ReadOnly)) - qWarning() << "Cannot open local file" << url; -} - -static void readAssetsFileContent(QPromise<DownloadableAssets> &promise, const QByteArray &content) -{ - const QJsonObject json = QJsonDocument::fromJson(content).object(); - const QJsonArray assetsArray = json[u"assets"].toArray(); - DownloadableAssets result; - result.remoteUrl = json[u"url"].toString(); - for (const QJsonValue &asset : assetsArray) { - if (promise.isCanceled()) - return; - result.files.append(asset.toString()); - } - - if (result.files.isEmpty() || result.remoteUrl.isEmpty()) - promise.future().cancel(); - else - promise.addResult(result); -} - -static void unzip(QPromise<void> &promise, const QByteArray &content, const QDir &directory, - const QString &fileName) -{ - const QString zipFilePath = directory.absoluteFilePath(fileName); - QFile zipFile(zipFilePath); - if (!zipFile.open(QIODevice::WriteOnly)) { - promise.future().cancel(); - return; - } - zipFile.write(content); - zipFile.close(); - - if (promise.isCanceled()) - return; - - QZipReader reader(zipFilePath); - const bool extracted = reader.extractAll(directory.absolutePath()); - reader.close(); - if (extracted) - QFile::remove(zipFilePath); - else - promise.future().cancel(); -} - -static void writeAsset(QPromise<void> &promise, const QByteArray &content, const QString &filePath) -{ - const QFileInfo fileInfo(filePath); - QFile file(fileInfo.absoluteFilePath()); - if (!createDirectory(fileInfo.dir()) || !file.open(QFile::WriteOnly)) { - promise.future().cancel(); - return; - } - - if (promise.isCanceled()) - return; - - file.write(content); - file.close(); -} - -static void copyAndCheck(QPromise<void> &promise, const QString &sourcePath, const QString &destPath) -{ - QFile sourceFile(sourcePath); - QFile destFile(destPath); - const QFileInfo sourceFileInfo(sourceFile.fileName()); - const QFileInfo destFileInfo(destFile.fileName()); - - if (destFile.exists() && !destFile.remove()) { - qWarning().noquote() << QString::fromLatin1("Unable to remove file \"%1\".") - .arg(QFileInfo(destFile.fileName()).absoluteFilePath()); - promise.future().cancel(); - return; - } - - if (!createDirectory(destFileInfo.absolutePath())) { - qWarning().noquote() << QString::fromLatin1("Cannot create directory \"%1\".") - .arg(destFileInfo.absolutePath()); - promise.future().cancel(); - return; - } - - if (promise.isCanceled()) - return; - - if (!sourceFile.copy(destFile.fileName()) && !sameFileContent(sourceFileInfo, destFileInfo)) - promise.future().cancel(); -} - -void AssetDownloader::start() -{ - if (d->m_taskTreeRunner.isRunning()) - return; - - struct StorageData - { - QDir tempDir; - QByteArray jsonContent; - DownloadableAssets assets; - QList<QUrl> assetsToDownload; - QByteArray zipContent; - int doneCount = 0; - }; - - const Storage<StorageData> storage; - - const auto onSetup = [this, storage] { - if (!d->m_manager) - d->m_manager = std::make_unique<QNetworkAccessManager>(); - if (!d->m_temporaryDir) - d->m_temporaryDir = std::make_unique<QTemporaryDir>(); - if (!d->m_temporaryDir->isValid()) { - qWarning() << "Cannot create a temporary directory."; - return SetupResult::StopWithError; - } - storage->tempDir = d->m_temporaryDir->path(); - d->setLocalDownloadDir(baseLocalDir(d->m_preferredLocalDownloadDir)); - d->m_networkErrors.clear(); - d->m_sslErrors.clear(); - precheckLocalFile(resolvedUrl(d->m_offlineAssetsFilePath)); - return SetupResult::Continue; - }; - - const auto onJsonDownloadSetup = [this](NetworkQuery &query) { - query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_jsonFileName))); - d->setupDownload(&query, tr("Downloading JSON file...")); - }; - const auto onJsonDownloadDone = [this, storage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) { - storage->jsonContent = query.reply()->readAll(); - return DoneResult::Success; - } - qWarning() << "Cannot download" << d->m_downloadBase.resolved(d->m_jsonFileName) - << query.reply()->errorString(); - if (d->m_offlineAssetsFilePath.isEmpty()) { - qWarning() << "Also there is no local file as a replacement"; - return DoneResult::Error; - } - - QFile file(pathFromUrl(resolvedUrl(d->m_offlineAssetsFilePath))); - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "Also failed to open" << d->m_offlineAssetsFilePath; - return DoneResult::Error; - } - - storage->jsonContent = file.readAll(); - return DoneResult::Success; - }; - - const auto onReadAssetsFileSetup = [storage](ConcurrentCall<DownloadableAssets> &async) { - async.setConcurrentCallData(readAssetsFileContent, storage->jsonContent); - }; - const auto onReadAssetsFileDone = [storage](const ConcurrentCall<DownloadableAssets> &async) { - storage->assets = async.result(); - storage->assetsToDownload = storage->assets.files; - }; - - const auto onSkipIfAllAssetsPresent = [this, storage] { - return allAssetsPresent(storage->assets.files, d->m_localDownloadDir) - ? SetupResult::StopWithSuccess : SetupResult::Continue; - }; - - const auto onZipDownloadSetup = [this, storage](NetworkQuery &query) { - if (d->m_zipFileName.isEmpty()) - return SetupResult::StopWithSuccess; - - query.setRequest(QNetworkRequest(d->m_downloadBase.resolved(d->m_zipFileName))); - d->setupDownload(&query, tr("Downloading zip file...")); - return SetupResult::Continue; - }; - const auto onZipDownloadDone = [storage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) - storage->zipContent = query.reply()->readAll(); - return DoneResult::Success; // Ignore zip download failure - }; - - const auto onUnzipSetup = [this, storage](ConcurrentCall<void> &async) { - if (storage->zipContent.isEmpty()) - return SetupResult::StopWithSuccess; - - async.setConcurrentCallData(unzip, storage->zipContent, storage->tempDir, d->m_zipFileName); - d->clearProgress(tr("Unzipping...")); - return SetupResult::Continue; - }; - const auto onUnzipDone = [storage](DoneWith result) { - if (result == DoneWith::Success) { - // Avoid downloading assets that are present in unzipped tree - StorageData &storageData = *storage; - storageData.assetsToDownload = - filterDownloadableAssets(storageData.assets.files, storageData.tempDir); - } else { - qWarning() << "ZipFile failed"; - } - return DoneResult::Success; // Ignore unzip failure - }; - - const LoopUntil downloadIterator([storage](int iteration) { - return iteration < storage->assetsToDownload.count(); - }); - - const Storage<QByteArray> assetStorage; - - const auto onAssetsDownloadGroupSetup = [this, storage] { - d->setProgress(0, storage->assetsToDownload.size(), tr("Downloading assets...")); - }; - - const auto onAssetDownloadSetup = [this, storage, downloadIterator](NetworkQuery &query) { - query.setNetworkAccessManager(d->m_manager.get()); - query.setRequest(QNetworkRequest(storage->assets.remoteUrl.resolved( - storage->assetsToDownload.at(downloadIterator.iteration())))); - }; - const auto onAssetDownloadDone = [assetStorage](const NetworkQuery &query, DoneWith result) { - if (result == DoneWith::Success) - *assetStorage = query.reply()->readAll(); - }; - - const auto onAssetWriteSetup = [storage, downloadIterator, assetStorage]( - ConcurrentCall<void> &async) { - const QString filePath = storage->tempDir.absoluteFilePath( - storage->assetsToDownload.at(downloadIterator.iteration()).toString()); - async.setConcurrentCallData(writeAsset, *assetStorage, filePath); - }; - const auto onAssetWriteDone = [this, storage](DoneWith result) { - if (result != DoneWith::Success) { - qWarning() << "Asset write failed"; - return; - } - StorageData &storageData = *storage; - ++storageData.doneCount; - d->updateProgress(storageData.doneCount, storageData.assetsToDownload.size()); - }; - - const LoopUntil copyIterator([storage](int iteration) { - return iteration < storage->assets.files.count(); - }); - - const auto onAssetsCopyGroupSetup = [this, storage] { - storage->doneCount = 0; - d->setProgress(0, storage->assets.files.size(), tr("Copying assets...")); - }; - - const auto onAssetCopySetup = [this, storage, copyIterator](ConcurrentCall<void> &async) { - const QString fileName = storage->assets.files.at(copyIterator.iteration()).toString(); - const QString sourcePath = storage->tempDir.absoluteFilePath(fileName); - const QString destPath = d->m_localDownloadDir.absoluteFilePath(fileName); - async.setConcurrentCallData(copyAndCheck, sourcePath, destPath); - }; - const auto onAssetCopyDone = [this, storage] { - StorageData &storageData = *storage; - ++storageData.doneCount; - d->updateProgress(storageData.doneCount, storageData.assets.files.size()); - }; - - const auto onAssetsCopyGroupDone = [this, storage](DoneWith result) { - if (result != DoneWith::Success) { - d->setLocalDownloadDir(storage->tempDir); - qWarning() << "Asset copy failed"; - return; - } - d->m_temporaryDir.reset(); - }; - - const Group recipe { - storage, - onGroupSetup(onSetup), - NetworkQueryTask(onJsonDownloadSetup, onJsonDownloadDone), - ConcurrentCallTask<DownloadableAssets>(onReadAssetsFileSetup, onReadAssetsFileDone, CallDoneIf::Success), - Group { - onGroupSetup(onSkipIfAllAssetsPresent), - NetworkQueryTask(onZipDownloadSetup, onZipDownloadDone), - ConcurrentCallTask<void>(onUnzipSetup, onUnzipDone), - For (downloadIterator) >> Do { - parallelIdealThreadCountLimit, - onGroupSetup(onAssetsDownloadGroupSetup), - Group { - assetStorage, - NetworkQueryTask(onAssetDownloadSetup, onAssetDownloadDone), - ConcurrentCallTask<void>(onAssetWriteSetup, onAssetWriteDone) - } - }, - For (copyIterator) >> Do { - parallelIdealThreadCountLimit, - onGroupSetup(onAssetsCopyGroupSetup), - ConcurrentCallTask<void>(onAssetCopySetup, onAssetCopyDone, CallDoneIf::Success), - onGroupDone(onAssetsCopyGroupDone) - } - } - }; - d->m_taskTreeRunner.start(recipe, [this](TaskTree *) { emit started(); }, - [this](DoneWith result) { emit finished(result == DoneWith::Success); }); -} - -QUrl AssetDownloader::resolvedUrl(const QUrl &url) const -{ - return url; -} - -} // namespace Assets::Downloader - -QT_END_NAMESPACE diff --git a/src/assets/downloader/assetdownloader.h b/src/assets/downloader/assetdownloader.h deleted file mode 100644 index 3c9351ceafe..00000000000 --- a/src/assets/downloader/assetdownloader.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef ASSETDOWNLOADER_H -#define ASSETDOWNLOADER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtCore/QObject> -#include <QtCore/QUrl> - -#include <memory> - -QT_BEGIN_NAMESPACE - -namespace Assets::Downloader { - -class AssetDownloaderPrivate; - -class AssetDownloader : public QObject -{ - Q_OBJECT - - Q_PROPERTY( - QUrl downloadBase - READ downloadBase - WRITE setDownloadBase - NOTIFY downloadBaseChanged) - - Q_PROPERTY( - QUrl preferredLocalDownloadDir - READ preferredLocalDownloadDir - WRITE setPreferredLocalDownloadDir - NOTIFY preferredLocalDownloadDirChanged) - - Q_PROPERTY( - QUrl offlineAssetsFilePath - READ offlineAssetsFilePath - WRITE setOfflineAssetsFilePath - NOTIFY offlineAssetsFilePathChanged) - - Q_PROPERTY( - QString jsonFileName - READ jsonFileName - WRITE setJsonFileName - NOTIFY jsonFileNameChanged) - - Q_PROPERTY( - QString zipFileName - READ zipFileName - WRITE setZipFileName - NOTIFY zipFileNameChanged) - - Q_PROPERTY( - QUrl localDownloadDir - READ localDownloadDir - NOTIFY localDownloadDirChanged) - -public: - AssetDownloader(QObject *parent = nullptr); - ~AssetDownloader(); - - QUrl downloadBase() const; - void setDownloadBase(const QUrl &downloadBase); - - QUrl preferredLocalDownloadDir() const; - void setPreferredLocalDownloadDir(const QUrl &localDir); - - QUrl offlineAssetsFilePath() const; - void setOfflineAssetsFilePath(const QUrl &offlineAssetsFilePath); - - QString jsonFileName() const; - void setJsonFileName(const QString &jsonFileName); - - QString zipFileName() const; - void setZipFileName(const QString &zipFileName); - - QUrl localDownloadDir() const; - - Q_INVOKABLE QStringList networkErrors() const; - Q_INVOKABLE QStringList sslErrors() const; - -public Q_SLOTS: - void start(); - -protected: - virtual QUrl resolvedUrl(const QUrl &url) const; - -Q_SIGNALS: - void started(); - void finished(bool success); - void progressChanged(int progressValue, int progressMaximum, const QString &progressText); - void localDownloadDirChanged(const QUrl &url); - - void downloadBaseChanged(const QUrl &); - void preferredLocalDownloadDirChanged(const QUrl &url); - void offlineAssetsFilePathChanged(const QUrl &); - void jsonFileNameChanged(const QString &); - void zipFileNameChanged(const QString &); - -private: - std::unique_ptr<AssetDownloaderPrivate> d; -}; - -} // namespace Assets::Downloader - -QT_END_NAMESPACE - -#endif // ASSETDOWNLOADER_H diff --git a/src/assets/downloader/tasking/barrier.cpp b/src/assets/downloader/tasking/barrier.cpp deleted file mode 100644 index c9e5992bc78..00000000000 --- a/src/assets/downloader/tasking/barrier.cpp +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "barrier.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__)) -#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0) - -void Barrier::setLimit(int value) -{ - QT_ASSERT(!isRunning(), return); - QT_ASSERT(value > 0, return); - - m_limit = value; -} - -void Barrier::start() -{ - QT_ASSERT(!isRunning(), return); - m_current = 0; - m_result.reset(); -} - -void Barrier::advance() -{ - // Calling advance on finished is OK - QT_ASSERT(isRunning() || m_result, return); - if (!isRunning()) // no-op - return; - ++m_current; - if (m_current == m_limit) - stopWithResult(DoneResult::Success); -} - -void Barrier::stopWithResult(DoneResult result) -{ - // Calling stopWithResult on finished is OK when the same success is passed - QT_ASSERT(isRunning() || (m_result && *m_result == result), return); - if (!isRunning()) // no-op - return; - m_current = -1; - m_result = result; - emit done(result); -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/barrier.h b/src/assets/downloader/tasking/barrier.h deleted file mode 100644 index d489d2722a4..00000000000 --- a/src/assets/downloader/tasking/barrier.h +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_BARRIER_H -#define TASKING_BARRIER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class TASKING_EXPORT Barrier final : public QObject -{ - Q_OBJECT - -public: - void setLimit(int value); - int limit() const { return m_limit; } - - void start(); - void advance(); // If limit reached, stops with true - void stopWithResult(DoneResult result); // Ignores limit - - bool isRunning() const { return m_current >= 0; } - int current() const { return m_current; } - std::optional<DoneResult> result() const { return m_result; } - -Q_SIGNALS: - void done(DoneResult success); - -private: - std::optional<DoneResult> m_result = {}; - int m_limit = 1; - int m_current = -1; -}; - -using BarrierTask = SimpleCustomTask<Barrier>; - -template <int Limit = 1> -class SharedBarrier -{ -public: - static_assert(Limit > 0, "SharedBarrier's limit should be 1 or more."); - SharedBarrier() : m_barrier(new Barrier) { - m_barrier->setLimit(Limit); - m_barrier->start(); - } - Barrier *barrier() const { return m_barrier.get(); } - -private: - std::shared_ptr<Barrier> m_barrier; -}; - -template <int Limit = 1> -using MultiBarrier = Storage<SharedBarrier<Limit>>; - -// Can't write: "MultiBarrier barrier;". Only "MultiBarrier<> barrier;" would work. -// Can't have one alias with default type in C++17, getting the following error: -// alias template deduction only available with C++20. -using SingleBarrier = MultiBarrier<1>; - -template <int Limit> -ExecutableItem waitForBarrierTask(const MultiBarrier<Limit> &sharedBarrier) -{ - return BarrierTask([sharedBarrier](Barrier &barrier) { - SharedBarrier<Limit> *activeBarrier = sharedBarrier.activeStorage(); - if (!activeBarrier) { - qWarning("The barrier referenced from WaitForBarrier element " - "is not reachable in the running tree. " - "It is possible that no barrier was added to the tree, " - "or the barrier is not reachable from where it is referenced. " - "The WaitForBarrier task finishes with an error. "); - return SetupResult::StopWithError; - } - Barrier *activeSharedBarrier = activeBarrier->barrier(); - const std::optional<DoneResult> result = activeSharedBarrier->result(); - if (result.has_value()) { - return *result == DoneResult::Success ? SetupResult::StopWithSuccess - : SetupResult::StopWithError; - } - QObject::connect(activeSharedBarrier, &Barrier::done, &barrier, &Barrier::stopWithResult); - return SetupResult::Continue; - }); -} - -template <typename Signal> -ExecutableItem signalAwaiter(const typename QtPrivate::FunctionPointer<Signal>::Object *sender, Signal signal) -{ - return BarrierTask([sender, signal](Barrier &barrier) { - QObject::connect(sender, signal, &barrier, &Barrier::advance, Qt::SingleShotConnection); - }); -} - -using BarrierKickerGetter = std::function<ExecutableItem(const SingleBarrier &)>; - -class TASKING_EXPORT When final -{ -public: - explicit When(const BarrierKickerGetter &kicker) : m_barrierKicker(kicker) {} - -private: - TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem); - - BarrierKickerGetter m_barrierKicker; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_BARRIER_H diff --git a/src/assets/downloader/tasking/concurrentcall.h b/src/assets/downloader/tasking/concurrentcall.h deleted file mode 100644 index cc701297d13..00000000000 --- a/src/assets/downloader/tasking/concurrentcall.h +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_CONCURRENTCALL_H -#define TASKING_CONCURRENTCALL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasktree.h" - -#include <QtConcurrent/QtConcurrent> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// This class introduces the dependency to Qt::Concurrent, otherwise Tasking namespace -// is independent on Qt::Concurrent. -// Possibly, it could be placed inside Qt::Concurrent library, as a wrapper around -// QtConcurrent::run() call. - -template <typename ResultType> -class ConcurrentCall -{ - Q_DISABLE_COPY_MOVE(ConcurrentCall) - -public: - ConcurrentCall() = default; - template <typename Function, typename ...Args> - void setConcurrentCallData(Function &&function, Args &&...args) - { - wrapConcurrent(std::forward<Function>(function), std::forward<Args>(args)...); - } - void setThreadPool(QThreadPool *pool) { m_threadPool = pool; } - ResultType result() const - { - return m_future.resultCount() ? m_future.result() : ResultType(); - } - QList<ResultType> results() const - { - return m_future.results(); - } - QFuture<ResultType> future() const { return m_future; } - -private: - template <typename Function, typename ...Args> - void wrapConcurrent(Function &&function, Args &&...args) - { - m_startHandler = [this, function = std::forward<Function>(function), args...] { - QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); - return QtConcurrent::run(threadPool, function, args...); - }; - } - - template <typename Function, typename ...Args> - void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args) - { - m_startHandler = [this, wrapper = std::forward<std::reference_wrapper<const Function>>(wrapper), args...] { - QThreadPool *threadPool = m_threadPool ? m_threadPool : QThreadPool::globalInstance(); - return QtConcurrent::run(threadPool, std::forward<const Function>(wrapper.get()), - args...); - }; - } - - template <typename T> - friend class ConcurrentCallTaskAdapter; - - std::function<QFuture<ResultType>()> m_startHandler; - QThreadPool *m_threadPool = nullptr; - QFuture<ResultType> m_future; -}; - -template <typename ResultType> -class ConcurrentCallTaskAdapter : public TaskAdapter<ConcurrentCall<ResultType>> -{ -public: - ~ConcurrentCallTaskAdapter() { - if (m_watcher) { - m_watcher->cancel(); - m_watcher->waitForFinished(); - } - } - - void start() final { - if (!this->task()->m_startHandler) { - emit this->done(DoneResult::Error); // TODO: Add runtime assert - return; - } - m_watcher.reset(new QFutureWatcher<ResultType>); - this->connect(m_watcher.get(), &QFutureWatcherBase::finished, this, [this] { - emit this->done(toDoneResult(!m_watcher->isCanceled())); - m_watcher.release()->deleteLater(); - }); - this->task()->m_future = this->task()->m_startHandler(); - m_watcher->setFuture(this->task()->m_future); - } - -private: - std::unique_ptr<QFutureWatcher<ResultType>> m_watcher; -}; - -template <typename T> -using ConcurrentCallTask = CustomTask<ConcurrentCallTaskAdapter<T>>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_CONCURRENTCALL_H diff --git a/src/assets/downloader/tasking/conditional.cpp b/src/assets/downloader/tasking/conditional.cpp deleted file mode 100644 index 24a03fb703e..00000000000 --- a/src/assets/downloader/tasking/conditional.cpp +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "conditional.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -static Group conditionRecipe(const Storage<bool> &bodyExecutedStorage, const ConditionData &condition) -{ - const auto onSetup = [bodyExecutedStorage] { - return *bodyExecutedStorage ? SetupResult::StopWithSuccess : SetupResult::Continue; - }; - - const auto onBodyDone = [bodyExecutedStorage] { *bodyExecutedStorage = true; }; - - const Group bodyTask { condition.m_body, onGroupDone(onBodyDone) }; - - return { - onGroupSetup(onSetup), - condition.m_condition ? Group{ !*condition.m_condition || bodyTask } : bodyTask - }; -} - -static ExecutableItem conditionsRecipe(const QList<ConditionData> &conditions) -{ - Storage<bool> bodyExecutedStorage; - - GroupItems recipes; - for (const ConditionData &condition : conditions) - recipes << conditionRecipe(bodyExecutedStorage, condition); - - return Group { bodyExecutedStorage, recipes }; -} - -ThenItem::operator ExecutableItem() const -{ - return conditionsRecipe(m_conditions); -} - -ThenItem::ThenItem(const If &ifItem, const Then &thenItem) - : m_conditions{{ifItem.m_condition, thenItem.m_body}} -{} - -ThenItem::ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem) - : m_conditions(elseIfItem.m_conditions) -{ - m_conditions.append({elseIfItem.m_nextCondition, thenItem.m_body}); -} - -ElseItem::operator ExecutableItem() const -{ - return conditionsRecipe(m_conditions); -} - -ElseItem::ElseItem(const ThenItem &thenItem, const Else &elseItem) - : m_conditions(thenItem.m_conditions) -{ - m_conditions.append({{}, elseItem.m_body}); -} - -ElseIfItem::ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem) - : m_conditions(thenItem.m_conditions) - , m_nextCondition(elseIfItem.m_condition) -{} - -ThenItem operator>>(const If &ifItem, const Then &thenItem) -{ - return {ifItem, thenItem}; -} - -ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem) -{ - return {elseIfItem, thenItem}; -} - -ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem) -{ - return {thenItem, elseIfItem}; -} - -ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem) -{ - return {thenItem, elseItem}; -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/conditional.h b/src/assets/downloader/tasking/conditional.h deleted file mode 100644 index 52fdfd64cb7..00000000000 --- a/src/assets/downloader/tasking/conditional.h +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_CONDITIONAL_H -#define TASKING_CONDITIONAL_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class Then; -class ThenItem; -class ElseItem; -class ElseIfItem; - -class TASKING_EXPORT If -{ -public: - explicit If(const ExecutableItem &condition) : m_condition(condition) {} - - template <typename Handler, - std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true> - explicit If(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {} - -private: - TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem); - - friend class ThenItem; - ExecutableItem m_condition; -}; - -class TASKING_EXPORT ElseIf -{ -public: - explicit ElseIf(const ExecutableItem &condition) : m_condition(condition) {} - - template <typename Handler, - std::enable_if_t<!std::is_base_of_v<ExecutableItem, std::decay_t<Handler>>, bool> = true> - explicit ElseIf(Handler &&handler) : m_condition(Sync(std::forward<Handler>(handler))) {} - -private: - friend class ElseIfItem; - ExecutableItem m_condition; -}; - -class TASKING_EXPORT Else -{ -public: - explicit Else(const GroupItems &children) : m_body({children}) {} - explicit Else(std::initializer_list<GroupItem> children) : m_body({children}) {} - -private: - friend class ElseItem; - Group m_body; -}; - -class TASKING_EXPORT Then -{ -public: - explicit Then(const GroupItems &children) : m_body({children}) {} - explicit Then(std::initializer_list<GroupItem> children) : m_body({children}) {} - -private: - friend class ThenItem; - Group m_body; -}; - -class ConditionData -{ -public: - std::optional<ExecutableItem> m_condition; - Group m_body; -}; - -class ElseIfItem; - -class TASKING_EXPORT ThenItem -{ -public: - operator ExecutableItem() const; - -private: - ThenItem(const If &ifItem, const Then &thenItem); - ThenItem(const ElseIfItem &elseIfItem, const Then &thenItem); - - TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem); - TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem); - TASKING_EXPORT friend ThenItem operator>>(const If &ifItem, const Then &thenItem); - TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem); - - friend class ElseItem; - friend class ElseIfItem; - QList<ConditionData> m_conditions; -}; - -class TASKING_EXPORT ElseItem -{ -public: - operator ExecutableItem() const; - -private: - ElseItem(const ThenItem &thenItem, const Else &elseItem); - - TASKING_EXPORT friend ElseItem operator>>(const ThenItem &thenItem, const Else &elseItem); - - QList<ConditionData> m_conditions; -}; - -class TASKING_EXPORT ElseIfItem -{ -private: - ElseIfItem(const ThenItem &thenItem, const ElseIf &elseIfItem); - - TASKING_EXPORT friend ThenItem operator>>(const ElseIfItem &elseIfItem, const Then &thenItem); - TASKING_EXPORT friend ElseIfItem operator>>(const ThenItem &thenItem, const ElseIf &elseIfItem); - - friend class ThenItem; - QList<ConditionData> m_conditions; - ExecutableItem m_nextCondition; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_CONDITIONAL_H diff --git a/src/assets/downloader/tasking/networkquery.cpp b/src/assets/downloader/tasking/networkquery.cpp deleted file mode 100644 index 3f15fed90e0..00000000000 --- a/src/assets/downloader/tasking/networkquery.cpp +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "networkquery.h" - -#include <QtNetwork/QNetworkAccessManager> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -void NetworkQuery::start() -{ - if (m_reply) { - qWarning("The NetworkQuery is already running. Ignoring the call to start()."); - return; - } - if (!m_manager) { - qWarning("Can't start the NetworkQuery without the QNetworkAccessManager. " - "Stopping with an error."); - emit done(DoneResult::Error); - return; - } - switch (m_operation) { - case NetworkOperation::Get: - m_reply.reset(m_manager->get(m_request)); - break; - case NetworkOperation::Put: - m_reply.reset(m_manager->put(m_request, m_writeData)); - break; - case NetworkOperation::Post: - m_reply.reset(m_manager->post(m_request, m_writeData)); - break; - case NetworkOperation::Delete: - m_reply.reset(m_manager->deleteResource(m_request)); - break; - } - - connect(m_reply.get(), &QNetworkReply::downloadProgress, this, &NetworkQuery::downloadProgress); -#if QT_CONFIG(ssl) - connect(m_reply.get(), &QNetworkReply::sslErrors, this, &NetworkQuery::sslErrors); -#endif - connect(m_reply.get(), &QNetworkReply::finished, this, [this] { - disconnect(m_reply.get(), &QNetworkReply::finished, this, nullptr); - emit done(toDoneResult(m_reply->error() == QNetworkReply::NoError)); - m_reply.release()->deleteLater(); - }); - if (m_reply->isRunning()) - emit started(); -} - -NetworkQuery::~NetworkQuery() -{ - if (m_reply) { - disconnect(m_reply.get(), nullptr, this, nullptr); - m_reply->abort(); - } -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/networkquery.h b/src/assets/downloader/tasking/networkquery.h deleted file mode 100644 index 2733fef8352..00000000000 --- a/src/assets/downloader/tasking/networkquery.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_NETWORKQUERY_H -#define TASKING_NETWORKQUERY_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -#include <QtNetwork/QNetworkReply> -#include <QtNetwork/QNetworkRequest> - -#include <memory> - -QT_BEGIN_NAMESPACE -class QNetworkAccessManager; - -namespace Tasking { - -// This class introduces the dependency to Qt::Network, otherwise Tasking namespace -// is independent on Qt::Network. -// Possibly, it could be placed inside Qt::Network library, as a wrapper around QNetworkReply. - -enum class NetworkOperation { Get, Put, Post, Delete }; - -class TASKING_EXPORT NetworkQuery final : public QObject -{ - Q_OBJECT - -public: - ~NetworkQuery(); - void setRequest(const QNetworkRequest &request) { m_request = request; } - void setOperation(NetworkOperation operation) { m_operation = operation; } - void setWriteData(const QByteArray &data) { m_writeData = data; } - void setNetworkAccessManager(QNetworkAccessManager *manager) { m_manager = manager; } - QNetworkReply *reply() const { return m_reply.get(); } - void start(); - -Q_SIGNALS: - void started(); - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); -#if QT_CONFIG(ssl) - void sslErrors(const QList<QSslError> &errors); -#endif - void done(DoneResult result); - -private: - QNetworkRequest m_request; - NetworkOperation m_operation = NetworkOperation::Get; - QByteArray m_writeData; // Used by Put and Post - QNetworkAccessManager *m_manager = nullptr; - std::unique_ptr<QNetworkReply> m_reply; -}; - -using NetworkQueryTask = SimpleCustomTask<NetworkQuery>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_NETWORKQUERY_H diff --git a/src/assets/downloader/tasking/qprocesstask.cpp b/src/assets/downloader/tasking/qprocesstask.cpp deleted file mode 100644 index a4696ebbbfb..00000000000 --- a/src/assets/downloader/tasking/qprocesstask.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "qprocesstask.h" - -#include <QtCore/QCoreApplication> -#include <QtCore/QDebug> -#include <QtCore/QElapsedTimer> -#include <QtCore/QMutex> -#include <QtCore/QThread> -#include <QtCore/QTimer> -#include <QtCore/QWaitCondition> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(process) - -namespace Tasking { - -class ProcessReaperPrivate; - -class ProcessReaper final -{ -public: - static void reap(QProcess *process, int timeoutMs = 500); - ProcessReaper(); - ~ProcessReaper(); - -private: - static ProcessReaper *instance(); - - QThread m_thread; - ProcessReaperPrivate *m_private; -}; - -static const int s_timeoutThreshold = 10000; // 10 seconds - -static QString execWithArguments(QProcess *process) -{ - QStringList commandLine; - commandLine.append(process->program()); - commandLine.append(process->arguments()); - return commandLine.join(QChar::Space); -} - -struct ReaperSetup -{ - QProcess *m_process = nullptr; - int m_timeoutMs; -}; - -class Reaper : public QObject -{ - Q_OBJECT - -public: - Reaper(const ReaperSetup &reaperSetup) : m_reaperSetup(reaperSetup) {} - - void reap() - { - m_timer.start(); - connect(m_reaperSetup.m_process, &QProcess::finished, this, &Reaper::handleFinished); - if (emitFinished()) - return; - terminate(); - } - -Q_SIGNALS: - void finished(); - -private: - void terminate() - { - m_reaperSetup.m_process->terminate(); - QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleTerminateTimeout); - } - - void kill() { m_reaperSetup.m_process->kill(); } - - bool emitFinished() - { - if (m_reaperSetup.m_process->state() != QProcess::NotRunning) - return false; - - if (!m_finished) { - const int timeout = m_timer.elapsed(); - if (timeout > s_timeoutThreshold) { - qWarning() << "Finished parallel reaping of" << execWithArguments(m_reaperSetup.m_process) - << "in" << (timeout / 1000.0) << "seconds."; - } - - m_finished = true; - emit finished(); - } - return true; - } - - void handleFinished() - { - if (emitFinished()) - return; - qWarning() << "Finished process still running..."; - // In case the process is still running - wait until it has finished - QTimer::singleShot(m_reaperSetup.m_timeoutMs, this, &Reaper::handleFinished); - } - - void handleTerminateTimeout() - { - if (emitFinished()) - return; - kill(); - } - - bool m_finished = false; - QElapsedTimer m_timer; - const ReaperSetup m_reaperSetup; -}; - -class ProcessReaperPrivate : public QObject -{ - Q_OBJECT - -public: - // Called from non-reaper's thread - void scheduleReap(const ReaperSetup &reaperSetup) - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't schedule reap from the reaper internal thread."; - - QMutexLocker locker(&m_mutex); - m_reaperSetupList.append(reaperSetup); - QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush); - } - - // Called from non-reaper's thread - void waitForFinished() - { - if (QThread::currentThread() == thread()) - qWarning() << "Can't wait for finished from the reaper internal thread."; - - QMetaObject::invokeMethod(this, &ProcessReaperPrivate::flush, - Qt::BlockingQueuedConnection); - QMutexLocker locker(&m_mutex); - if (m_reaperList.isEmpty()) - return; - - m_waitCondition.wait(&m_mutex); - } - -private: - // All the private methods are called from the reaper's thread - QList<ReaperSetup> takeReaperSetupList() - { - QMutexLocker locker(&m_mutex); - return std::exchange(m_reaperSetupList, {}); - } - - void flush() - { - while (true) { - const QList<ReaperSetup> reaperSetupList = takeReaperSetupList(); - if (reaperSetupList.isEmpty()) - return; - for (const ReaperSetup &reaperSetup : reaperSetupList) - reap(reaperSetup); - } - } - - void reap(const ReaperSetup &reaperSetup) - { - Reaper *reaper = new Reaper(reaperSetup); - connect(reaper, &Reaper::finished, this, [this, reaper, process = reaperSetup.m_process] { - QMutexLocker locker(&m_mutex); - const bool isRemoved = m_reaperList.removeOne(reaper); - if (!isRemoved) - qWarning() << "Reaper list doesn't contain the finished process."; - - delete reaper; - delete process; - if (m_reaperList.isEmpty()) - m_waitCondition.wakeOne(); - }, Qt::QueuedConnection); - - { - QMutexLocker locker(&m_mutex); - m_reaperList.append(reaper); - } - - reaper->reap(); - } - - QMutex m_mutex; - QWaitCondition m_waitCondition; - QList<ReaperSetup> m_reaperSetupList; - QList<Reaper *> m_reaperList; -}; - -static ProcessReaper *s_instance = nullptr; -static QMutex s_instanceMutex; - -// Call me with s_instanceMutex locked. -ProcessReaper *ProcessReaper::instance() -{ - if (!s_instance) - s_instance = new ProcessReaper; - return s_instance; -} - -ProcessReaper::ProcessReaper() - : m_private(new ProcessReaperPrivate) -{ - m_private->moveToThread(&m_thread); - QObject::connect(&m_thread, &QThread::finished, m_private, &QObject::deleteLater); - m_thread.start(); - m_thread.moveToThread(qApp->thread()); -} - -ProcessReaper::~ProcessReaper() -{ - if (!QThread::isMainThread()) - qWarning() << "Destructing process reaper from non-main thread."; - - instance()->m_private->waitForFinished(); - m_thread.quit(); - m_thread.wait(); -} - -void ProcessReaper::reap(QProcess *process, int timeoutMs) -{ - if (!process) - return; - - if (QThread::currentThread() != process->thread()) { - qWarning() << "Can't reap process from non-process's thread."; - return; - } - - process->disconnect(); - if (process->state() == QProcess::NotRunning) { - delete process; - return; - } - - // Neither can move object with a parent into a different thread - // nor reaping the process with a parent makes any sense. - process->setParent(nullptr); - - QMutexLocker locker(&s_instanceMutex); - ProcessReaperPrivate *priv = instance()->m_private; - - process->moveToThread(priv->thread()); - ReaperSetup reaperSetup {process, timeoutMs}; - priv->scheduleReap(reaperSetup); -} - -void QProcessDeleter::deleteAll() -{ - QMutexLocker locker(&s_instanceMutex); - delete s_instance; - s_instance = nullptr; -} - -void QProcessDeleter::operator()(QProcess *process) -{ - ProcessReaper::reap(process); -} - -} // namespace Tasking - -#endif // QT_CONFIG(process) - -QT_END_NAMESPACE - -#if QT_CONFIG(process) - -#include "qprocesstask.moc" - -#endif // QT_CONFIG(process) - diff --git a/src/assets/downloader/tasking/qprocesstask.h b/src/assets/downloader/tasking/qprocesstask.h deleted file mode 100644 index 6f2ca4a18e2..00000000000 --- a/src/assets/downloader/tasking/qprocesstask.h +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_QPROCESSTASK_H -#define TASKING_QPROCESSTASK_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include "tasktree.h" - -#include <QtCore/QProcess> - -QT_BEGIN_NAMESPACE - -#if QT_CONFIG(process) - -namespace Tasking { - -// Deleting a running QProcess may block the caller thread up to 30 seconds and issue warnings. -// To avoid these issues we move the running QProcess into a separate thread -// managed by the internal ProcessReaper, instead of deleting it immediately. -// Inside the ProcessReaper's thread we try to finish the process in a most gentle way: -// we call QProcess::terminate() with 500 ms timeout, and if the process is still running -// after this timeout passed, we call QProcess::kill() and wait for the process to finish. -// All these handlings are done is a separate thread, so the main thread doesn't block at all -// when the QProcessTask is destructed. -// Finally, on application quit, QProcessDeleter::deleteAll() should be called in order -// to synchronize all the processes being still potentially reaped in a separate thread. -// The call to QProcessDeleter::deleteAll() is blocking in case some processes -// are still being reaped. -// This strategy seems most sensible, since when passing the running QProcess into the -// ProcessReaper we don't block immediately, but postpone the possible (not certain) block -// until the end of an application. -// In this way we terminate the running processes in the most safe way and keep the main thread -// responsive. That's a common case when the running application wants to terminate the QProcess -// immediately (e.g. on Cancel button pressed), without keeping and managing the handle -// to the still running QProcess. - -// The implementation of the internal reaper is inspired by the Utils::ProcessReaper taken -// from the QtCreator codebase. - -class TASKING_EXPORT QProcessDeleter -{ -public: - // Blocking, should be called after all QProcessAdapter instances are deleted. - static void deleteAll(); - void operator()(QProcess *process); -}; - -class TASKING_EXPORT QProcessAdapter : public TaskAdapter<QProcess, QProcessDeleter> -{ -private: - void start() final { - connect(task(), &QProcess::finished, this, [this] { - const bool success = task()->exitStatus() == QProcess::NormalExit - && task()->error() == QProcess::UnknownError - && task()->exitCode() == 0; - Q_EMIT done(toDoneResult(success)); - }); - connect(task(), &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { - if (error != QProcess::FailedToStart) - return; - Q_EMIT done(DoneResult::Error); - }); - task()->start(); - } -}; - -using QProcessTask = CustomTask<QProcessAdapter>; - -} // namespace Tasking - -#endif // QT_CONFIG(process) - -QT_END_NAMESPACE - -#endif // TASKING_QPROCESSTASK_H diff --git a/src/assets/downloader/tasking/tasking_global.h b/src/assets/downloader/tasking/tasking_global.h deleted file mode 100644 index 57f0b7fefef..00000000000 --- a/src/assets/downloader/tasking/tasking_global.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_GLOBAL_H -#define TASKING_GLOBAL_H - -#include <QtCore/qglobal.h> - -QT_BEGIN_NAMESPACE - -// #if defined(QT_SHARED) || !defined(QT_STATIC) -// # if defined(TASKING_LIBRARY) -// # define TASKING_EXPORT Q_DECL_EXPORT -// # else -// # define TASKING_EXPORT Q_DECL_IMPORT -// # endif -// #else -// # define TASKING_EXPORT -// #endif - -#define TASKING_EXPORT - -QT_END_NAMESPACE - -#endif // TASKING_GLOBAL_H diff --git a/src/assets/downloader/tasking/tasktree.cpp b/src/assets/downloader/tasking/tasktree.cpp deleted file mode 100644 index 37064a3e714..00000000000 --- a/src/assets/downloader/tasking/tasktree.cpp +++ /dev/null @@ -1,3701 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tasktree.h" - -#include "barrier.h" -#include "conditional.h" - -#include <QtCore/QDebug> -#include <QtCore/QEventLoop> -#include <QtCore/QFutureWatcher> -#include <QtCore/QHash> -#include <QtCore/QMetaEnum> -#include <QtCore/QMutex> -#include <QtCore/QPointer> -#include <QtCore/QPromise> -#include <QtCore/QSet> -#include <QtCore/QTime> -#include <QtCore/QTimer> - -using namespace Qt::StringLiterals; -using namespace std::chrono; - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// That's cut down qtcassert.{c,h} to avoid the dependency. -#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__)) -#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0) -#define QT_CHECK(cond) if (cond) {} else { QT_STRING(#cond); } do {} while (0) - -class Guard -{ - Q_DISABLE_COPY(Guard) -public: - Guard() = default; - ~Guard() { QT_CHECK(m_lockCount == 0); } - bool isLocked() const { return m_lockCount; } -private: - int m_lockCount = 0; - friend class GuardLocker; -}; - -class GuardLocker -{ - Q_DISABLE_COPY(GuardLocker) -public: - GuardLocker(Guard &guard) : m_guard(guard) { ++m_guard.m_lockCount; } - ~GuardLocker() { --m_guard.m_lockCount; } -private: - Guard &m_guard; -}; - -/*! - \module TaskingSolution - \title Tasking Solution - \ingroup solutions-modules - \brief Contains a general purpose Tasking solution. - - The Tasking solution depends on Qt only, and doesn't depend on any \QC specific code. -*/ - -/*! - \namespace Tasking - \inmodule TaskingSolution - \brief The Tasking namespace encloses all classes and global functions of the Tasking solution. -*/ - -/*! - \class Tasking::TaskInterface - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief TaskInterface is the abstract base class for implementing custom task adapters. - \reentrant - - To implement a custom task adapter, derive your adapter from the - \c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys - the custom task instance and associates the adapter with a given \c Task type. -*/ - -/*! - \fn virtual void TaskInterface::start() - - This method is called by the running TaskTree for starting the \c Task instance. - Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the - associated task. - - Use TaskAdapter::task() to access the associated \c Task instance. - - \sa done(), TaskAdapter::task() -*/ - -/*! - \fn void TaskInterface::done(DoneResult result) - - Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished. - Pass DoneResult::Success as a \a result argument when the task finishes with success; - otherwise, when an error occurs, pass DoneResult::Error. -*/ - -/*! - \class Tasking::TaskAdapter - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template for implementing custom task adapters. - \reentrant - - The TaskAdapter class template is responsible for creating a task of the \c Task type, - starting it, and reporting success or an error when the task is finished. - It also associates the adapter with a given \c Task type. - - Reimplement this class with the actual \c Task type to adapt the task's interface - into the general TaskTree's interface for managing the \c Task instances. - - Each subclass needs to provide a public default constructor, - implement the start() method, and emit the done() signal when the task is finished. - Use task() to access the associated \c Task instance. - - To use your task adapter inside the task tree, create an alias to the - Tasking::CustomTask template passing your task adapter as a template parameter: - \code - // Defines actual worker - class Worker {...}; - - // Adapts Worker's interface to work with task tree - class WorkerTaskAdapter : public TaskAdapter<Worker> {...}; - - // Defines WorkerTask as a new custom task type to be placed inside Group items - using WorkerTask = CustomTask<WorkerTaskAdapter>; - \endcode - - Optionally, you may pass a custom \c Deleter for the associated \c Task - as a second template parameter of your \c TaskAdapter subclass. - When the \c Deleter parameter is omitted, the \c std::default_delete<Task> is used by default. - The custom \c Deleter is useful when the destructor of the running \c Task - may potentially block the caller thread. Instead of blocking, the custom deleter may move - the running task into a separate thread and implement the blocking destruction there. - In this way, the fast destruction (seen from the caller thread) of the running task - with a blocking destructor may be achieved. - - For more information on implementing the custom task adapters, refer to \l {Task Adapters}. - - \sa start(), done(), task() -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> TaskAdapter<Task, Deleter>::TaskAdapter<Task, Deleter>() - - Creates a task adapter for the given \c Task type. - - Internally, it creates an instance of \c Task, which is accessible via the task() method. - The optionally provided \c Deleter is used instead of the \c Task destructor. - When \c Deleter is omitted, the \c std::default_delete<Task> is used by default. - - \sa task() -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() - - Returns the pointer to the associated \c Task instance. -*/ - -/*! - \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() const - \overload - - Returns the \c const pointer to the associated \c Task instance. -*/ - -/*! - \class Tasking::Storage - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template for custom data exchange in the running task tree. - \reentrant - - The Storage class template is responsible for dynamically creating and destructing objects - of the custom \c StorageStruct type. The creation and destruction are managed by the - running task tree. If a Storage object is placed inside a \l {Tasking::Group} {Group} element, - the running task tree creates the \c StorageStruct object when the group is started and before - the group's setup handler is called. Later, whenever any handler inside this group is called, - the task tree activates the previously created instance of the \c StorageStruct object. - This includes all tasks' and groups' setup and done handlers inside the group where the - Storage object was placed, also within the nested groups. - When a copy of the Storage object is passed to the handler via the lambda capture, - the handler may access the instance activated by the running task tree via the - \l {Tasking::Storage::operator->()} {operator->()}, - \l {Tasking::Storage::operator*()} {operator*()}, or activeStorage() method. - If two handlers capture the same Storage object, one of them may store a custom data there, - and the other may read it afterwards. - When the group is finished, the previously created instance of the \c StorageStruct - object is destroyed after the group's done handler is called. - - An example of data exchange between tasks: - - \code - const Storage<QString> storage; - - const auto onFirstDone = [storage](const Task &task) { - // Assings QString, taken from the first task result, to the active QString instance - // of the Storage object. - *storage = task.getResultAsString(); - }; - - const auto onSecondSetup = [storage](Task &task) { - // Reads QString from the active QString instance of the Storage object and use it to - // configure the second task before start. - task.configureWithString(*storage); - }; - - const Group root { - // The running task tree creates QString instance when root in entered - storage, - // The done handler of the first task stores the QString in the storage - TaskItem(..., onFirstDone), - // The setup handler of the second task reads the QString from the storage - TaskItem(onSecondSetup, ...) - }; - \endcode - - Since the root group executes its tasks sequentially, the \c onFirstDone handler - is always called before the \c onSecondSetup handler. This means that the QString data, - read from the \c storage inside the \c onSecondSetup handler's body, - has already been set by the \c onFirstDone handler. - You can always rely on it in \l {Tasking::sequential} {sequential} execution mode. - - The Storage internals are shared between all of its copies. That is why the copies of the - Storage object inside the handlers' lambda captures still refer to the same Storage instance. - You may place multiple Storage objects inside one \l {Tasking::Group} {Group} element, - provided that they do not include copies of the same Storage object. - Otherwise, an assert is triggered at runtime that includes an error message. - However, you can place copies of the same Storage object in different - \l {Tasking::Group} {Group} elements of the same recipe. In this case, the running task - tree will create multiple instances of the \c StorageStruct objects (one for each copy) - and storage shadowing will take place. Storage shadowing works in a similar way - to C++ variable shadowing inside the nested blocks of code: - - \code - Storage<QString> storage; - - const Group root { - storage, // Top copy, 1st instance of StorageStruct - onGroupSetup([storage] { ... }), // Top copy is active - Group { - storage, // Nested copy, 2nd instance of StorageStruct, - // shadows Top copy - onGroupSetup([storage] { ... }), // Nested copy is active - }, - Group { - onGroupSetup([storage] { ... }), // Top copy is active - } - }; - \endcode - - The Storage objects may also be used for passing the initial data to the executed task tree, - and for reading the final data out of the task tree before it finishes. - To do this, use \l {TaskTree::onStorageSetup()} {onStorageSetup()} or - \l {TaskTree::onStorageDone()} {onStorageDone()}, respectively. - - \note If you use an unreachable Storage object inside the handler, - because you forgot to place the storage in the recipe, - or placed it, but not in any handler's ancestor group, - you may expect a crash, preceded by the following message: - \e {The referenced storage is not reachable in the running tree. - A nullptr will be returned which might lead to a crash in the calling code. - It is possible that no storage was added to the tree, - or the storage is not reachable from where it is referenced.} -*/ - -/*! - \fn template <typename StorageStruct> Storage<StorageStruct>::Storage<StorageStruct>() - - Creates a storage for the given \c StorageStruct type. - - \note All copies of \c this object are considered to be the same Storage instance. -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct &Storage<StorageStruct>::operator*() const noexcept - - Returns a \e reference to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned reference is valid as long as the group that created this instance - is still running. - - \sa activeStorage(), operator->() -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::operator->() const noexcept - - Returns a \e pointer to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned pointer is valid as long as the group that created this instance - is still running. - - \sa activeStorage(), operator*() -*/ - -/*! - \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::activeStorage() const - - Returns a \e pointer to the active \c StorageStruct object, created by the running task tree. - Use this function only from inside the handler body of any GroupItem element placed - in the recipe, otherwise you may expect a crash. - Make sure that Storage is placed in any group ancestor of the handler's group item. - - \note The returned pointer is valid as long as the group that created this instance - is still running. - - \sa operator->(), operator*() -*/ - -/*! - \typealias Tasking::GroupItems - - Type alias for QList<GroupItem>. -*/ - -/*! - \class Tasking::GroupItem - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief GroupItem represents the basic element that may be a part of any Group. - \reentrant - - GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}. - It encapsulates the functionality provided by any GroupItem's subclass. - It is a value type and it is safe to copy the GroupItem instance, - even when it is originally created via the subclass' constructor. - - There are four main kinds of GroupItem: - \table - \header - \li GroupItem Kind - \li Brief Description - \row - \li \l CustomTask - \li Defines asynchronous task type and task's start, done, and error handlers. - Aliased with a unique task name, such as, \c ConcurrentCallTask<ResultType> - or \c NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree. - \row - \li \l {Tasking::Group} {Group} - \li A container for other group items. Since the group is of the GroupItem type, - it's possible to nest it inside another group. The group is seen by its parent - as a single asynchronous task. - \row - \li GroupItem containing \l {Tasking::Storage} {Storage} - \li Enables the child tasks of a group to exchange data. When GroupItem containing - \l {Tasking::Storage} {Storage} is placed inside a group, the task tree instantiates - the storage's data object just before the group is entered, - and destroys it just after the group is left. - \row - \li Other group control items - \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or - \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior. - The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()} or - \l {Tasking::onGroupDone()} {onGroupDone()} define custom handlers called when - the group starts or ends execution. - \endtable -*/ - -/*! - \fn template <typename StorageStruct> GroupItem::GroupItem(const Storage<StorageStruct> &storage) - - Constructs a \c GroupItem element holding the \a storage object. - - When the \l {Tasking::Group} {Group} element containing \e this GroupItem is entered - by the running task tree, an instance of the \c StorageStruct is created dynamically. - - When that group is about to be left after its execution, the previously instantiated - \c StorageStruct is deleted. - - The dynamically created instance of \c StorageStruct is accessible from inside any - handler body of the parent \l {Tasking::Group} {Group} element, - including nested groups and its tasks, via the - \l {Tasking::Storage::operator->()} {Storage::operator->()}, - \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method. - - \sa {Tasking::Storage} {Storage} -*/ - -/*! - \fn GroupItem::GroupItem(const GroupItems &items) - - Constructs a \c GroupItem element with a given list of \a items. - - When this \c GroupItem element is parsed by the TaskTree, it is simply replaced with - its \a items. - - This constructor is useful when constructing a \l {Tasking::Group} {Group} element with - lists of \c GroupItem elements: - - \code - static QList<GroupItems> getItems(); - - ... - - const Group root { - parallel, - finishAllAndSuccess, - getItems(), // OK, getItems() list is wrapped into a single GroupItem element - onGroupSetup(...), - onGroupDone(...) - }; - \endcode - - If you want to create a subtree, use \l {Tasking::Group} {Group} instead. - - \note Don't confuse this \c GroupItem with the \l {Tasking::Group} {Group} element, as - \l {Tasking::Group} {Group} keeps its children nested - after being parsed by the task tree, while this \c GroupItem does not. - - \sa {Tasking::Group} {Group} -*/ - -/*! - \fn Tasking::GroupItem(std::initializer_list<GroupItem> items) - \overload - \sa GroupItem(const GroupItems &items) -*/ - -/*! - \class Tasking::Group - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Group represents the basic element for composing declarative recipes describing - how to execute and handle a nested tree of asynchronous tasks. - \reentrant - - Group is a container for other group items. It encloses child tasks into one unit, - which is seen by the group's parent as a single, asynchronous task. - Since Group is of the GroupItem type, it may also be a child of Group. - - Insert child tasks into the group by using aliased custom task names, such as, - \c ConcurrentCallTask<ResultType> or \c NetworkQueryTask: - - \code - const Group group { - NetworkQueryTask(...), - ConcurrentCallTask<int>(...) - }; - \endcode - - The group's behavior may be customized by inserting the items returned by - \l {Tasking::parallelLimit()} {parallelLimit()} or - \l {Tasking::workflowPolicy()} {workflowPolicy()} functions: - - \code - const Group group { - parallel, - continueOnError, - NetworkQueryTask(...), - NetworkQueryTask(...) - }; - \endcode - - The group may contain nested groups: - - \code - const Group group { - finishAllAndSuccess, - NetworkQueryTask(...), - Group { - NetworkQueryTask(...), - Group { - parallel, - NetworkQueryTask(...), - NetworkQueryTask(...), - } - ConcurrentCallTask<QString>(...) - } - }; - \endcode - - The group may dynamically instantiate a custom storage structure, which may be used for - inter-task data exchange: - - \code - struct MyCustomStruct { QByteArray data; }; - - Storage<MyCustomStruct> storage; - - const auto onFirstSetup = [](NetworkQuery &task) { ... }; - const auto onFirstDone = [storage](const NetworkQuery &task) { - // storage-> gives a pointer to MyCustomStruct instance, - // created dynamically by the running task tree. - storage->data = task.reply()->readAll(); - }; - const auto onSecondSetup = [storage](ConcurrentCall<QImage> &task) { - // storage-> gives a pointer to MyCustomStruct. Since the group is sequential, - // the stored MyCustomStruct was already updated inside the onFirstDone handler. - const QByteArray storedData = storage->data; - }; - - const Group group { - // When the group is entered by a running task tree, it creates MyCustomStruct - // instance dynamically. It is later accessible from all handlers via - // the *storage or storage-> operators. - sequential, - storage, - NetworkQueryTask(onFirstSetup, onFirstDone, CallDoneIf::Success), - ConcurrentCallTask<QImage>(onSecondSetup) - }; - \endcode -*/ - -/*! - \fn Group::Group(const GroupItems &children) - - Constructs a group with a given list of \a children. - - This constructor is useful when the child items of the group are not known at compile time, - but later, at runtime: - - \code - const QStringList sourceList = ...; - - GroupItems groupItems { parallel }; - - for (const QString &source : sourceList) { - const NetworkQueryTask task(...); // use source for setup handler - groupItems << task; - } - - const Group group(groupItems); - \endcode -*/ - -/*! - \fn Group::Group(std::initializer_list<GroupItem> children) - - Constructs a group from \c std::initializer_list given by \a children. - - This constructor is useful when all child items of the group are known at compile time: - - \code - const Group group { - finishAllAndSuccess, - NetworkQueryTask(...), - Group { - NetworkQueryTask(...), - Group { - parallel, - NetworkQueryTask(...), - NetworkQueryTask(...), - } - ConcurrentCallTask<QString>(...) - } - }; - \endcode -*/ - -/*! - \class Tasking::Sync - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Synchronously executes a custom handler between other tasks. - \reentrant - - \c Sync is useful when you want to execute an additional handler between other tasks. - \c Sync is seen by its parent \l {Tasking::Group} {Group} as any other task. - Avoid long-running execution of the \c Sync's handler body, since it is executed - synchronously from the caller thread. If that is unavoidable, consider using - \c ConcurrentCallTask instead. -*/ - -/*! - \fn template <typename Handler> Sync::Sync(Handler &&handler) - - Constructs an element that executes a passed \a handler synchronously. - The \c Handler is of the \c std::function<DoneResult()> type. - The DoneResult value, returned by the \a handler, is considered during parent group's - \l {workflowPolicy} {workflow policy} resolution. - Optionally, the shortened form of \c std::function<void()> is also accepted. - In this case, it's assumed that the return value is DoneResult::Success. - - The passed \a handler executes synchronously from the caller thread, so avoid a long-running - execution of the handler body. Otherwise, consider using \c ConcurrentCallTask. - - \note The \c Sync element is not counted as a task when reporting task tree progress, - and is not included in TaskTree::taskCount() or TaskTree::progressMaximum(). -*/ - -/*! - \class Tasking::CustomTask - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief A class template used for declaring custom task items and defining their setup - and done handlers. - \reentrant - - Describes custom task items within task tree recipes. - - Custom task names are aliased with unique names using the \c CustomTask template - with a given TaskAdapter subclass as a template parameter. - For example, \c ConcurrentCallTask<T> is an alias to the \c CustomTask that is defined - to work with \c ConcurrentCall<T> as an associated task class. - The following table contains example custom tasks and their associated task classes: - - \table - \header - \li Aliased Task Name (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li ConcurrentCallTask<ReturnType> - \li ConcurrentCall<ReturnType> - \li Starts an asynchronous task. Runs in a separate thread. - \row - \li NetworkQueryTask - \li NetworkQuery - \li Sends a network query. - \row - \li TaskTreeTask - \li TaskTree - \li Starts a nested task tree. - \row - \li TimeoutTask - \li \c std::chrono::milliseconds - \li Starts a timer. - \row - \li WaitForBarrierTask - \li MultiBarrier<Limit> - \li Starts an asynchronous task waiting for the barrier to pass. - \endtable -*/ - -/*! - \typealias Tasking::CustomTask::Task - - Type alias for the task type associated with the custom task's \c Adapter. -*/ - -/*! - \typealias Tasking::CustomTask::Deleter - - Type alias for the task's type deleter associated with the custom task's \c Adapter. -*/ - -/*! - \typealias Tasking::CustomTask::TaskSetupHandler - - Type alias for \c std::function<SetupResult(Task &)>. - - The \c TaskSetupHandler is an optional argument of a custom task element's constructor. - Any function with the above signature, when passed as a task setup handler, - will be called by the running task tree after the task is created and before it is started. - - Inside the body of the handler, you may configure the task according to your needs. - The additional parameters, including storages, may be passed to the handler - via the lambda capture. - You can decide dynamically whether the task should be started or skipped with - success or an error. - - \note Do not start the task inside the start handler by yourself. Leave it for TaskTree, - otherwise the behavior is undefined. - - The return value of the handler instructs the running task tree on how to proceed - after the handler's invocation is finished. The return value of SetupResult::Continue - instructs the task tree to continue running, that is, to execute the associated \c Task. - The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError - instructs the task tree to skip the task's execution and finish it immediately with - success or an error, respectively. - - When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError, - the task's done handler (if provided) isn't called afterwards. - - The constructor of a custom task accepts also functions in the shortened form of - \c std::function<void(Task &)>, that is, the return value is \c void. - In this case, it's assumed that the return value is SetupResult::Continue. - - \sa CustomTask(), TaskDoneHandler, GroupSetupHandler -*/ - -/*! - \typealias Tasking::CustomTask::TaskDoneHandler - - Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult. - - The \c TaskDoneHandler is an optional argument of a custom task element's constructor. - Any function with the above signature, when passed as a task done handler, - will be called by the running task tree after the task execution finished and before - the final result of the execution is reported back to the parent group. - - Inside the body of the handler you may retrieve the final data from the finished task. - The additional parameters, including storages, may be passed to the handler - via the lambda capture. - It is also possible to decide dynamically whether the task should finish with its return - value, or the final result should be tweaked. - - The DoneWith argument is optional and your done handler may omit it. - When provided, it holds the info about the final result of a task that will be - reported to its parent. - - If you do not plan to read any data from the finished task, - you may omit the \c {const Task &} argument. - - The returned DoneResult value is optional and your handler may return \c void instead. - In this case, the final result of the task will be equal to the value indicated by - the DoneWith argument. When the handler returns the DoneResult value, - the task's final result may be tweaked inside the done handler's body by the returned value. - - For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed, - and the task finishes unconditionally with the passed value of DoneResult. - - \sa CustomTask(), TaskSetupHandler, GroupDoneHandler -*/ - -/*! - \fn template <typename Adapter> template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> CustomTask<Adapter>::CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - - Constructs a \c CustomTask instance and attaches the \a setup and \a done handlers to the task. - When the running task tree is about to start the task, - it instantiates the associated \l Task object, invokes \a setup handler with a \e reference - to the created task, and starts it. When the running task finishes, - the task tree invokes a \a done handler, with a \c const \e reference to the created task. - - The passed \a setup handler is of the \l TaskSetupHandler type. For example: - - \code - static void parseAndLog(const QString &input); - - ... - - const QString input = ...; - - const auto onFirstSetup = [input](ConcurrentCall<void> &task) { - if (input == "Skip") - return SetupResult::StopWithSuccess; // This task won't start, the next one will - if (input == "Error") - return SetupResult::StopWithError; // This task and the next one won't start - task.setConcurrentCallData(parseAndLog, input); - // This task will start, and the next one will start after this one finished with success - return SetupResult::Continue; - }; - - const auto onSecondSetup = [input](ConcurrentCall<void> &task) { - task.setConcurrentCallData(parseAndLog, input); - }; - - const Group group { - ConcurrentCallTask<void>(onFirstSetup), - ConcurrentCallTask<void>(onSecondSetup) - }; - \endcode - - The \a done handler is of the \l TaskDoneHandler type. - By default, the \a done handler is invoked whenever the task finishes. - Pass a non-default value for the \a callDoneIf argument when you want the handler to be called - only on a successful or failed execution. - - \sa TaskSetupHandler, TaskDoneHandler -*/ - -/*! - \enum Tasking::WorkflowPolicy - - This enum describes the possible behavior of the Group element when any group's child task - finishes its execution. It's also used when the running Group is canceled. - - \value StopOnError - Default. Corresponds to the stopOnError global element. - If any child task finishes with an error, the group stops and finishes with an error. - If all child tasks finished with success, the group finishes with success. - If a group is empty, it finishes with success. - \value ContinueOnError - Corresponds to the continueOnError global element. - Similar to stopOnError, but in case any child finishes with an error, - the execution continues until all tasks finish, and the group reports an error - afterwards, even when some other tasks in the group finished with success. - If all child tasks finish successfully, the group finishes with success. - If a group is empty, it finishes with success. - \value StopOnSuccess - Corresponds to the stopOnSuccess global element. - If any child task finishes with success, the group stops and finishes with success. - If all child tasks finished with an error, the group finishes with an error. - If a group is empty, it finishes with an error. - \value ContinueOnSuccess - Corresponds to the continueOnSuccess global element. - Similar to stopOnSuccess, but in case any child finishes successfully, - the execution continues until all tasks finish, and the group reports success - afterwards, even when some other tasks in the group finished with an error. - If all child tasks finish with an error, the group finishes with an error. - If a group is empty, it finishes with an error. - \value StopOnSuccessOrError - Corresponds to the stopOnSuccessOrError global element. - The group starts as many tasks as it can. When any task finishes, - the group stops and reports the task's result. - Useful only in parallel mode. - In sequential mode, only the first task is started, and when finished, - the group finishes too, so the other tasks are always skipped. - If a group is empty, it finishes with an error. - \value FinishAllAndSuccess - Corresponds to the finishAllAndSuccess global element. - The group executes all tasks and ignores their return results. When all - tasks finished, the group finishes with success. - If a group is empty, it finishes with success. - \value FinishAllAndError - Corresponds to the finishAllAndError global element. - The group executes all tasks and ignores their return results. When all - tasks finished, the group finishes with an error. - If a group is empty, it finishes with an error. - - Whenever a child task's result causes the Group to stop, that is, - in case of StopOnError, StopOnSuccess, or StopOnSuccessOrError policies, - the Group cancels the other running child tasks (if any - for example in parallel mode), - and skips executing tasks it has not started yet (for example, in the sequential mode - - those, that are placed after the failed task). Both canceling and skipping child tasks - may happen when parallelLimit() is used. - - The table below summarizes the differences between various workflow policies: - - \table - \header - \li \l WorkflowPolicy - \li Executes all child tasks - \li Result - \li Result when the group is empty - \row - \li StopOnError - \li Stops when any child task finished with an error and reports an error - \li An error when at least one child task failed, success otherwise - \li Success - \row - \li ContinueOnError - \li Yes - \li An error when at least one child task failed, success otherwise - \li Success - \row - \li StopOnSuccess - \li Stops when any child task finished with success and reports success - \li Success when at least one child task succeeded, an error otherwise - \li An error - \row - \li ContinueOnSuccess - \li Yes - \li Success when at least one child task succeeded, an error otherwise - \li An error - \row - \li StopOnSuccessOrError - \li Stops when any child task finished and reports child task's result - \li Success or an error, depending on the finished child task's result - \li An error - \row - \li FinishAllAndSuccess - \li Yes - \li Success - \li Success - \row - \li FinishAllAndError - \li Yes - \li An error - \li An error - \endtable - - If a child of a group is also a group, the child group runs its tasks according to its own - workflow policy. When a parent group stops the running child group because - of parent group's workflow policy, that is, when the StopOnError, StopOnSuccess, - or StopOnSuccessOrError policy was used for the parent, - the child group's result is reported according to the - \b Result column and to the \b {child group's workflow policy} row in the table above. -*/ - -/*! - \variable Tasking::nullItem - - A convenient global group's element indicating a no-op item. - - This is useful in conditional expressions to indicate the absence of an optional element: - - \code - const ExecutableItem task = ...; - const std::optional<ExecutableItem> optionalTask = ...; - - Group group { - task, - optionalTask ? *optionalTask : nullItem - }; - \endcode -*/ - -/*! - \variable Tasking::successItem - - A convenient global executable element containing an empty, successful, synchronous task. - - This is useful in if-statements to indicate that a branch ends with success: - - \code - const ExecutableItem conditionalTask = ...; - - Group group { - stopOnDone, - If (conditionalTask) >> Then { - ... - } >> Else { - successItem - }, - nextTask - }; - \endcode - - In the above example, if the \c conditionalTask finishes with an error, the \c Else branch - is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped - (because of the stopOnDone workflow policy of the \c group) - and the \c group finishes with success. - - \sa errorItem -*/ - -/*! - \variable Tasking::errorItem - - A convenient global executable element containing an empty, erroneous, synchronous task. - - This is useful in if-statements to indicate that a branch ends with an error: - - \code - const ExecutableItem conditionalTask = ...; - - Group group { - stopOnError, - If (conditionalTask) >> Then { - ... - } >> Else { - errorItem - }, - nextTask - }; - \endcode - - In the above example, if the \c conditionalTask finishes with an error, the \c Else branch - is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped - (because of the stopOnError workflow policy of the \c group) - and the \c group finishes with an error. - - \sa successItem -*/ - -/*! - \variable Tasking::sequential - A convenient global group's element describing the sequential execution mode. - - This is the default execution mode of the Group element. - - When a Group has no execution mode, it runs in the sequential mode. - All the direct child tasks of a group are started in a chain, so that when one task finishes, - the next one starts. This enables you to pass the results from the previous task - as input to the next task before it starts. This mode guarantees that the next task - is started only after the previous task finishes. - - \sa parallel, parallelLimit() -*/ - -/*! - \variable Tasking::parallel - A convenient global group's element describing the parallel execution mode. - - All the direct child tasks of a group are started after the group is started, - without waiting for the previous child tasks to finish. - In this mode, all child tasks run simultaneously. - - \sa sequential, parallelLimit() -*/ - -/*! - \variable Tasking::parallelIdealThreadCountLimit - A convenient global group's element describing the parallel execution mode with a limited - number of tasks running simultanously. The limit is equal to the ideal number of threads - excluding the calling thread. - - This is a shortcut to: - \code - parallelLimit(qMax(QThread::idealThreadCount() - 1, 1)) - \endcode - - \sa parallel, parallelLimit() -*/ - -/*! - \variable Tasking::stopOnError - A convenient global group's element describing the StopOnError workflow policy. - - This is the default workflow policy of the Group element. -*/ - -/*! - \variable Tasking::continueOnError - A convenient global group's element describing the ContinueOnError workflow policy. -*/ - -/*! - \variable Tasking::stopOnSuccess - A convenient global group's element describing the StopOnSuccess workflow policy. -*/ - -/*! - \variable Tasking::continueOnSuccess - A convenient global group's element describing the ContinueOnSuccess workflow policy. -*/ - -/*! - \variable Tasking::stopOnSuccessOrError - A convenient global group's element describing the StopOnSuccessOrError workflow policy. -*/ - -/*! - \variable Tasking::finishAllAndSuccess - A convenient global group's element describing the FinishAllAndSuccess workflow policy. -*/ - -/*! - \variable Tasking::finishAllAndError - A convenient global group's element describing the FinishAllAndError workflow policy. -*/ - -/*! - \enum Tasking::SetupResult - - This enum is optionally returned from the group's or task's setup handler function. - It instructs the running task tree on how to proceed after the setup handler's execution - finished. - \value Continue - Default. The group's or task's execution continues normally. - When a group's or task's setup handler returns void, it's assumed that - it returned Continue. - \value StopWithSuccess - The group's or task's execution stops immediately with success. - When returned from the group's setup handler, all child tasks are skipped, - and the group's onGroupDone() handler is invoked with DoneWith::Success. - The group reports success to its parent. The group's workflow policy is ignored. - When returned from the task's setup handler, the task isn't started, - its done handler isn't invoked, and the task reports success to its parent. - \value StopWithError - The group's or task's execution stops immediately with an error. - When returned from the group's setup handler, all child tasks are skipped, - and the group's onGroupDone() handler is invoked with DoneWith::Error. - The group reports an error to its parent. The group's workflow policy is ignored. - When returned from the task's setup handler, the task isn't started, - its error handler isn't invoked, and the task reports an error to its parent. -*/ - -/*! - \enum Tasking::DoneResult - - This enum is optionally returned from the group's or task's done handler function. - When the done handler doesn't return any value, that is, its return type is \c void, - its final return value is automatically deduced by the running task tree and reported - to its parent group. - - When the done handler returns the DoneResult, you can tweak the final return value - inside the handler. - - When the DoneResult is returned by the group's done handler, the group's workflow policy - is ignored. - - This enum is also used inside the TaskInterface::done() signal and it indicates whether - the task finished with success or an error. - - \value Success - The group's or task's execution ends with success. - \value Error - The group's or task's execution ends with an error. -*/ - -/*! - \enum Tasking::DoneWith - - This enum is an optional argument for the group's or task's done handler. - It indicates whether the group or task finished with success or an error, or it was canceled. - - It is also used as an argument inside the TaskTree::done() signal, - indicating the final result of the TaskTree execution. - - \value Success - The group's or task's execution ended with success. - \value Error - The group's or task's execution ended with an error. - \value Cancel - The group's or task's execution was canceled. This happens when the user calls - TaskTree::cancel() for the running task tree or when the group's workflow policy - results in canceling some of its running children. - Tweaking the done handler's final result by returning Tasking::DoneResult from - the handler is no-op when the group's or task's execution was canceled. -*/ - -/*! - \enum Tasking::CallDoneIf - - This enum is an optional argument for the \l onGroupDone() element or custom task's constructor. - It instructs the task tree on when the group's or task's done handler should be invoked. - - \value SuccessOrError - The done handler is always invoked. - \value Success - The done handler is invoked only after successful execution, - that is, when DoneWith::Success. - \value Error - The done handler is invoked only after failed execution, - that is, when DoneWith::Error or when DoneWith::Cancel. -*/ - -/*! - \typealias Tasking::GroupItem::GroupSetupHandler - - Type alias for \c std::function<SetupResult()>. - - The \c GroupSetupHandler is an argument of the onGroupSetup() element. - Any function with the above signature, when passed as a group setup handler, - will be called by the running task tree when the group execution starts. - - The return value of the handler instructs the running group on how to proceed - after the handler's invocation is finished. The default return value of SetupResult::Continue - instructs the group to continue running, that is, to start executing its child tasks. - The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError - instructs the group to skip the child tasks' execution and finish immediately with - success or an error, respectively. - - When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError, - the group's done handler (if provided) is called synchronously immediately afterwards. - - \note Even if the group setup handler returns StopWithSuccess or StopWithError, - the group's done handler is invoked. This behavior differs from that of task done handler - and might change in the future. - - The onGroupSetup() element accepts also functions in the shortened form of - \c std::function<void()>, that is, the return value is \c void. - In this case, it's assumed that the return value is SetupResult::Continue. - - \sa onGroupSetup(), GroupDoneHandler, CustomTask::TaskSetupHandler -*/ - -/*! - \typealias Tasking::GroupItem::GroupDoneHandler - - Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult. - - The \c GroupDoneHandler is an argument of the onGroupDone() element. - Any function with the above signature, when passed as a group done handler, - will be called by the running task tree when the group execution ends. - - The DoneWith argument is optional and your done handler may omit it. - When provided, it holds the info about the final result of a group that will be - reported to its parent. - - The returned DoneResult value is optional and your handler may return \c void instead. - In this case, the final result of the group will be equal to the value indicated by - the DoneWith argument. When the handler returns the DoneResult value, - the group's final result may be tweaked inside the done handler's body by the returned value. - - For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed, - and the group finishes unconditionally with the passed value of DoneResult, - ignoring the group's workflow policy. - - \sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler -*/ - -/*! - \fn template <typename Handler> GroupItem onGroupSetup(Handler &&handler) - - Constructs a group's element holding the group setup handler. - The \a handler is invoked whenever the group starts. - - The passed \a handler is either of the \c std::function<SetupResult()> or the - \c std::function<void()> type. For more information on a possible handler type, refer to - \l {GroupItem::GroupSetupHandler}. - - When the \a handler is invoked, none of the group's child tasks are running yet. - - If a group contains the Storage elements, the \a handler is invoked - after the storages are constructed, so that the \a handler may already - perform some initial modifications to the active storages. - - \sa GroupItem::GroupSetupHandler, onGroupDone() -*/ - -/*! - \fn template <typename Handler> GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - - Constructs a group's element holding the group done handler. - By default, the \a handler is invoked whenever the group finishes. - Pass a non-default value for the \a callDoneIf argument when you want the handler to be called - only on a successful or failed execution. - Depending on the group's workflow policy, this handler may also be called - when the running group is canceled (e.g. when stopOnError element was used). - - The passed \a handler is of the \c std::function<DoneResult(DoneWith)> type. - Optionally, each of the return DoneResult type or the argument DoneWith type may be omitted - (that is, its return type may be \c void). For more information on a possible handler type, - refer to \l {GroupItem::GroupDoneHandler}. - - When the \a handler is invoked, all of the group's child tasks are already finished. - - If a group contains the Storage elements, the \a handler is invoked - before the storages are destructed, so that the \a handler may still - perform a last read of the active storages' data. - - \sa GroupItem::GroupDoneHandler, onGroupSetup() -*/ - -/*! - Constructs a group's element describing the \l{Execution Mode}{execution mode}. - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. - - For convenience, when appropriate, the \l sequential or \l parallel global elements - may be used instead. - - The \a limit defines the maximum number of direct child tasks running in parallel: - - \list - \li When \a limit equals to 0, there is no limit, and all direct child tasks are started - together, in the oder in which they appear in a group. This means the fully parallel - execution, and the \l parallel element may be used instead. - - \li When \a limit equals to 1, it means that only one child task may run at the time. - This means the sequential execution, and the \l sequential element may be used instead. - In this case, child tasks run in chain, so the next child task starts after - the previous child task has finished. - - \li When other positive number is passed as \a limit, the group's child tasks run - in parallel, but with a limited number of tasks running simultanously. - The \e limit defines the maximum number of tasks running in parallel in a group. - When the group is started, the first batch of tasks is started - (the number of tasks in a batch equals to the passed \a limit, at most), - while the others are kept waiting. When any running task finishes, - the group starts the next remaining one, so that the \e limit of simultaneously - running tasks inside a group isn't exceeded. This repeats on every child task's - finish until all child tasks are started. This enables you to limit the maximum - number of tasks that run simultaneously, for example if running too many processes might - block the machine for a long time. - \endlist - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group, the child group runs its tasks according - to its own execution mode. - - \sa sequential, parallel -*/ - -GroupItem parallelLimit(int limit) -{ - struct ParallelLimit : GroupItem { - ParallelLimit(int limit) : GroupItem({{}, limit}) {} - }; - return ParallelLimit(limit); -} - -/*! - Constructs a group's \l {Workflow Policy} {workflow policy} element for a given \a policy. - - For convenience, global elements may be used instead. - - \sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError, - finishAllAndSuccess, finishAllAndError, WorkflowPolicy -*/ -GroupItem workflowPolicy(WorkflowPolicy policy) -{ - struct WorkflowPolicyItem : GroupItem { - WorkflowPolicyItem(WorkflowPolicy policy) : GroupItem({{}, {}, policy}) {} - }; - return WorkflowPolicyItem(policy); -} - -const GroupItem sequential = parallelLimit(1); -const GroupItem parallel = parallelLimit(0); -const GroupItem parallelIdealThreadCountLimit = parallelLimit(qMax(QThread::idealThreadCount() - 1, 1)); - -const GroupItem stopOnError = workflowPolicy(WorkflowPolicy::StopOnError); -const GroupItem continueOnError = workflowPolicy(WorkflowPolicy::ContinueOnError); -const GroupItem stopOnSuccess = workflowPolicy(WorkflowPolicy::StopOnSuccess); -const GroupItem continueOnSuccess = workflowPolicy(WorkflowPolicy::ContinueOnSuccess); -const GroupItem stopOnSuccessOrError = workflowPolicy(WorkflowPolicy::StopOnSuccessOrError); -const GroupItem finishAllAndSuccess = workflowPolicy(WorkflowPolicy::FinishAllAndSuccess); -const GroupItem finishAllAndError = workflowPolicy(WorkflowPolicy::FinishAllAndError); - -// Keep below the above in order to avoid static initialization fiasco. -const GroupItem nullItem = Group {}; -const ExecutableItem successItem = Group { finishAllAndSuccess }; -const ExecutableItem errorItem = Group { finishAllAndError }; - -Group operator>>(const For &forItem, const Do &doItem) -{ - return {forItem.m_loop, doItem.m_children}; -} - -Group operator>>(const When &whenItem, const Do &doItem) -{ - const SingleBarrier barrier; - - return { - barrier, - parallel, - whenItem.m_barrierKicker(barrier), - Group { - waitForBarrierTask(barrier), - doItem.m_children - } - }; -} - -// Please note the thread_local keyword below guarantees a separate instance per thread. -// The s_activeTaskTrees is currently used internally only and is not exposed in the public API. -// It serves for withLog() implementation now. Add a note here when a new usage is introduced. -static thread_local QList<TaskTree *> s_activeTaskTrees = {}; - -static TaskTree *activeTaskTree() -{ - QT_ASSERT(s_activeTaskTrees.size(), return nullptr); - return s_activeTaskTrees.back(); -} - -DoneResult toDoneResult(bool success) -{ - return success ? DoneResult::Success : DoneResult::Error; -} - -static SetupResult toSetupResult(bool success) -{ - return success ? SetupResult::StopWithSuccess : SetupResult::StopWithError; -} - -static DoneResult toDoneResult(DoneWith doneWith) -{ - return doneWith == DoneWith::Success ? DoneResult::Success : DoneResult::Error; -} - -static DoneWith toDoneWith(DoneResult result) -{ - return result == DoneResult::Success ? DoneWith::Success : DoneWith::Error; -} - -class LoopThreadData -{ - Q_DISABLE_COPY_MOVE(LoopThreadData) - -public: - LoopThreadData() = default; - void pushIteration(int index) - { - m_activeLoopStack.push_back(index); - } - void popIteration() - { - QT_ASSERT(m_activeLoopStack.size(), return); - m_activeLoopStack.pop_back(); - } - int iteration() const - { - QT_ASSERT(m_activeLoopStack.size(), qWarning( - "The referenced loop is not reachable in the running tree. " - "A -1 will be returned which might lead to a crash in the calling code. " - "It is possible that no loop was added to the tree, " - "or the loop is not reachable from where it is referenced."); return -1); - return m_activeLoopStack.last(); - } - -private: - QList<int> m_activeLoopStack; -}; - -class LoopData -{ -public: - LoopThreadData &threadData() { - QMutexLocker lock(&m_threadDataMutex); - return m_threadDataMap.try_emplace(QThread::currentThread()).first->second; - } - - const std::optional<int> m_loopCount = {}; - const Loop::ValueGetter m_valueGetter = {}; - const Loop::Condition m_condition = {}; - QMutex m_threadDataMutex = {}; - // Use std::map on purpose, so that it doesn't invalidate references on modifications. - // Don't optimize it by using std::unordered_map. - std::map<QThread *, LoopThreadData> m_threadDataMap = {}; -}; - -Loop::Loop() - : m_loopData(new LoopData) -{} - -Loop::Loop(int count, const ValueGetter &valueGetter) - : m_loopData(new LoopData{count, valueGetter}) -{} - -Loop::Loop(const Condition &condition) - : m_loopData(new LoopData{{}, {}, condition}) -{} - -int Loop::iteration() const -{ - return m_loopData->threadData().iteration(); -} - -const void *Loop::valuePtr() const -{ - return m_loopData->m_valueGetter(iteration()); -} - -using StoragePtr = void *; - -static constexpr QLatin1StringView s_activeStorageWarning = - "The referenced storage is not reachable in the running tree. " - "A nullptr will be returned which might lead to a crash in the calling code. " - "It is possible that no storage was added to the tree, " - "or the storage is not reachable from where it is referenced."_L1; - -class StorageThreadData -{ - Q_DISABLE_COPY_MOVE(StorageThreadData) - -public: - StorageThreadData() = default; - void pushStorage(StoragePtr storagePtr) - { - m_activeStorageStack.push_back({storagePtr, activeTaskTree()}); - } - void popStorage() - { - QT_ASSERT(m_activeStorageStack.size(), return); - m_activeStorageStack.pop_back(); - } - StoragePtr activeStorage() const - { - QT_ASSERT(m_activeStorageStack.size(), - qWarning().noquote() << s_activeStorageWarning; return nullptr); - const QPair<StoragePtr, TaskTree *> &top = m_activeStorageStack.last(); - QT_ASSERT(top.second == activeTaskTree(), - qWarning().noquote() << s_activeStorageWarning; return nullptr); - return top.first; - } - -private: - QList<QPair<StoragePtr, TaskTree *>> m_activeStorageStack; -}; - -class StorageData -{ -public: - StorageThreadData &threadData() { - QMutexLocker lock(&m_threadDataMutex); - return m_threadDataMap.try_emplace(QThread::currentThread()).first->second; - } - - const StorageBase::StorageConstructor m_constructor = {}; - const StorageBase::StorageDestructor m_destructor = {}; - QMutex m_threadDataMutex = {}; - // Use std::map on purpose, so that it doesn't invalidate references on modifications. - // Don't optimize it by using std::unordered_map. - std::map<QThread *, StorageThreadData> m_threadDataMap = {}; -}; - -StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor) - : m_storageData(new StorageData{ctor, dtor}) -{} - -void *StorageBase::activeStorageVoid() const -{ - return m_storageData->threadData().activeStorage(); -} - -void GroupItem::addChildren(const GroupItems &children) -{ - QT_ASSERT(m_type == Type::Group || m_type == Type::List, - qWarning("Only Group or List may have children, skipping..."); return); - if (m_type == Type::List) { - m_children.append(children); - return; - } - for (const GroupItem &child : children) { - switch (child.m_type) { - case Type::List: - addChildren(child.m_children); - break; - case Type::Group: - m_children.append(child); - break; - case Type::GroupData: - if (child.m_groupData.m_groupHandler.m_setupHandler) { - QT_ASSERT(!m_groupData.m_groupHandler.m_setupHandler, - qWarning("Group setup handler redefinition, overriding...")); - m_groupData.m_groupHandler.m_setupHandler - = child.m_groupData.m_groupHandler.m_setupHandler; - } - if (child.m_groupData.m_groupHandler.m_doneHandler) { - QT_ASSERT(!m_groupData.m_groupHandler.m_doneHandler, - qWarning("Group done handler redefinition, overriding...")); - m_groupData.m_groupHandler.m_doneHandler - = child.m_groupData.m_groupHandler.m_doneHandler; - m_groupData.m_groupHandler.m_callDoneIf - = child.m_groupData.m_groupHandler.m_callDoneIf; - } - if (child.m_groupData.m_parallelLimit) { - QT_ASSERT(!m_groupData.m_parallelLimit, - qWarning("Group execution mode redefinition, overriding...")); - m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit; - } - if (child.m_groupData.m_workflowPolicy) { - QT_ASSERT(!m_groupData.m_workflowPolicy, - qWarning("Group workflow policy redefinition, overriding...")); - m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy; - } - if (child.m_groupData.m_loop) { - QT_ASSERT(!m_groupData.m_loop, - qWarning("Group loop redefinition, overriding...")); - m_groupData.m_loop = child.m_groupData.m_loop; - } - break; - case Type::TaskHandler: - QT_ASSERT(child.m_taskHandler.m_createHandler, - qWarning("Task create handler can't be null, skipping..."); return); - m_children.append(child); - break; - case Type::Storage: - // Check for duplicates, as can't have the same storage twice on the same level. - for (const StorageBase &storage : child.m_storageList) { - if (m_storageList.contains(storage)) { - QT_ASSERT(false, qWarning("Can't add the same storage into one Group twice, " - "skipping...")); - continue; - } - m_storageList.append(storage); - } - break; - } - } -} - -/*! - \class Tasking::ExecutableItem - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief Base class for executable task items. - \reentrant - - \c ExecutableItem provides an additional interface for items containing executable tasks. - Use withTimeout() to attach a timeout to a task. - Use withLog() to include debugging information about the task startup and the execution result. -*/ - -/*! - Attaches \c TimeoutTask to a copy of \c this ExecutableItem, elapsing after \a timeout - in milliseconds, with an optionally provided timeout \a handler, and returns the coupled item. - - When the ExecutableItem finishes before \a timeout passes, the returned item finishes - immediately with the task's result. Otherwise, \a handler is invoked (if provided), - the task is canceled, and the returned item finishes with an error. -*/ -Group ExecutableItem::withTimeout(milliseconds timeout, - const std::function<void()> &handler) const -{ - const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; }; - return Group { - parallel, - stopOnSuccessOrError, - Group { - finishAllAndError, - handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success) - : TimeoutTask(onSetup) - }, - *this - }; -} - -static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); } - -static QString logHeader(const QString &logName) -{ - return QString::fromLatin1("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName); -}; - -/*! - Attaches a custom debug printout to a copy of \c this ExecutableItem, - issued on task startup and after the task is finished, and returns the coupled item. - - The debug printout includes a timestamp of the event (start or finish) - and \a logName to identify the specific task in the debug log. - - The finish printout contains the additional information whether the execution was - synchronous or asynchronous, its result (the value described by the DoneWith enum), - and the total execution time in milliseconds. -*/ -Group ExecutableItem::withLog(const QString &logName) const -{ - struct LogStorage - { - time_point<system_clock, nanoseconds> start; - int asyncCount = 0; - }; - const Storage<LogStorage> storage; - return Group { - storage, - onGroupSetup([storage, logName] { - storage->start = system_clock::now(); - storage->asyncCount = activeTaskTree()->asyncCount(); - qDebug().noquote().nospace() << logHeader(logName) << " started."; - }), - *this, - onGroupDone([storage, logName](DoneWith result) { - const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start); - const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount; - QT_CHECK(asyncCountDiff >= 0); - const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>(); - const QString syncType = asyncCountDiff ? QString::fromLatin1("asynchronously") - : QString::fromLatin1("synchronously"); - qDebug().noquote().nospace() << logHeader(logName) << " finished " << syncType - << " with " << doneWithEnum.valueToKey(int(result)) - << " within " << elapsed.count() << "ms."; - }) - }; -} - -/*! - \fn Group ExecutableItem::operator!(const ExecutableItem &item) - - Returns a Group with the DoneResult of \a item negated. - - If \a item reports DoneResult::Success, the returned item reports DoneResult::Error. - If \a item reports DoneResult::Error, the returned item reports DoneResult::Success. - - The returned item is equivalent to: - \code - Group { - item, - onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); }) - } - \endcode - - \sa operator&&(), operator||() -*/ -Group operator!(const ExecutableItem &item) -{ - return { - item, - onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); }) - }; -} - -/*! - \fn Group ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second) - - Returns a Group with \a first and \a second tasks merged with conjunction. - - Both \a first and \a second tasks execute in sequence. - If both tasks report DoneResult::Success, the returned item reports DoneResult::Success. - Otherwise, the returned item reports DoneResult::Error. - - The returned item is - \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}: - if the \a first task reports DoneResult::Error, the \a second task is skipped, - and the returned item reports DoneResult::Error immediately. - - The returned item is equivalent to: - \code - Group { stopOnError, first, second } - \endcode - - \note Parallel execution of conjunction in a short-circuit manner can be achieved with the - following code: \c {Group { parallel, stopOnError, first, second }}. In this case: - if the \e {first finished} task reports DoneResult::Error, - the \e other task is canceled, and the group reports DoneResult::Error immediately. - - \sa operator||(), operator!() -*/ -Group operator&&(const ExecutableItem &first, const ExecutableItem &second) -{ - return { stopOnError, first, second }; -} - -/*! - \fn Group ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second) - - Returns a Group with \a first and \a second tasks merged with disjunction. - - Both \a first and \a second tasks execute in sequence. - If both tasks report DoneResult::Error, the returned item reports DoneResult::Error. - Otherwise, the returned item reports DoneResult::Success. - - The returned item is - \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}: - if the \a first task reports DoneResult::Success, the \a second task is skipped, - and the returned item reports DoneResult::Success immediately. - - The returned item is equivalent to: - \code - Group { stopOnSuccess, first, second } - \endcode - - \note Parallel execution of disjunction in a short-circuit manner can be achieved with the - following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case: - if the \e {first finished} task reports DoneResult::Success, - the \e other task is canceled, and the group reports DoneResult::Success immediately. - - \sa operator&&(), operator!() -*/ -Group operator||(const ExecutableItem &first, const ExecutableItem &second) -{ - return { stopOnSuccess, first, second }; -} - -/*! - \fn Group ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result) - \overload ExecutableItem::operator&&() - - Returns the \a item task if the \a result is DoneResult::Success; otherwise returns - the \a item task with its done result tweaked to DoneResult::Error. - - The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result - into DoneResult::Error unconditionally. -*/ -Group operator&&(const ExecutableItem &item, DoneResult result) -{ - return { result == DoneResult::Success ? stopOnError : finishAllAndError, item }; -} - -/*! - \fn Group ExecutableItem::operator||(const ExecutableItem &item, DoneResult result) - \overload ExecutableItem::operator||() - - Returns the \a item task if the \a result is DoneResult::Error; otherwise returns - the \a item task with its done result tweaked to DoneResult::Success. - - The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result - into DoneResult::Success unconditionally. -*/ -Group operator||(const ExecutableItem &item, DoneResult result) -{ - return { result == DoneResult::Error ? stopOnError : finishAllAndSuccess, item }; -} - -Group ExecutableItem::withCancelImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper, - const GroupItems &postCancelRecipe) const -{ - const Storage<bool> canceledStorage(false); - - const auto onSetup = [connectWrapper, canceledStorage](Barrier &barrier) { - connectWrapper(&barrier, [barrierPtr = &barrier, canceled = canceledStorage.activeStorage()] { - *canceled = true; - barrierPtr->advance(); - }); - }; - - const auto wasCanceled = [canceledStorage] { return *canceledStorage; }; - - return { - continueOnError, - canceledStorage, - Group { - parallel, - stopOnSuccessOrError, - BarrierTask(onSetup) && errorItem, - *this - }, - If (wasCanceled) >> Then { - postCancelRecipe - } - }; -} - -Group ExecutableItem::withAcceptImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const -{ - const auto onSetup = [connectWrapper](Barrier &barrier) { - connectWrapper(&barrier, [barrierPtr = &barrier] { barrierPtr->advance(); }); - }; - return Group { - parallel, - BarrierTask(onSetup), - *this - }; -} - -class TaskTreePrivate; -class TaskNode; -class RuntimeContainer; -class RuntimeIteration; -class RuntimeTask; - -class ExecutionContextActivator -{ -public: - ExecutionContextActivator(RuntimeIteration *iteration) { - activateTaskTree(iteration); - activateContext(iteration); - } - ExecutionContextActivator(RuntimeContainer *container) { - activateTaskTree(container); - activateContext(container); - } - ~ExecutionContextActivator() { - for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order - m_activeStorages[i].m_storageData->threadData().popStorage(); - for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order - m_activeLoops[i].m_loopData->threadData().popIteration(); - QT_ASSERT(s_activeTaskTrees.size(), return); - s_activeTaskTrees.pop_back(); - } - -private: - void activateTaskTree(RuntimeIteration *iteration); - void activateTaskTree(RuntimeContainer *container); - void activateContext(RuntimeIteration *iteration); - void activateContext(RuntimeContainer *container); - QList<Loop> m_activeLoops; - QList<StorageBase> m_activeStorages; -}; - -class ContainerNode -{ - Q_DISABLE_COPY(ContainerNode) - -public: - ContainerNode(ContainerNode &&other) = default; - ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task); - - TaskTreePrivate *const m_taskTreePrivate = nullptr; - - const GroupItem::GroupHandler m_groupHandler; - const int m_parallelLimit = 1; - const WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError; - const std::optional<Loop> m_loop; - const QList<StorageBase> m_storageList; - std::vector<TaskNode> m_children; - const int m_taskCount = 0; -}; - -class TaskNode -{ - Q_DISABLE_COPY(TaskNode) - -public: - TaskNode(TaskNode &&other) = default; - TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task) - : m_taskHandler(task.m_taskHandler) - , m_container(taskTreePrivate, task) - {} - - bool isTask() const { return bool(m_taskHandler.m_createHandler); } - int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; } - - const GroupItem::TaskHandler m_taskHandler; - ContainerNode m_container; -}; - -class TaskTreePrivate -{ - Q_DISABLE_COPY_MOVE(TaskTreePrivate) - -public: - explicit TaskTreePrivate(TaskTree *taskTree); - ~TaskTreePrivate(); - - void start(); - void stop(); - void bumpAsyncCount(); - void advanceProgress(int byValue); - void emitDone(DoneWith result); - void callSetupHandler(const StorageBase &storage, StoragePtr storagePtr) { - callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler); - } - void callDoneHandler(const StorageBase &storage, StoragePtr storagePtr) { - callStorageHandler(storage, storagePtr, &StorageHandler::m_doneHandler); - } - struct StorageHandler { - StorageBase::StorageHandler m_setupHandler = {}; - StorageBase::StorageHandler m_doneHandler = {}; - }; - typedef StorageBase::StorageHandler StorageHandler::*HandlerPtr; // ptr to class member - void callStorageHandler(const StorageBase &storage, StoragePtr storagePtr, HandlerPtr ptr) - { - const auto it = m_storageHandlers.constFind(storage); - if (it == m_storageHandlers.constEnd()) - return; - const StorageHandler storageHandler = *it; - if (storageHandler.*ptr) { - GuardLocker locker(m_guard); - (storageHandler.*ptr)(storagePtr); - } - } - - // Node related methods - - // If returned value != Continue, childDone() needs to be called in parent container (in caller) - // in order to unwind properly. - void startTask(const std::shared_ptr<RuntimeTask> &node); - void stopTask(RuntimeTask *node); - bool invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith); - - // Container related methods - - void continueContainer(RuntimeContainer *container); - void startChildren(RuntimeContainer *container); - void childDone(RuntimeIteration *iteration, bool success); - void stopContainer(RuntimeContainer *container); - bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith); - bool invokeLoopHandler(RuntimeContainer *container); - - template <typename Container, typename Handler, typename ...Args, - typename ReturnType = std::invoke_result_t<Handler, Args...>> - ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args) - { - QT_ASSERT(!m_guard.isLocked(), qWarning("Nested execution of handlers detected." - "This may happen when one task's handler has entered a nested event loop," - "and other task finished during nested event loop's processing, " - "causing stopping (canceling) the task executing the nested event loop. " - "This includes the case when QCoreApplication::processEvents() was called from " - "the handler. It may also happen when the Barrier task is advanced directly " - "from some other task handler. This will lead to a crash. " - "Avoid event processing during handlers' execution. " - "If it can't be avoided, make sure no other tasks are run in parallel when " - "processing events from the handler.")); - ExecutionContextActivator activator(container); - GuardLocker locker(m_guard); - return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...); - } - - static int effectiveLoopCount(const std::optional<Loop> &loop) - { - return loop && loop->m_loopData->m_loopCount ? *loop->m_loopData->m_loopCount : 1; - } - - TaskTree *q = nullptr; - Guard m_guard; - int m_progressValue = 0; - int m_asyncCount = 0; - QSet<StorageBase> m_storages; - QHash<StorageBase, StorageHandler> m_storageHandlers; - std::optional<TaskNode> m_root; - std::shared_ptr<RuntimeTask> m_runtimeRoot; // Keep me last in order to destruct first -}; - -static bool initialSuccessBit(WorkflowPolicy workflowPolicy) -{ - switch (workflowPolicy) { - case WorkflowPolicy::StopOnError: - case WorkflowPolicy::ContinueOnError: - case WorkflowPolicy::FinishAllAndSuccess: - return true; - case WorkflowPolicy::StopOnSuccess: - case WorkflowPolicy::ContinueOnSuccess: - case WorkflowPolicy::StopOnSuccessOrError: - case WorkflowPolicy::FinishAllAndError: - return false; - } - QT_CHECK(false); - return false; -} - -static bool isProgressive(RuntimeContainer *container); - -class RuntimeIteration -{ - Q_DISABLE_COPY(RuntimeIteration) - -public: - RuntimeIteration(int index, RuntimeContainer *container); - ~RuntimeIteration(); - std::optional<Loop> loop() const; - void removeChild(RuntimeTask *node); - - const int m_iterationIndex = 0; - const bool m_isProgressive = true; - RuntimeContainer *m_container = nullptr; - int m_doneCount = 0; - std::vector<std::shared_ptr<RuntimeTask>> m_children = {}; // Owning. -}; - -class RuntimeContainer -{ - Q_DISABLE_COPY(RuntimeContainer) - -public: - RuntimeContainer(const ContainerNode &taskContainer, RuntimeTask *parentTask) - : m_containerNode(taskContainer) - , m_parentTask(parentTask) - , m_storages(createStorages(taskContainer)) - , m_successBit(initialSuccessBit(taskContainer.m_workflowPolicy)) - , m_shouldIterate(taskContainer.m_loop) - {} - - ~RuntimeContainer() - { - for (int i = m_containerNode.m_storageList.size() - 1; i >= 0; --i) { // iterate in reverse order - const StorageBase storage = m_containerNode.m_storageList[i]; - StoragePtr storagePtr = m_storages.value(i); - if (m_callStorageDoneHandlersOnDestruction) - m_containerNode.m_taskTreePrivate->callDoneHandler(storage, storagePtr); - storage.m_storageData->m_destructor(storagePtr); - } - } - - static QList<StoragePtr> createStorages(const ContainerNode &container); - bool isStarting() const { return m_startGuard.isLocked(); } - RuntimeIteration *parentIteration() const; - bool updateSuccessBit(bool success); - void deleteFinishedIterations(); - int progressiveLoopCount() const - { - return m_containerNode.m_taskTreePrivate->effectiveLoopCount(m_containerNode.m_loop); - } - - const ContainerNode &m_containerNode; // Not owning. - RuntimeTask *m_parentTask = nullptr; // Not owning. - const QList<StoragePtr> m_storages; // Owning. - - bool m_successBit = true; - bool m_callStorageDoneHandlersOnDestruction = false; - Guard m_startGuard; - - int m_iterationCount = 0; - int m_nextToStart = 0; - int m_runningChildren = 0; - bool m_shouldIterate = true; - std::vector<std::unique_ptr<RuntimeIteration>> m_iterations; // Owning. -}; - -class RuntimeTask -{ -public: - ~RuntimeTask() - { - if (m_task) { - // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204. - QObject::disconnect(m_task.get(), &TaskInterface::done, nullptr, nullptr); - } - } - - const TaskNode &m_taskNode; // Not owning. - RuntimeIteration *m_parentIteration = nullptr; // Not owning. - std::optional<RuntimeContainer> m_container = {}; // Owning. - std::unique_ptr<TaskInterface> m_task = {}; // Owning. - SetupResult m_setupResult = SetupResult::Continue; -}; - -RuntimeIteration::~RuntimeIteration() = default; - -TaskTreePrivate::TaskTreePrivate(TaskTree *taskTree) - : q(taskTree) {} - -TaskTreePrivate::~TaskTreePrivate() = default; - -static bool isProgressive(RuntimeContainer *container) -{ - RuntimeIteration *iteration = container->m_parentTask->m_parentIteration; - return iteration ? iteration->m_isProgressive : true; -} - -void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration) -{ - activateTaskTree(iteration->m_container); -} - -void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container) -{ - s_activeTaskTrees.push_back(container->m_containerNode.m_taskTreePrivate->q); -} - -void ExecutionContextActivator::activateContext(RuntimeIteration *iteration) -{ - std::optional<Loop> loop = iteration->loop(); - if (loop) { - loop->m_loopData->threadData().pushIteration(iteration->m_iterationIndex); - m_activeLoops.append(*loop); - } - activateContext(iteration->m_container); -} - -void ExecutionContextActivator::activateContext(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - for (int i = 0; i < containerNode.m_storageList.size(); ++i) { - const StorageBase &storage = containerNode.m_storageList[i]; - if (m_activeStorages.contains(storage)) - continue; // Storage shadowing: The storage is already active, skipping it... - m_activeStorages.append(storage); - storage.m_storageData->threadData().pushStorage(container->m_storages.value(i)); - } - // Go to the parent after activating this storages so that storage shadowing works - // in the direction from child to parent root. - if (container->parentIteration()) - activateContext(container->parentIteration()); -} - -void TaskTreePrivate::start() -{ - QT_ASSERT(m_root, return); - QT_ASSERT(!m_runtimeRoot, return); - m_asyncCount = 0; - m_progressValue = 0; - { - GuardLocker locker(m_guard); - emit q->started(); - emit q->asyncCountChanged(m_asyncCount); - emit q->progressValueChanged(m_progressValue); - } - // TODO: check storage handlers for not existing storages in tree - for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) { - QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't " - "exist in task tree. Its handlers will never be called.")); - } - m_runtimeRoot.reset(new RuntimeTask{*m_root}); - startTask(m_runtimeRoot); - bumpAsyncCount(); -} - -void TaskTreePrivate::stop() -{ - QT_ASSERT(m_root, return); - if (!m_runtimeRoot) - return; - stopTask(m_runtimeRoot.get()); - m_runtimeRoot.reset(); - emitDone(DoneWith::Cancel); -} - -void TaskTreePrivate::bumpAsyncCount() -{ - if (!m_runtimeRoot) - return; - ++m_asyncCount; - GuardLocker locker(m_guard); - emit q->asyncCountChanged(m_asyncCount); -} - -void TaskTreePrivate::advanceProgress(int byValue) -{ - if (byValue == 0) - return; - QT_CHECK(byValue > 0); - QT_CHECK(m_progressValue + byValue <= m_root->taskCount()); - m_progressValue += byValue; - GuardLocker locker(m_guard); - emit q->progressValueChanged(m_progressValue); -} - -void TaskTreePrivate::emitDone(DoneWith result) -{ - QT_CHECK(m_progressValue == m_root->taskCount()); - GuardLocker locker(m_guard); - emit q->done(result); -} - -RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container) - : m_iterationIndex(index) - , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container)) - , m_container(container) -{} - -std::optional<Loop> RuntimeIteration::loop() const -{ - return m_container->m_containerNode.m_loop; -} - -void RuntimeIteration::removeChild(RuntimeTask *task) -{ - const auto it = std::find_if(m_children.cbegin(), m_children.cend(), [task](const auto &ptr) { - return ptr.get() == task; - }); - if (it != m_children.cend()) - m_children.erase(it); -} - -static std::vector<TaskNode> createChildren(TaskTreePrivate *taskTreePrivate, - const GroupItems &children) -{ - std::vector<TaskNode> result; - result.reserve(children.size()); - for (const GroupItem &child : children) - result.emplace_back(taskTreePrivate, child); - return result; -} - -ContainerNode::ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task) - : m_taskTreePrivate(taskTreePrivate) - , m_groupHandler(task.m_groupData.m_groupHandler) - , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(1)) - , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError)) - , m_loop(task.m_groupData.m_loop) - , m_storageList(task.m_storageList) - , m_children(createChildren(taskTreePrivate, task.m_children)) - , m_taskCount(std::accumulate(m_children.cbegin(), m_children.cend(), 0, - [](int r, const TaskNode &n) { return r + n.taskCount(); }) - * taskTreePrivate->effectiveLoopCount(m_loop)) -{ - for (const StorageBase &storage : m_storageList) - m_taskTreePrivate->m_storages << storage; -} - -QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container) -{ - QList<StoragePtr> storages; - for (const StorageBase &storage : container.m_storageList) { - StoragePtr storagePtr = storage.m_storageData->m_constructor(); - storages.append(storagePtr); - container.m_taskTreePrivate->callSetupHandler(storage, storagePtr); - } - return storages; -} - -RuntimeIteration *RuntimeContainer::parentIteration() const -{ - return m_parentTask->m_parentIteration; -} - -bool RuntimeContainer::updateSuccessBit(bool success) -{ - if (m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndSuccess - || m_containerNode.m_workflowPolicy == WorkflowPolicy::FinishAllAndError - || m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) { - if (m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccessOrError) - m_successBit = success; - return m_successBit; - } - - const bool donePolicy = m_containerNode.m_workflowPolicy == WorkflowPolicy::StopOnSuccess - || m_containerNode.m_workflowPolicy == WorkflowPolicy::ContinueOnSuccess; - m_successBit = donePolicy ? (m_successBit || success) : (m_successBit && success); - return m_successBit; -} - -void RuntimeContainer::deleteFinishedIterations() -{ - for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) { - if (it->get()->m_doneCount == int(m_containerNode.m_children.size())) - it = m_iterations.erase(it); - else - ++it; - } -} - -void TaskTreePrivate::continueContainer(RuntimeContainer *container) -{ - RuntimeTask *parentTask = container->m_parentTask; - if (parentTask->m_setupResult == SetupResult::Continue) - startChildren(container); - if (parentTask->m_setupResult == SetupResult::Continue) - return; - - const bool bit = container->updateSuccessBit(parentTask->m_setupResult == SetupResult::StopWithSuccess); - RuntimeIteration *parentIteration = container->parentIteration(); - QT_CHECK(parentTask); - const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error); - parentTask->m_setupResult = toSetupResult(result); - if (parentIteration) { - parentIteration->removeChild(parentTask); - if (!parentIteration->m_container->isStarting()) - childDone(parentIteration, result); - } else { - QT_CHECK(m_runtimeRoot.get() == parentTask); - m_runtimeRoot.reset(); - emitDone(result ? DoneWith::Success : DoneWith::Error); - } -} - -void TaskTreePrivate::startChildren(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - const int childCount = int(containerNode.m_children.size()); - - if (container->m_iterationCount == 0) { - if (container->m_shouldIterate && !invokeLoopHandler(container)) { - if (isProgressive(container)) - advanceProgress(containerNode.m_taskCount); - container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit); - return; - } - container->m_iterations.emplace_back( - std::make_unique<RuntimeIteration>(container->m_iterationCount, container)); - ++container->m_iterationCount; - } - - GuardLocker locker(container->m_startGuard); - - while (containerNode.m_parallelLimit == 0 - || container->m_runningChildren < containerNode.m_parallelLimit) { - container->deleteFinishedIterations(); - if (container->m_nextToStart == childCount) { - if (invokeLoopHandler(container)) { - container->m_nextToStart = 0; - container->m_iterations.emplace_back( - std::make_unique<RuntimeIteration>(container->m_iterationCount, container)); - ++container->m_iterationCount; - } else if (container->m_iterations.empty()) { - container->m_parentTask->m_setupResult = toSetupResult(container->m_successBit); - return; - } else { - return; - } - } - if (containerNode.m_children.size() == 0) // Empty loop body. - continue; - - RuntimeIteration *iteration = container->m_iterations.back().get(); - const std::shared_ptr<RuntimeTask> task( - new RuntimeTask{containerNode.m_children.at(container->m_nextToStart), iteration}); - iteration->m_children.emplace_back(task); - ++container->m_runningChildren; - ++container->m_nextToStart; - - startTask(task); - if (task->m_setupResult == SetupResult::Continue) - continue; - - task->m_parentIteration->removeChild(task.get()); - childDone(iteration, task->m_setupResult == SetupResult::StopWithSuccess); - if (container->m_parentTask->m_setupResult != SetupResult::Continue) - return; - } -} - -void TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success) -{ - RuntimeContainer *container = iteration->m_container; - const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy; - const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError - || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success) - || (workflowPolicy == WorkflowPolicy::StopOnError && !success); - ++iteration->m_doneCount; - --container->m_runningChildren; - const bool updatedSuccess = container->updateSuccessBit(success); - container->m_parentTask->m_setupResult = shouldStop ? toSetupResult(updatedSuccess) : SetupResult::Continue; - if (shouldStop) - stopContainer(container); - - if (container->isStarting()) - return; - continueContainer(container); -} - -void TaskTreePrivate::stopContainer(RuntimeContainer *container) -{ - const ContainerNode &containerNode = container->m_containerNode; - for (auto &iteration : container->m_iterations) { - for (auto &child : iteration->m_children) { - ++iteration->m_doneCount; - stopTask(child.get()); - } - - if (iteration->m_isProgressive) { - int skippedTaskCount = 0; - for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i) - skippedTaskCount += containerNode.m_children.at(i).taskCount(); - advanceProgress(skippedTaskCount); - } - } - const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount; - if (skippedIterations > 0) { - advanceProgress(container->m_containerNode.m_taskCount / container->progressiveLoopCount() - * skippedIterations); - } -} - -static bool shouldCall(CallDoneIf callDoneIf, DoneWith result) -{ - if (result == DoneWith::Success) - return callDoneIf != CallDoneIf::Error; - return callDoneIf != CallDoneIf::Success; -} - -bool TaskTreePrivate::invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith) -{ - DoneResult result = toDoneResult(doneWith); - const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler; - if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith)) - result = invokeHandler(container, groupHandler.m_doneHandler, doneWith); - container->m_callStorageDoneHandlersOnDestruction = true; - return result == DoneResult::Success; -} - -bool TaskTreePrivate::invokeLoopHandler(RuntimeContainer *container) -{ - if (container->m_shouldIterate) { - const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get(); - if (loopData->m_loopCount) { - container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount; - } else if (loopData->m_condition) { - container->m_shouldIterate = invokeHandler(container, loopData->m_condition, - container->m_iterationCount); - } - } - return container->m_shouldIterate; -} - -void TaskTreePrivate::startTask(const std::shared_ptr<RuntimeTask> &node) -{ - if (!node->m_taskNode.isTask()) { - const ContainerNode &containerNode = node->m_taskNode.m_container; - node->m_container.emplace(containerNode, node.get()); - RuntimeContainer *container = &*node->m_container; - if (containerNode.m_groupHandler.m_setupHandler) { - container->m_parentTask->m_setupResult = invokeHandler(container, containerNode.m_groupHandler.m_setupHandler); - if (container->m_parentTask->m_setupResult != SetupResult::Continue) { - if (isProgressive(container)) - advanceProgress(containerNode.m_taskCount); - // Non-Continue SetupResult takes precedence over the workflow policy. - container->m_successBit = container->m_parentTask->m_setupResult == SetupResult::StopWithSuccess; - } - } - continueContainer(container); - return; - } - - const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; - node->m_task.reset(handler.m_createHandler()); - node->m_setupResult = handler.m_setupHandler - ? invokeHandler(node->m_parentIteration, handler.m_setupHandler, *node->m_task.get()) - : SetupResult::Continue; - if (node->m_setupResult != SetupResult::Continue) { - if (node->m_parentIteration->m_isProgressive) - advanceProgress(1); - node->m_parentIteration->removeChild(node.get()); - return; - } - QObject::connect(node->m_task.get(), &TaskInterface::done, - q, [this, node](DoneResult doneResult) { - const bool result = invokeTaskDoneHandler(node.get(), toDoneWith(doneResult)); - node->m_setupResult = toSetupResult(result); - QObject::disconnect(node->m_task.get(), &TaskInterface::done, q, nullptr); - node->m_task.release()->deleteLater(); - RuntimeIteration *parentIteration = node->m_parentIteration; - if (parentIteration->m_container->isStarting()) - return; - - parentIteration->removeChild(node.get()); - childDone(parentIteration, result); - bumpAsyncCount(); - }); - node->m_task->start(); -} - -void TaskTreePrivate::stopTask(RuntimeTask *node) -{ - if (!node->m_task) { - if (!node->m_container) - return; - stopContainer(&*node->m_container); - node->m_container->updateSuccessBit(false); - invokeDoneHandler(&*node->m_container, DoneWith::Cancel); - return; - } - - invokeTaskDoneHandler(node, DoneWith::Cancel); - node->m_task.reset(); -} - -bool TaskTreePrivate::invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith) -{ - DoneResult result = toDoneResult(doneWith); - const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler; - if (handler.m_doneHandler && shouldCall(handler.m_callDoneIf, doneWith)) { - result = invokeHandler(node->m_parentIteration, - handler.m_doneHandler, *node->m_task.get(), doneWith); - } - if (node->m_parentIteration->m_isProgressive) - advanceProgress(1); - return result == DoneResult::Success; -} - -/*! - \class Tasking::TaskTree - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief The TaskTree class runs an async task tree structure defined in a declarative way. - \reentrant - - Use the Tasking namespace to build extensible, declarative task tree - structures that contain possibly asynchronous tasks, such as QProcess, - NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you - to create a sophisticated mixture of a parallel or sequential flow of tasks - in the form of a tree and to run it any time later. - - \section1 Root Element and Tasks - - The TaskTree has a mandatory Group root element, which may contain - any number of tasks of various types, such as QProcessTask, NetworkQueryTask, - or ConcurrentCallTask<ReturnType>: - - \code - using namespace Tasking; - - const Group root { - QProcessTask(...), - NetworkQueryTask(...), - ConcurrentCallTask<int>(...) - }; - - TaskTree *taskTree = new TaskTree(root); - connect(taskTree, &TaskTree::done, ...); // finish handler - taskTree->start(); - \endcode - - The task tree above has a top level element of the Group type that contains - tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type. - After taskTree->start() is called, the tasks are run in a chain, starting - with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask - task is started. Finally, when the network task finishes successfully, the - ConcurrentCallTask<int> task is started. - - When the last running task finishes with success, the task tree is considered - to have run successfully and the done() signal is emitted with DoneWith::Success. - When a task finishes with an error, the execution of the task tree is stopped - and the remaining tasks are skipped. The task tree finishes with an error and - sends the TaskTree::done() signal with DoneWith::Error. - - \section1 Groups - - The parent of the Group sees it as a single task. Like other tasks, - the group can be started and it can finish with success or an error. - The Group elements can be nested to create a tree structure: - - \code - const Group root { - Group { - parallel, - QProcessTask(...), - ConcurrentCallTask<int>(...) - }, - NetworkQueryTask(...) - }; - \endcode - - The example above differs from the first example in that the root element has - a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a - sibling element of the NetworkQueryTask in the root. The subgroup contains an - additional \e parallel element that instructs its Group to execute its tasks - in parallel. - - So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start - immediately and run in parallel. Since the root group doesn't contain a - \e parallel element, its direct child tasks are run in sequence. Thus, the - NetworkQueryTask starts when the whole subgroup finishes. The group is - considered as finished when all its tasks have finished. The order in which - the tasks finish is not relevant. - - So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the - following scenarios can take place: - - \table - \header - \li Scenario 1 - \li Scenario 2 - \row - \li Root Group starts - \li Root Group starts - \row - \li Sub Group starts - \li Sub Group starts - \row - \li QProcessTask starts - \li QProcessTask starts - \row - \li ConcurrentCallTask<int> starts - \li ConcurrentCallTask<int> starts - \row - \li ... - \li ... - \row - \li \b {QProcessTask finishes} - \li \b {ConcurrentCallTask<int> finishes} - \row - \li ... - \li ... - \row - \li \b {ConcurrentCallTask<int> finishes} - \li \b {QProcessTask finishes} - \row - \li Sub Group finishes - \li Sub Group finishes - \row - \li NetworkQueryTask starts - \li NetworkQueryTask starts - \row - \li ... - \li ... - \row - \li NetworkQueryTask finishes - \li NetworkQueryTask finishes - \row - \li Root Group finishes - \li Root Group finishes - \endtable - - The differences between the scenarios are marked with bold. Three dots mean - that an unspecified amount of time passes between previous and next events - (a task or tasks continue to run). No dots between events - means that they occur synchronously. - - The presented scenarios assume that all tasks run successfully. If a task - fails during execution, the task tree finishes with an error. In particular, - when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed, - the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error, - the NetworkQueryTask is skipped, and the tree finishes with an error. - - \section1 Task Types - - Each task type is associated with its corresponding task class that executes - the task. For example, a QProcessTask inside a task tree is associated with - the QProcess class that executes the process. The associated objects are - automatically created, started, and destructed exclusively by the task tree - at the appropriate time. - - If a root group consists of five sequential QProcessTask tasks, and the task tree - executes the group, it creates an instance of QProcess for the first - QProcessTask and starts it. If the QProcess instance finishes successfully, - the task tree destructs it and creates a new QProcess instance for the - second QProcessTask, and so on. If the first task finishes with an error, the task - tree stops creating QProcess instances, and the root group finishes with an - error. - - The following table shows examples of task types and their corresponding task - classes: - - \table - \header - \li Task Type (Tasking Namespace) - \li Associated Task Class - \li Brief Description - \row - \li QProcessTask - \li QProcess - \li Starts process. - \row - \li ConcurrentCallTask<ReturnType> - \li Tasking::ConcurrentCall<ReturnType> - \li Starts asynchronous task, runs in separate thread. - \row - \li TaskTreeTask - \li Tasking::TaskTree - \li Starts nested task tree. - \row - \li NetworkQueryTask - \li NetworkQuery - \li Starts network download. - \endtable - - \section1 Task Handlers - - Use Task handlers to set up a task for execution and to enable reading - the output data from the task when it finishes with success or an error. - - \section2 Task's Start Handler - - When a corresponding task class object is created and before it's started, - the task tree invokes an optionally user-provided setup handler. The setup - handler should always take a \e reference to the associated task class object: - - \code - const auto onSetup = [](QProcess &process) { - process.setProgram("sleep"); - process.setArguments({"3"}); - }; - const Group root { - QProcessTask(onSetup) - }; - \endcode - - You can modify the passed QProcess in the setup handler, so that the task - tree can start the process according to your configuration. - You should not call \c {process.start();} in the setup handler, - as the task tree calls it when needed. The setup handler is optional. When used, - it must be the first argument of the task's constructor. - - Optionally, the setup handler may return a SetupResult. The returned - SetupResult influences the further start behavior of a given task. The - possible values are: - - \table - \header - \li SetupResult Value - \li Brief Description - \row - \li Continue - \li The task will be started normally. This is the default behavior when the - setup handler doesn't return SetupResult (that is, its return type is - void). - \row - \li StopWithSuccess - \li The task won't be started and it will report success to its parent. - \row - \li StopWithError - \li The task won't be started and it will report an error to its parent. - \endtable - - This is useful for running a task only when a condition is met and the data - needed to evaluate this condition is not known until previously started tasks - finish. In this way, the setup handler dynamically decides whether to start the - corresponding task normally or skip it and report success or an error. - For more information about inter-task data exchange, see \l Storage. - - \section2 Task's Done Handler - - When a running task finishes, the task tree invokes an optionally provided done handler. - The handler should take a \c const \e reference to the associated task class object: - - \code - const auto onSetup = [](QProcess &process) { - process.setProgram("sleep"); - process.setArguments({"3"}); - }; - const auto onDone = [](const QProcess &process, DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "Success" << process.cleanedStdOut(); - else - qDebug() << "Failure" << process.cleanedStdErr(); - }; - const Group root { - QProcessTask(onSetup, onDone) - }; - \endcode - - The done handler may collect output data from QProcess, and store it - for further processing or perform additional actions. - - \note If the task setup handler returns StopWithSuccess or StopWithError, - the done handler is not invoked. - - \section1 Group Handlers - - Similarly to task handlers, group handlers enable you to set up a group to - execute and to apply more actions when the whole group finishes with - success or an error. - - \section2 Group's Start Handler - - The task tree invokes the group start handler before it starts the child - tasks. The group handler doesn't take any arguments: - - \code - const auto onSetup = [] { - qDebug() << "Entering the group"; - }; - const Group root { - onGroupSetup(onSetup), - QProcessTask(...) - }; - \endcode - - The group setup handler is optional. To define a group setup handler, add an - onGroupSetup() element to a group. The argument of onGroupSetup() is a user - handler. If you add more than one onGroupSetup() element to a group, an assert - is triggered at runtime that includes an error message. - - Like the task's start handler, the group start handler may return SetupResult. - The returned SetupResult value affects the start behavior of the - whole group. If you do not specify a group start handler or its return type - is void, the default group's action is SetupResult::Continue, so that all - tasks are started normally. Otherwise, when the start handler returns - SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not - started (they are skipped) and the group itself reports success or failure, - depending on the returned value, respectively. - - \code - const Group root { - onGroupSetup([] { qDebug() << "Root setup"; }), - Group { - onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }), - QProcessTask(...) // Process 1 - }, - Group { - onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }), - QProcessTask(...) // Process 2 - }, - Group { - onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }), - QProcessTask(...) // Process 3 - }, - QProcessTask(...) // Process 4 - }; - \endcode - - In the above example, all subgroups of a root group define their setup handlers. - The following scenario assumes that all started processes finish with success: - - \table - \header - \li Scenario - \li Comment - \row - \li Root Group starts - \li Doesn't return SetupResult, so its tasks are executed. - \row - \li Group 1 starts - \li Returns Continue, so its tasks are executed. - \row - \li Process 1 starts - \li - \row - \li ... - \li ... - \row - \li Process 1 finishes (success) - \li - \row - \li Group 1 finishes (success) - \li - \row - \li Group 2 starts - \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports - success. - \row - \li Group 2 finishes (success) - \li - \row - \li Group 3 starts - \li Returns StopWithError, so Process 3 is skipped and Group 3 reports - an error. - \row - \li Group 3 finishes (error) - \li - \row - \li Root Group finishes (error) - \li Group 3, which is a direct child of the root group, finished with an - error, so the root group stops executing, skips Process 4, which has - not started yet, and reports an error. - \endtable - - \section2 Groups's Done Handler - - A Group's done handler is executed after the successful or failed execution of its tasks. - The final value reported by the group depends on its \l {Workflow Policy}. - The handler can apply other necessary actions. - The done handler is defined inside the onGroupDone() element of a group. - It may take the optional DoneWith argument, indicating the successful or failed execution: - - \code - const Group root { - onGroupSetup([] { qDebug() << "Root setup"; }), - QProcessTask(...), - onGroupDone([](DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "Root finished with success"; - else - qDebug() << "Root finished with an error"; - }) - }; - \endcode - - The group done handler is optional. If you add more than one onGroupDone() to a group, - an assert is triggered at runtime that includes an error message. - - \note Even if the group setup handler returns StopWithSuccess or StopWithError, - the group's done handler is invoked. This behavior differs from that of task done handler - and might change in the future. - - \section1 Other Group Elements - - A group can contain other elements that describe the processing flow, such as - the execution mode or workflow policy. It can also contain storage elements - that are responsible for collecting and sharing custom common data gathered - during group execution. - - \section2 Execution Mode - - The execution mode element in a Group specifies how the direct child tasks of - the Group are started. The most common execution modes are \l sequential and - \l parallel. It's also possible to specify the limit of tasks running - in parallel by using the parallelLimit() function. - - In all execution modes, a group starts tasks in the oder in which they appear. - - If a child of a group is also a group, the child group runs its tasks - according to its own execution mode. - - \section2 Workflow Policy - - The workflow policy element in a Group specifies how the group should behave - when any of its \e direct child's tasks finish. For a detailed description of possible - policies, refer to WorkflowPolicy. - - If a child of a group is also a group, the child group runs its tasks - according to its own workflow policy. - - \section2 Storage - - Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks. - Especially, in the sequential execution mode, when a task needs data from another, - already finished task, before it can start. For example, a task tree that copies data by reading - it from a source and writing it to a destination might look as follows: - - \code - static QByteArray load(const QString &fileName) { ... } - static void save(const QString &fileName, const QByteArray &array) { ... } - - static Group copyRecipe(const QString &source, const QString &destination) - { - struct CopyStorage { // [1] custom inter-task struct - QByteArray content; // [2] custom inter-task data - }; - - // [3] instance of custom inter-task struct manageable by task tree - const Storage<CopyStorage> storage; - - const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) { - async.setConcurrentCallData(&load, source); - }; - // [4] runtime: task tree activates the instance from [7] before invoking handler - const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) { - storage->content = async.result(); // [5] loader stores the result in storage - }; - - // [4] runtime: task tree activates the instance from [7] before invoking handler - const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) { - const QByteArray content = storage->content; // [6] saver takes data from storage - async.setConcurrentCallData(&save, destination, content); - }; - const auto onSaverDone = [](const ConcurrentCall<void> &async) { - qDebug() << "Save done successfully"; - }; - - const Group root { - // [7] runtime: task tree creates an instance of CopyStorage when root is entered - storage, - ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success), - ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success) - }; - return root; - } - - const QString source = ...; - const QString destination = ...; - TaskTree taskTree(copyRecipe(source, destination)); - connect(&taskTree, &TaskTree::done, - &taskTree, [](DoneWith result) { - if (result == DoneWith::Success) - qDebug() << "The copying finished successfully."; - }); - tasktree.start(); - \endcode - - In the example above, the inter-task data consists of a QByteArray content - variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader - finishes successfully, it stores the data in a \c CopyStorage::content - variable [5]. The saver then uses the variable to configure the saving task [6]. - - To enable a task tree to manage the \c CopyStorage struct, an instance of - \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is - inserted as the group's child item [7], an instance of the \c CopyStorage struct is - created dynamically when the task tree enters this group. When the task - tree leaves this group, the existing instance of the \c CopyStorage struct is - destructed as it's no longer needed. - - If several task trees holding a copy of the common - \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously - (including the case when the task trees are run in different threads), - each task tree contains its own copy of the \c CopyStorage struct. - - You can access \c CopyStorage from any handler in the group with a storage object. - This includes all handlers of all descendant tasks of the group with - a storage object. To access the custom struct in a handler, pass the - copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler - (for example, in a lambda capture) [4]. - - When the task tree invokes a handler in a subtree containing the storage [7], - the task tree activates its own \c CopyStorage instance inside the - \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct - may be accessed only from within the handler body. To access the currently active - \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the - \l {Tasking::Storage::operator->()} {Storage::operator->()}, - \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method. - - The following list summarizes how to employ a Storage object into the task - tree: - \list 1 - \li Define the custom structure \c MyStorage with custom data [1], [2] - \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3] - \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4] - \li Access the \c MyStorage instance in handlers [5], [6] - \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7] - \endlist - - \section1 TaskTree class - - TaskTree executes the tree structure of asynchronous tasks according to the - recipe described by the Group root element. - - As TaskTree is also an asynchronous task, it can be a part of another TaskTree. - To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask - element into another Group element. - - TaskTree reports progress of completed tasks when running. The progress value - is increased when a task finishes or is skipped or canceled. - When TaskTree is finished and the TaskTree::done() signal is emitted, - the current value of the progress equals the maximum progress value. - Maximum progress equals the total number of asynchronous tasks in a tree. - A nested TaskTree is counted as a single task, and its child tasks are not - counted in the top level tree. Groups themselves are not counted as tasks, - but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous, - so they are not counted as tasks. - - To set additional initial data for the running tree, modify the storage - instances in a tree when it creates them by installing a storage setup - handler: - - \code - Storage<CopyStorage> storage; - const Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto initStorage = [](CopyStorage &storage) { - storage.content = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree creates a \c CopyStorage instance, and before any - handler inside a tree is called, the task tree calls the initStorage handler, - to enable setting up initial data of the storage, unique to this particular - run of taskTree. - - Similarly, to collect some additional result data from the running tree, - read it from storage instances in the tree when they are about to be - destroyed. To do this, install a storage done handler: - - \code - Storage<CopyStorage> storage; - const Group root = ...; // storage placed inside root's group and inside handlers - TaskTree taskTree(root); - auto collectStorage = [](const CopyStorage &storage) { - qDebug() << "final content" << storage.content; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to destroy a \c CopyStorage instance, the - task tree calls the collectStorage handler, to enable reading the final data - from the storage, unique to this particular run of taskTree. - - \section1 Task Adapters - - To extend a TaskTree with a new task type, implement a simple adapter class - derived from the TaskAdapter class template. The following class is an - adapter for a single shot timer, which may be considered as a new asynchronous task: - - \code - class TimerTaskAdapter : public TaskAdapter<QTimer> - { - public: - TimerTaskAdapter() { - task()->setSingleShot(true); - task()->setInterval(1000); - connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); }); - } - private: - void start() final { task()->start(); } - }; - - using TimerTask = CustomTask<TimerTaskAdapter>; - \endcode - - You must derive the custom adapter from the TaskAdapter class template - instantiated with a template parameter of the class implementing a running - task. The code above uses QTimer to run the task. This class appears - later as an argument to the task's handlers. The instance of this class - parameter automatically becomes a member of the TaskAdapter template, and is - accessible through the TaskAdapter::task() method. The constructor - of \c TimerTaskAdapter initially configures the QTimer object and connects - to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter - emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that - the task finished successfully. If it emits TaskInterface::done(DoneResult::Error), - the task finished with an error. - The TaskAdapter::start() method starts the timer. - - To make QTimer accessible inside TaskTree under the \c TimerTask name, - define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>. - \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter. - - The new task type is now registered, and you can use it in TaskTree: - - \code - const auto onSetup = [](QTimer &task) { task.setInterval(2000); }; - const auto onDone = [] { qDebug() << "timer triggered"; }; - const Group root { - TimerTask(onSetup, onDone) - }; - \endcode - - When a task tree containing the root from the above example is started, it - prints a debug message within two seconds and then finishes successfully. - - \note The class implementing the running task should have a default constructor, - and objects of this class should be freely destructible. It should be allowed - to destroy a running object, preferably without waiting for the running task - to finish (that is, safe non-blocking destructor of a running task). - To achieve a non-blocking destruction of a task that has a blocking destructor, - consider using the optional \c Deleter template parameter of the TaskAdapter. -*/ - -/*! - Constructs an empty task tree. Use setRecipe() to pass a declarative description - on how the task tree should execute the tasks and how it should handle the finished tasks. - - Starting an empty task tree is no-op and the relevant warning message is issued. - - \sa setRecipe(), start() -*/ -TaskTree::TaskTree() - : d(new TaskTreePrivate(this)) -{} - -/*! - \overload - - Constructs a task tree with a given \a recipe. After the task tree is started, - it executes the tasks contained inside the \a recipe and - handles finished tasks according to the passed description. - - \sa setRecipe(), start() -*/ -TaskTree::TaskTree(const Group &recipe) : TaskTree() -{ - setRecipe(recipe); -} - -/*! - Destroys the task tree. - - When the task tree is running while being destructed, it cancels all the running tasks - immediately. In this case, no handlers are called, not even the groups' and - tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any - signals from the destructor, not even done() or progressValueChanged() signals. - This behavior may always be relied on. - It is completely safe to destruct the running task tree. - - It's a usual pattern to destruct the running task tree. - It's guaranteed that the destruction will run quickly, without having to wait for - the currently running tasks to finish, provided that the used tasks implement - their destructors in a non-blocking way. - - \note Do not call the destructor directly from any of the running task's handlers - or task tree's signals. In these cases, use \l deleteLater() instead. - - \sa cancel() -*/ -TaskTree::~TaskTree() -{ - QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from " - "one of its handlers will lead to a crash!")); - // TODO: delete storages explicitly here? - delete d; -} - -/*! - Sets a given \a recipe for the task tree. After the task tree is started, - it executes the tasks contained inside the \a recipe and - handles finished tasks according to the passed description. - - \note When called for a running task tree, the call is ignored. - - \sa TaskTree(const Tasking::Group &recipe), start() -*/ -void TaskTree::setRecipe(const Group &recipe) -{ - QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the" - "TaskTree handlers, ignoring..."); return); - // TODO: Should we clear the m_storageHandlers, too? - d->m_storages.clear(); - d->m_root.emplace(d, recipe); -} - -/*! - Starts the task tree. - - Use setRecipe() or the constructor to set the declarative description according to which - the task tree will execute the contained tasks and handle finished tasks. - - When the task tree is empty, that is, constructed with a default constructor, - a call to \c start() is no-op and the relevant warning message is issued. - - Otherwise, when the task tree is already running, a call to \e start() is ignored and the - relevant warning message is issued. - - Otherwise, the task tree is started. - - The started task tree may finish synchronously, - for example when the main group's start handler returns SetupResult::StopWithError. - For this reason, the connection to the done signal should be established before calling - \c start(). Use isRunning() in order to detect whether the task tree is still running - after a call to \c start(). - - The task tree implementation relies on the running event loop. - Make sure you have a QEventLoop or QCoreApplication or one of its - subclasses running (or about to be run) when calling this method. - - \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel() -*/ -void TaskTree::start() -{ - QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return); - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the" - "TaskTree handlers, ignoring..."); return); - d->start(); -} - -/*! - \fn void TaskTree::started() - - This signal is emitted when the task tree is started. The emission of this signal is - followed synchronously by the progressValueChanged() signal with an initial \c 0 value. - - \sa start(), done() -*/ - -/*! - \fn void TaskTree::done(DoneWith result) - - This signal is emitted when the task tree finished, passing the final \a result - of the execution. The task tree neither calls any handler, - nor emits any signal anymore after this signal was emitted. - - \note Do not delete the task tree directly from this signal's handler. - Use deleteLater() instead. - - \sa started() -*/ - -/*! - Cancels the execution of the running task tree. - - Cancels all the running tasks immediately. - All running tasks finish with an error, invoking their error handlers. - All running groups dispatch their handlers according to their workflow policies, - invoking their done handlers. The storages' onStorageDone() handlers are invoked, too. - The progressValueChanged() signals are also being sent. - This behavior may always be relied on. - - The \c cancel() function is executed synchronously, so that after a call to \c cancel() - all running tasks are finished and the tree is already canceled. - It's guaranteed that \c cancel() will run quickly, without any blocking wait for - the currently running tasks to finish, provided the used tasks implement their destructors - in a non-blocking way. - - When the task tree is empty, that is, constructed with a default constructor, - a call to \c cancel() is no-op and the relevant warning message is issued. - - Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored. - - \note Do not call this function directly from any of the running task's handlers - or task tree's signals. - - \sa ~TaskTree() -*/ -void TaskTree::cancel() -{ - QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the" - "TaskTree handlers, ignoring..."); return); - d->stop(); -} - -/*! - Returns \c true if the task tree is currently running; otherwise returns \c false. - - \sa start(), cancel() -*/ -bool TaskTree::isRunning() const -{ - return bool(d->m_runtimeRoot); -} - -/*! - Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree. - - Returns DoneWith::Success if the task tree finished successfully; - otherwise returns DoneWith::Error. - - \note Avoid using this method from the main thread. Use asynchronous start() instead. - This method is to be used in non-main threads or in auto tests. - - \sa start() -*/ -DoneWith TaskTree::runBlocking() -{ - QPromise<void> dummy; - dummy.start(); - return runBlocking(dummy.future()); -} - -/*! - \overload runBlocking() - - The passed \a future is used for listening to the cancel event. - When the task tree is canceled, this method cancels the passed \a future. -*/ -DoneWith TaskTree::runBlocking(const QFuture<void> &future) -{ - if (future.isCanceled()) - return DoneWith::Cancel; - - DoneWith doneWith = DoneWith::Cancel; - QEventLoop loop; - connect(this, &TaskTree::done, &loop, [&loop, &doneWith](DoneWith result) { - doneWith = result; - // Otherwise, the tasks from inside the running tree that were deleteLater() - // will be leaked. Refer to the QObject::deleteLater() docs. - QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection); - }); - QFutureWatcher<void> watcher; - connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::cancel); - watcher.setFuture(future); - - QTimer::singleShot(0, this, &TaskTree::start); - - loop.exec(QEventLoop::ExcludeUserInputEvents); - if (doneWith == DoneWith::Cancel) { - auto nonConstFuture = future; - nonConstFuture.cancel(); - } - return doneWith; -} - -/*! - Constructs a temporary task tree using the passed \a recipe and runs it blocking. - - Returns DoneWith::Success if the task tree finished successfully; - otherwise returns DoneWith::Error. - - \note Avoid using this method from the main thread. Use asynchronous start() instead. - This method is to be used in non-main threads or in auto tests. - - \sa start() -*/ -DoneWith TaskTree::runBlocking(const Group &recipe) -{ - QPromise<void> dummy; - dummy.start(); - return TaskTree::runBlocking(recipe, dummy.future()); -} - -/*! - \overload runBlocking(const Group &recipe) - - The passed \a future is used for listening to the cancel event. - When the task tree is canceled, this method cancels the passed \a future. -*/ -DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future) -{ - TaskTree taskTree(recipe); - return taskTree.runBlocking(future); -} - -/*! - Returns the current real count of asynchronous chains of invocations. - - The returned value indicates how many times the control returns to the caller's - event loop while the task tree is running. Initially, this value is 0. - If the execution of the task tree finishes fully synchronously, this value remains 0. - If the task tree contains any asynchronous tasks that are successfully started during - a call to start(), this value is bumped to 1 just before the call to start() finishes. - Later, when any asynchronous task finishes and any possible continuations are started, - this value is bumped again. The bumping continues until the task tree finishes. - When the task tree emits the done() signal, the bumping stops. - The asyncCountChanged() signal is emitted on every bump of this value. - - \sa asyncCountChanged() -*/ -int TaskTree::asyncCount() const -{ - return d->m_asyncCount; -} - -/*! - \fn void TaskTree::asyncCountChanged(int count) - - This signal is emitted when the running task tree is about to return control to the caller's - event loop. When the task tree is started, this signal is emitted with \a count value of 0, - and emitted later on every asyncCount() value bump with an updated \a count value. - Every signal sent (except the initial one with the value of 0) guarantees that the task tree - is still running asynchronously after the emission. - - \sa asyncCount() -*/ - -/*! - Returns the number of asynchronous tasks contained in the stored recipe. - - \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks. - \note Any task or group that was set up using withTimeout() increases the total number of - tasks by \c 1. - - \sa setRecipe(), progressMaximum() -*/ -int TaskTree::taskCount() const -{ - return d->m_root ? d->m_root->taskCount() : 0; -} - -/*! - \fn void TaskTree::progressValueChanged(int value) - - This signal is emitted when the running task tree finished, canceled, or skipped some tasks. - The \a value gives the current total number of finished, canceled or skipped tasks. - When the task tree is started, and after the started() signal was emitted, - this signal is emitted with an initial \a value of \c 0. - When the task tree is about to finish, and before the done() signal is emitted, - this signal is emitted with the final \a value of progressMaximum(). - - \sa progressValue(), progressMaximum() -*/ - -/*! - \fn int TaskTree::progressMaximum() const - - Returns the maximum progressValue(). - - \note Currently, it's the same as taskCount(). This might change in the future. - - \sa progressValue() -*/ - -/*! - Returns the current progress value, which is between the \c 0 and progressMaximum(). - - The returned number indicates how many tasks have been already finished, canceled, or skipped - while the task tree is running. - When the task tree is started, this number is set to \c 0. - When the task tree is finished, this number always equals progressMaximum(). - - \sa progressMaximum(), progressValueChanged() -*/ -int TaskTree::progressValue() const -{ - return d->m_progressValue; -} - -/*! - \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) - - Installs a storage setup \a handler for the \a storage to pass the initial data - dynamically to the running task tree. - - The \c StorageHandler takes a \e reference to the \c StorageStruct instance: - - \code - static void save(const QString &fileName, const QByteArray &array) { ... } - - Storage<QByteArray> storage; - - const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) { - concurrent.setConcurrentCallData(&save, "foo.txt", *storage); - }; - - const Group root { - storage, - ConcurrentCallTask(onSaverSetup) - }; - - TaskTree taskTree(root); - auto initStorage = [](QByteArray &storage){ - storage = "initial content"; - }; - taskTree.onStorageSetup(storage, initStorage); - taskTree.start(); - \endcode - - When the running task tree enters a Group where the \a storage is placed in, - it creates a \c StorageStruct instance, ready to be used inside this group. - Just after the \c StorageStruct instance is created, and before any handler of this group - is called, the task tree invokes the passed \a handler. This enables setting up - initial content for the given storage dynamically. Later, when any group's handler is invoked, - the task tree activates the created and initialized storage, so that it's available inside - any group's handler. - - \sa onStorageDone() -*/ - -/*! - \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) - - Installs a storage done \a handler for the \a storage to retrieve the final data - dynamically from the running task tree. - - The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance: - - \code - static QByteArray load(const QString &fileName) { ... } - - Storage<QByteArray> storage; - - const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) { - concurrent.setConcurrentCallData(&load, "foo.txt"); - }; - const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) { - *storage = concurrent.result(); - }; - - const Group root { - storage, - ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success) - }; - - TaskTree taskTree(root); - auto collectStorage = [](const QByteArray &storage){ - qDebug() << "final content" << storage; - }; - taskTree.onStorageDone(storage, collectStorage); - taskTree.start(); - \endcode - - When the running task tree is about to leave a Group where the \a storage is placed in, - it destructs a \c StorageStruct instance. - Just before the \c StorageStruct instance is destructed, and after all possible handlers from - this group were called, the task tree invokes the passed \a handler. This enables reading - the final content of the given storage dynamically and processing it further outside of - the task tree. - - This handler is called also when the running tree is canceled. However, it's not called - when the running tree is destructed. - - \sa onStorageSetup() -*/ - -void TaskTree::setupStorageHandler(const StorageBase &storage, - const StorageBase::StorageHandler &setupHandler, - const StorageBase::StorageHandler &doneHandler) -{ - auto it = d->m_storageHandlers.find(storage); - if (it == d->m_storageHandlers.end()) { - d->m_storageHandlers.insert(storage, {setupHandler, doneHandler}); - return; - } - if (setupHandler) { - QT_ASSERT(!it->m_setupHandler, - qWarning("The storage has its setup handler defined, overriding...")); - it->m_setupHandler = setupHandler; - } - if (doneHandler) { - QT_ASSERT(!it->m_doneHandler, - qWarning("The storage has its done handler defined, overriding...")); - it->m_doneHandler = doneHandler; - } -} - -TaskTreeTaskAdapter::TaskTreeTaskAdapter() -{ - connect(task(), &TaskTree::done, this, - [this](DoneWith result) { emit done(toDoneResult(result)); }); -} - -void TaskTreeTaskAdapter::start() -{ - task()->start(); -} - -using TimeoutCallback = std::function<void()>; - -struct TimerData -{ - system_clock::time_point m_deadline; - QPointer<QObject> m_context; - TimeoutCallback m_callback; -}; - -struct TimerThreadData -{ - Q_DISABLE_COPY_MOVE(TimerThreadData) - - TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20 - QHash<int, TimerData> m_timerIdToTimerData = {}; - QMap<system_clock::time_point, QList<int>> m_deadlineToTimerId = {}; - int m_timerIdCounter = 0; -}; - -// Please note the thread_local keyword below guarantees a separate instance per thread. -static thread_local TimerThreadData s_threadTimerData = {}; - -static void removeTimerId(int timerId) -{ - const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(timerId); - QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(), - qWarning("Removing active timerId failed."); return); - - const system_clock::time_point deadline = it->m_deadline; - s_threadTimerData.m_timerIdToTimerData.erase(it); - - QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline]; - const int removedCount = ids.removeAll(timerId); - QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return); - if (ids.isEmpty()) - s_threadTimerData.m_deadlineToTimerId.remove(deadline); -} - -static void handleTimeout(int timerId) -{ - const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(timerId); - if (itData == s_threadTimerData.m_timerIdToTimerData.cend()) - return; // The timer was already activated. - - const auto deadline = itData->m_deadline; - while (true) { - auto itMap = s_threadTimerData.m_deadlineToTimerId.begin(); - if (itMap == s_threadTimerData.m_deadlineToTimerId.end()) - return; - - if (itMap.key() > deadline) - return; - - std::optional<TimerData> timerData; - QList<int> &idList = *itMap; - if (!idList.isEmpty()) { - const int first = idList.first(); - idList.removeFirst(); - - const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(first); - if (it != s_threadTimerData.m_timerIdToTimerData.cend()) { - timerData = it.value(); - s_threadTimerData.m_timerIdToTimerData.erase(it); - } else { - QT_CHECK(false); - } - } else { - QT_CHECK(false); - } - - if (idList.isEmpty()) - s_threadTimerData.m_deadlineToTimerId.erase(itMap); - if (timerData && timerData->m_context) - timerData->m_callback(); - } -} - -static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback) -{ - const int timerId = ++s_threadTimerData.m_timerIdCounter; - const system_clock::time_point deadline = system_clock::now() + timeout; - QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); }); - s_threadTimerData.m_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback}); - s_threadTimerData.m_deadlineToTimerId[deadline].append(timerId); - return timerId; -} - -TimeoutTaskAdapter::TimeoutTaskAdapter() -{ - *task() = milliseconds::zero(); -} - -TimeoutTaskAdapter::~TimeoutTaskAdapter() -{ - if (m_timerId) - removeTimerId(*m_timerId); -} - -void TimeoutTaskAdapter::start() -{ - m_timerId = scheduleTimeout(*task(), this, [this] { - m_timerId.reset(); - emit done(DoneResult::Success); - }); -} - -ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, DoneResult result) -{ - return TimeoutTask([timeout](std::chrono::milliseconds &t) { t = timeout; }, result); -} - -/*! - \typealias Tasking::TaskTreeTask - - Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task. -*/ - -/*! - \typealias Tasking::TimeoutTask - - Type alias for the CustomTask, to be used inside recipes, associated with the - \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the - timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is, - the TimeoutTask finishes as soon as the control returns to the running event loop. - - Example usage: - - \code - using namespace std::chrono; - using namespace std::chrono_literals; - - const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; } - const auto onDone = [] { qDebug() << "Timed out."; } - - const Group root { - Timeout(onSetup, onDone) - }; - \endcode -*/ - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tasktree.h b/src/assets/downloader/tasking/tasktree.h deleted file mode 100644 index d46bd8eee3e..00000000000 --- a/src/assets/downloader/tasking/tasktree.h +++ /dev/null @@ -1,757 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TASKTREE_H -#define TASKING_TASKTREE_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" - -#include <QtCore/QList> -#include <QtCore/QObject> - -#include <memory> - -QT_BEGIN_NAMESPACE -template <class T> -class QFuture; - -namespace Tasking { - -class Do; -class For; -class Group; -class GroupItem; -using GroupItems = QList<GroupItem>; - -Q_NAMESPACE_EXPORT(TASKING_EXPORT) - -// WorkflowPolicy: -// 1. When all children finished with success -> report success, otherwise: -// a) Report error on first error and stop executing other children (including their subtree). -// b) On first error - continue executing all children and report error afterwards. -// 2. When all children finished with error -> report error, otherwise: -// a) Report success on first success and stop executing other children (including their subtree). -// b) On first success - continue executing all children and report success afterwards. -// 3. Stops on first finished child. In sequential mode it will never run other children then the first one. -// Useful only in parallel mode. -// 4. Always run all children, let them finish, ignore their results and report success afterwards. -// 5. Always run all children, let them finish, ignore their results and report error afterwards. - -enum class WorkflowPolicy -{ - StopOnError, // 1a - Reports error on first child error, otherwise success (if all children were success). - ContinueOnError, // 1b - The same, but children execution continues. Reports success when no children. - StopOnSuccess, // 2a - Reports success on first child success, otherwise error (if all children were error). - ContinueOnSuccess, // 2b - The same, but children execution continues. Reports error when no children. - StopOnSuccessOrError, // 3 - Stops on first finished child and report its result. - FinishAllAndSuccess, // 4 - Reports success after all children finished. - FinishAllAndError // 5 - Reports error after all children finished. -}; -Q_ENUM_NS(WorkflowPolicy) - -enum class SetupResult -{ - Continue, - StopWithSuccess, - StopWithError -}; -Q_ENUM_NS(SetupResult) - -enum class DoneResult -{ - Success, - Error -}; -Q_ENUM_NS(DoneResult) - -enum class DoneWith -{ - Success, - Error, - Cancel -}; -Q_ENUM_NS(DoneWith) - -enum class CallDoneIf -{ - SuccessOrError, - Success, - Error -}; -Q_ENUM_NS(CallDoneIf) - -TASKING_EXPORT DoneResult toDoneResult(bool success); - -class LoopData; -class StorageData; -class TaskTreePrivate; - -class TASKING_EXPORT TaskInterface : public QObject -{ - Q_OBJECT - -Q_SIGNALS: - void done(DoneResult result); - -private: - template <typename Task, typename Deleter> friend class TaskAdapter; - friend class TaskTreePrivate; - TaskInterface() = default; -#ifdef Q_QDOC -protected: -#endif - virtual void start() = 0; -}; - -class TASKING_EXPORT Loop -{ -public: - using Condition = std::function<bool(int)>; // Takes iteration, called prior to each iteration. - using ValueGetter = std::function<const void *(int)>; // Takes iteration, returns ptr to ref. - - int iteration() const; - -protected: - Loop(); // LoopForever - Loop(int count, const ValueGetter &valueGetter = {}); // LoopRepeat, LoopList - Loop(const Condition &condition); // LoopUntil - - const void *valuePtr() const; - -private: - friend class ExecutionContextActivator; - friend class TaskTreePrivate; - std::shared_ptr<LoopData> m_loopData; -}; - -class TASKING_EXPORT LoopForever final : public Loop -{ -public: - LoopForever() : Loop() {} -}; - -class TASKING_EXPORT LoopRepeat final : public Loop -{ -public: - LoopRepeat(int count) : Loop(count) {} -}; - -class TASKING_EXPORT LoopUntil final : public Loop -{ -public: - LoopUntil(const Condition &condition) : Loop(condition) {} -}; - -template <typename T> -class LoopList final : public Loop -{ -public: - LoopList(const QList<T> &list) : Loop(list.size(), [list](int i) { return &list.at(i); }) {} - const T *operator->() const { return static_cast<const T *>(valuePtr()); } - const T &operator*() const { return *static_cast<const T *>(valuePtr()); } -}; - -class TASKING_EXPORT StorageBase -{ -private: - using StorageConstructor = std::function<void *(void)>; - using StorageDestructor = std::function<void(void *)>; - using StorageHandler = std::function<void(void *)>; - - StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor); - - void *activeStorageVoid() const; - - friend bool operator==(const StorageBase &first, const StorageBase &second) - { return first.m_storageData == second.m_storageData; } - - friend bool operator!=(const StorageBase &first, const StorageBase &second) - { return first.m_storageData != second.m_storageData; } - - friend size_t qHash(const StorageBase &storage, uint seed = 0) - { return size_t(storage.m_storageData.get()) ^ seed; } - - std::shared_ptr<StorageData> m_storageData; - - template <typename StorageStruct> friend class Storage; - friend class ExecutionContextActivator; - friend class StorageData; - friend class RuntimeContainer; - friend class TaskTree; - friend class TaskTreePrivate; -}; - -template <typename StorageStruct> -class Storage final : public StorageBase -{ -public: - Storage() : StorageBase(Storage::ctor(), Storage::dtor()) {} -#if __cplusplus >= 201803L // C++20: Allow pack expansion in lambda init-capture. - template <typename ...Args> - Storage(const Args &...args) - : StorageBase([...args = args] { return new StorageStruct(args...); }, Storage::dtor()) {} -#else // C++17 - template <typename ...Args> - Storage(const Args &...args) - : StorageBase([argsTuple = std::tuple(args...)] { - return std::apply([](const Args &...arguments) { return new StorageStruct(arguments...); }, argsTuple); - }, Storage::dtor()) {} -#endif - StorageStruct &operator*() const noexcept { return *activeStorage(); } - StorageStruct *operator->() const noexcept { return activeStorage(); } - StorageStruct *activeStorage() const { - return static_cast<StorageStruct *>(activeStorageVoid()); - } - -private: - static StorageConstructor ctor() { return [] { return new StorageStruct(); }; } - static StorageDestructor dtor() { - return [](void *storage) { delete static_cast<StorageStruct *>(storage); }; - } -}; - -class TASKING_EXPORT GroupItem -{ -public: - // Called when group entered, after group's storages are created - using GroupSetupHandler = std::function<SetupResult()>; - // Called when group done, before group's storages are deleted - using GroupDoneHandler = std::function<DoneResult(DoneWith)>; - - template <typename StorageStruct> - GroupItem(const Storage<StorageStruct> &storage) - : m_type(Type::Storage) - , m_storageList{storage} {} - - // TODO: Add tests. - GroupItem(const GroupItems &children) : m_type(Type::List) { addChildren(children); } - GroupItem(std::initializer_list<GroupItem> children) : m_type(Type::List) { addChildren(children); } - -protected: - GroupItem(const Loop &loop) : GroupItem(GroupData{{}, {}, {}, loop}) {} - // Internal, provided by CustomTask - using InterfaceCreateHandler = std::function<TaskInterface *(void)>; - // Called prior to task start, just after createHandler - using InterfaceSetupHandler = std::function<SetupResult(TaskInterface &)>; - // Called on task done, just before deleteLater - using InterfaceDoneHandler = std::function<DoneResult(const TaskInterface &, DoneWith)>; - - struct TaskHandler { - InterfaceCreateHandler m_createHandler; - InterfaceSetupHandler m_setupHandler = {}; - InterfaceDoneHandler m_doneHandler = {}; - CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError; - }; - - struct GroupHandler { - GroupSetupHandler m_setupHandler; - GroupDoneHandler m_doneHandler = {}; - CallDoneIf m_callDoneIf = CallDoneIf::SuccessOrError; - }; - - struct GroupData { - GroupHandler m_groupHandler = {}; - std::optional<int> m_parallelLimit = {}; - std::optional<WorkflowPolicy> m_workflowPolicy = {}; - std::optional<Loop> m_loop = {}; - }; - - enum class Type { - List, - Group, - GroupData, - Storage, - TaskHandler - }; - - GroupItem() = default; - GroupItem(Type type) : m_type(type) { } - GroupItem(const GroupData &data) - : m_type(Type::GroupData) - , m_groupData(data) {} - GroupItem(const TaskHandler &handler) - : m_type(Type::TaskHandler) - , m_taskHandler(handler) {} - void addChildren(const GroupItems &children); - - static GroupItem groupHandler(const GroupHandler &handler) { return GroupItem({handler}); } - - // Checks if Function may be invoked with Args and if Function's return type is Result. - template <typename Result, typename Function, typename ...Args, - typename DecayedFunction = std::decay_t<Function>> - static constexpr bool isInvocable() - { - // Note, that std::is_invocable_r_v doesn't check Result type properly. - if constexpr (std::is_invocable_r_v<Result, DecayedFunction, Args...>) - return std::is_same_v<Result, std::invoke_result_t<DecayedFunction, Args...>>; - return false; - } - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - friend class ContainerNode; - friend class TaskNode; - friend class TaskTreePrivate; - friend class ParallelLimitFunctor; - friend class WorkflowPolicyFunctor; - Type m_type = Type::Group; - GroupItems m_children; - GroupData m_groupData; - QList<StorageBase> m_storageList; - TaskHandler m_taskHandler; -}; - -class TASKING_EXPORT ExecutableItem : public GroupItem -{ -public: - Group withTimeout(std::chrono::milliseconds timeout, - const std::function<void()> &handler = {}) const; - Group withLog(const QString &logName) const; - template <typename SenderSignalPairGetter> - Group withCancel(SenderSignalPairGetter &&getter, std::initializer_list<GroupItem> postCancelRecipe = {}) const; - template <typename SenderSignalPairGetter> - Group withAccept(SenderSignalPairGetter &&getter) const; - -protected: - ExecutableItem() = default; - ExecutableItem(const TaskHandler &handler) : GroupItem(handler) {} - -private: - TASKING_EXPORT friend Group operator!(const ExecutableItem &item); - TASKING_EXPORT friend Group operator&&(const ExecutableItem &first, - const ExecutableItem &second); - TASKING_EXPORT friend Group operator||(const ExecutableItem &first, - const ExecutableItem &second); - TASKING_EXPORT friend Group operator&&(const ExecutableItem &item, DoneResult result); - TASKING_EXPORT friend Group operator||(const ExecutableItem &item, DoneResult result); - - Group withCancelImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper, - const GroupItems &postCancelRecipe) const; - Group withAcceptImpl( - const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const; -}; - -class TASKING_EXPORT Group : public ExecutableItem -{ -public: - Group(const GroupItems &children) { addChildren(children); } - Group(std::initializer_list<GroupItem> children) { addChildren(children); } - - // GroupData related: - template <typename Handler> - static GroupItem onGroupSetup(Handler &&handler) { - return groupHandler({wrapGroupSetup(std::forward<Handler>(handler))}); - } - template <typename Handler> - static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) { - return groupHandler({{}, wrapGroupDone(std::forward<Handler>(handler)), callDoneIf}); - } - -private: - template <typename Handler> - static GroupSetupHandler wrapGroupSetup(Handler &&handler) - { - // R, V stands for: Setup[R]esult, [V]oid - static constexpr bool isR = isInvocable<SetupResult, Handler>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isR || isV, - "Group setup handler needs to take no arguments and has to return void or SetupResult. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)] { - if constexpr (isR) - return std::invoke(handler); - std::invoke(handler); - return SetupResult::Continue; - }; - } - template <typename Handler> - static GroupDoneHandler wrapGroupDone(Handler &&handler) - { - static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>; - // R, B, V, D stands for: Done[R]esult, [B]ool, [V]oid, [D]oneWith - static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>(); - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isVD = isInvocable<void, Handler, DoneWith>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isDoneResultType || isRD || isR || isBD || isB || isVD || isV, - "Group done handler needs to take (DoneWith) or (void) as an argument and has to " - "return void, bool or DoneResult. Alternatively, it may be of DoneResult type. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](DoneWith result) { - if constexpr (isDoneResultType) - return handler; - if constexpr (isRD) - return std::invoke(handler, result); - if constexpr (isR) - return std::invoke(handler); - if constexpr (isBD) - return toDoneResult(std::invoke(handler, result)); - if constexpr (isB) - return toDoneResult(std::invoke(handler)); - if constexpr (isVD) - std::invoke(handler, result); - else if constexpr (isV) - std::invoke(handler); - return toDoneResult(result == DoneWith::Success); - }; - } -}; - -template <typename SenderSignalPairGetter> -Group ExecutableItem::withCancel(SenderSignalPairGetter &&getter, - std::initializer_list<GroupItem> postCancelRecipe) const -{ - const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) { - const auto senderSignalPair = getter(); - QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] { - trigger(); - }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); - }; - return withCancelImpl(connectWrapper, postCancelRecipe); -} - -template <typename SenderSignalPairGetter> -Group ExecutableItem::withAccept(SenderSignalPairGetter &&getter) const -{ - const auto connectWrapper = [getter](QObject *guard, const std::function<void()> &trigger) { - const auto senderSignalPair = getter(); - QObject::connect(senderSignalPair.first, senderSignalPair.second, guard, [trigger] { - trigger(); - }, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); - }; - return withAcceptImpl(connectWrapper); -} - -template <typename Handler> -static GroupItem onGroupSetup(Handler &&handler) -{ - return Group::onGroupSetup(std::forward<Handler>(handler)); -} - -template <typename Handler> -static GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) -{ - return Group::onGroupDone(std::forward<Handler>(handler), callDoneIf); -} - -// Default: 1 (sequential). 0 means unlimited (parallel). -TASKING_EXPORT GroupItem parallelLimit(int limit); - -// Default: WorkflowPolicy::StopOnError. -TASKING_EXPORT GroupItem workflowPolicy(WorkflowPolicy policy); - -TASKING_EXPORT extern const GroupItem sequential; -TASKING_EXPORT extern const GroupItem parallel; -TASKING_EXPORT extern const GroupItem parallelIdealThreadCountLimit; - -TASKING_EXPORT extern const GroupItem stopOnError; -TASKING_EXPORT extern const GroupItem continueOnError; -TASKING_EXPORT extern const GroupItem stopOnSuccess; -TASKING_EXPORT extern const GroupItem continueOnSuccess; -TASKING_EXPORT extern const GroupItem stopOnSuccessOrError; -TASKING_EXPORT extern const GroupItem finishAllAndSuccess; -TASKING_EXPORT extern const GroupItem finishAllAndError; - -TASKING_EXPORT extern const GroupItem nullItem; -TASKING_EXPORT extern const ExecutableItem successItem; -TASKING_EXPORT extern const ExecutableItem errorItem; - -class TASKING_EXPORT For final -{ -public: - explicit For(const Loop &loop) : m_loop(loop) {} - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - - Loop m_loop; -}; - -class When; - -class TASKING_EXPORT Do final -{ -public: - explicit Do(const GroupItems &children) : m_children(children) {} - explicit Do(std::initializer_list<GroupItem> children) : m_children(children) {} - -private: - TASKING_EXPORT friend Group operator>>(const For &forItem, const Do &doItem); - TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem); - - GroupItem m_children; -}; - -class TASKING_EXPORT Forever final : public ExecutableItem -{ -public: - explicit Forever(const GroupItems &children) - { addChildren({ For (LoopForever()) >> Do { children } } ); } - explicit Forever(std::initializer_list<GroupItem> children) - { addChildren({ For (LoopForever()) >> Do { children } } ); } -}; - -// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount() -class TASKING_EXPORT Sync final : public ExecutableItem -{ -public: - template <typename Handler> - Sync(Handler &&handler) { - addChildren({ onGroupDone(wrapHandler(std::forward<Handler>(handler))) }); - } - -private: - template <typename Handler> - static auto wrapHandler(Handler &&handler) { - // R, B, V stands for: Done[R]esult, [B]ool, [V]oid - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isR || isB || isV, - "Sync handler needs to take no arguments and has to return void, bool or DoneResult. " - "The passed handler doesn't fulfill these requirements."); - return handler; - } -}; - -template <typename Task, typename Deleter = std::default_delete<Task>> -class TaskAdapter : public TaskInterface -{ -protected: - TaskAdapter() : m_task(new Task) {} - Task *task() { return m_task.get(); } - const Task *task() const { return m_task.get(); } - -private: - using TaskType = Task; - using DeleterType = Deleter; - template <typename Adapter> friend class CustomTask; - std::unique_ptr<Task, Deleter> m_task; -}; - -template <typename Adapter> -class CustomTask final : public ExecutableItem -{ -public: - using Task = typename Adapter::TaskType; - using Deleter = typename Adapter::DeleterType; - static_assert(std::is_base_of_v<TaskAdapter<Task, Deleter>, Adapter>, - "The Adapter type for the CustomTask<Adapter> needs to be derived from " - "TaskAdapter<Task>."); - using TaskSetupHandler = std::function<SetupResult(Task &)>; - using TaskDoneHandler = std::function<DoneResult(const Task &, DoneWith)>; - - template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> - CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), - CallDoneIf callDoneIf = CallDoneIf::SuccessOrError) - : ExecutableItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)), - wrapDone(std::forward<DoneHandler>(done)), callDoneIf}) - {} - -private: - static Adapter *createAdapter() { return new Adapter; } - - template <typename Handler> - static InterfaceSetupHandler wrapSetup(Handler &&handler) { - if constexpr (std::is_same_v<Handler, TaskSetupHandler>) - return {}; // When user passed {} for the setup handler. - // R, V stands for: Setup[R]esult, [V]oid - static constexpr bool isR = isInvocable<SetupResult, Handler, Task &>(); - static constexpr bool isV = isInvocable<void, Handler, Task &>(); - static_assert(isR || isV, - "Task setup handler needs to take (Task &) as an argument and has to return void or " - "SetupResult. The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](TaskInterface &taskInterface) { - Adapter &adapter = static_cast<Adapter &>(taskInterface); - if constexpr (isR) - return std::invoke(handler, *adapter.task()); - std::invoke(handler, *adapter.task()); - return SetupResult::Continue; - }; - } - - template <typename Handler> - static InterfaceDoneHandler wrapDone(Handler &&handler) { - if constexpr (std::is_same_v<Handler, TaskDoneHandler>) - return {}; // User passed {} for the done handler. - static constexpr bool isDoneResultType = std::is_same_v<std::decay_t<Handler>, DoneResult>; - // R, B, V, T, D stands for: Done[R]esult, [B]ool, [V]oid, [T]ask, [D]oneWith - static constexpr bool isRTD = isInvocable<DoneResult, Handler, const Task &, DoneWith>(); - static constexpr bool isRT = isInvocable<DoneResult, Handler, const Task &>(); - static constexpr bool isRD = isInvocable<DoneResult, Handler, DoneWith>(); - static constexpr bool isR = isInvocable<DoneResult, Handler>(); - static constexpr bool isBTD = isInvocable<bool, Handler, const Task &, DoneWith>(); - static constexpr bool isBT = isInvocable<bool, Handler, const Task &>(); - static constexpr bool isBD = isInvocable<bool, Handler, DoneWith>(); - static constexpr bool isB = isInvocable<bool, Handler>(); - static constexpr bool isVTD = isInvocable<void, Handler, const Task &, DoneWith>(); - static constexpr bool isVT = isInvocable<void, Handler, const Task &>(); - static constexpr bool isVD = isInvocable<void, Handler, DoneWith>(); - static constexpr bool isV = isInvocable<void, Handler>(); - static_assert(isDoneResultType || isRTD || isRT || isRD || isR - || isBTD || isBT || isBD || isB - || isVTD || isVT || isVD || isV, - "Task done handler needs to take (const Task &, DoneWith), (const Task &), " - "(DoneWith) or (void) as arguments and has to return void, bool or DoneResult. " - "Alternatively, it may be of DoneResult type. " - "The passed handler doesn't fulfill these requirements."); - return [handler = std::move(handler)](const TaskInterface &taskInterface, DoneWith result) { - if constexpr (isDoneResultType) - return handler; - const Adapter &adapter = static_cast<const Adapter &>(taskInterface); - if constexpr (isRTD) - return std::invoke(handler, *adapter.task(), result); - if constexpr (isRT) - return std::invoke(handler, *adapter.task()); - if constexpr (isRD) - return std::invoke(handler, result); - if constexpr (isR) - return std::invoke(handler); - if constexpr (isBTD) - return toDoneResult(std::invoke(handler, *adapter.task(), result)); - if constexpr (isBT) - return toDoneResult(std::invoke(handler, *adapter.task())); - if constexpr (isBD) - return toDoneResult(std::invoke(handler, result)); - if constexpr (isB) - return toDoneResult(std::invoke(handler)); - if constexpr (isVTD) - std::invoke(handler, *adapter.task(), result); - else if constexpr (isVT) - std::invoke(handler, *adapter.task()); - else if constexpr (isVD) - std::invoke(handler, result); - else if constexpr (isV) - std::invoke(handler); - return toDoneResult(result == DoneWith::Success); - }; - } -}; - -template <typename Task> -class SimpleTaskAdapter final : public TaskAdapter<Task> -{ -public: - SimpleTaskAdapter() { this->connect(this->task(), &Task::done, this, &TaskInterface::done); } - void start() final { this->task()->start(); } -}; - -// A convenient helper, when: -// 1. Task is derived from QObject. -// 2. Task::start() method starts the task. -// 3. Task::done(DoneResult) signal is emitted when the task is finished. -template <typename Task> -using SimpleCustomTask = CustomTask<SimpleTaskAdapter<Task>>; - -class TASKING_EXPORT TaskTree final : public QObject -{ - Q_OBJECT - -public: - TaskTree(); - TaskTree(const Group &recipe); - ~TaskTree(); - - void setRecipe(const Group &recipe); - - void start(); - void cancel(); - bool isRunning() const; - - // Helper methods. They execute a local event loop with ExcludeUserInputEvents. - // The passed future is used for listening to the cancel event. - // Don't use it in main thread. To be used in non-main threads or in auto tests. - DoneWith runBlocking(); - DoneWith runBlocking(const QFuture<void> &future); - static DoneWith runBlocking(const Group &recipe); - static DoneWith runBlocking(const Group &recipe, const QFuture<void> &future); - - int asyncCount() const; - int taskCount() const; - int progressMaximum() const { return taskCount(); } - int progressValue() const; // all finished / skipped / stopped tasks, groups itself excluded - - template <typename StorageStruct, typename Handler> - void onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler) { - static_assert(std::is_invocable_v<std::decay_t<Handler>, StorageStruct &>, - "Storage setup handler needs to take (Storage &) as an argument. " - "The passed handler doesn't fulfill this requirement."); - setupStorageHandler(storage, - wrapHandler<StorageStruct>(std::forward<Handler>(handler)), {}); - } - template <typename StorageStruct, typename Handler> - void onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler) { - static_assert(std::is_invocable_v<std::decay_t<Handler>, const StorageStruct &>, - "Storage done handler needs to take (const Storage &) as an argument. " - "The passed handler doesn't fulfill this requirement."); - setupStorageHandler(storage, {}, - wrapHandler<const StorageStruct>(std::forward<Handler>(handler))); - } - -Q_SIGNALS: - void started(); - void done(DoneWith result); - void asyncCountChanged(int count); - void progressValueChanged(int value); // updated whenever task finished / skipped / stopped - -private: - void setupStorageHandler(const StorageBase &storage, - const StorageBase::StorageHandler &setupHandler, - const StorageBase::StorageHandler &doneHandler); - template <typename StorageStruct, typename Handler> - StorageBase::StorageHandler wrapHandler(Handler &&handler) { - return [handler](void *voidStruct) { - auto *storageStruct = static_cast<StorageStruct *>(voidStruct); - std::invoke(handler, *storageStruct); - }; - } - - TaskTreePrivate *d; -}; - -class TASKING_EXPORT TaskTreeTaskAdapter : public TaskAdapter<TaskTree> -{ -public: - TaskTreeTaskAdapter(); - -private: - void start() final; -}; - -class TASKING_EXPORT TimeoutTaskAdapter : public TaskAdapter<std::chrono::milliseconds> -{ -public: - TimeoutTaskAdapter(); - ~TimeoutTaskAdapter(); - -private: - void start() final; - std::optional<int> m_timerId; -}; - -using TaskTreeTask = CustomTask<TaskTreeTaskAdapter>; -using TimeoutTask = CustomTask<TimeoutTaskAdapter>; - -TASKING_EXPORT ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, - DoneResult result = DoneResult::Error); - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TASKTREE_H diff --git a/src/assets/downloader/tasking/tasktreerunner.cpp b/src/assets/downloader/tasking/tasktreerunner.cpp deleted file mode 100644 index 6ed642b1bfd..00000000000 --- a/src/assets/downloader/tasking/tasktreerunner.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tasktreerunner.h" - -#include "tasktree.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -TaskTreeRunner::~TaskTreeRunner() = default; - -void TaskTreeRunner::start(const Group &recipe, - const SetupHandler &setupHandler, - const DoneHandler &doneHandler) -{ - m_taskTree.reset(new TaskTree(recipe)); - connect(m_taskTree.get(), &TaskTree::done, this, [this, doneHandler](DoneWith result) { - m_taskTree.release()->deleteLater(); - if (doneHandler) - doneHandler(result); - emit done(result); - }); - if (setupHandler) - setupHandler(m_taskTree.get()); - emit aboutToStart(m_taskTree.get()); - m_taskTree->start(); -} - -void TaskTreeRunner::cancel() -{ - if (m_taskTree) - m_taskTree->cancel(); -} - -void TaskTreeRunner::reset() -{ - m_taskTree.reset(); -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tasktreerunner.h b/src/assets/downloader/tasking/tasktreerunner.h deleted file mode 100644 index f91e7608113..00000000000 --- a/src/assets/downloader/tasking/tasktreerunner.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TASKTREERUNNER_H -#define TASKING_TASKTREERUNNER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasking_global.h" -#include "tasktree.h" - -#include <QtCore/QObject> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -class TASKING_EXPORT TaskTreeRunner : public QObject -{ - Q_OBJECT - -public: - using SetupHandler = std::function<void(TaskTree *)>; - using DoneHandler = std::function<void(DoneWith)>; - - ~TaskTreeRunner(); - - bool isRunning() const { return bool(m_taskTree); } - - // When task tree is running it resets the old task tree. - void start(const Group &recipe, - const SetupHandler &setupHandler = {}, - const DoneHandler &doneHandler = {}); - - // When task tree is running it emits done(DoneWith::Cancel) synchronously. - void cancel(); - - // No done() signal is emitted. - void reset(); - -Q_SIGNALS: - void aboutToStart(TaskTree *taskTree); - void done(DoneWith result); - -private: - std::unique_ptr<TaskTree> m_taskTree; -}; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TASKTREERUNNER_H diff --git a/src/assets/downloader/tasking/tcpsocket.cpp b/src/assets/downloader/tasking/tcpsocket.cpp deleted file mode 100644 index 19aab8fc714..00000000000 --- a/src/assets/downloader/tasking/tcpsocket.cpp +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#include "tcpsocket.h" - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -void TcpSocket::start() -{ - if (m_socket) { - qWarning("The TcpSocket is already running. Ignoring the call to start()."); - return; - } - if (m_address.isNull()) { - qWarning("Can't start the TcpSocket with invalid address. " - "Stopping with an error."); - m_error = QAbstractSocket::HostNotFoundError; - emit done(DoneResult::Error); - return; - } - - m_socket.reset(new QTcpSocket); - connect(m_socket.get(), &QAbstractSocket::errorOccurred, this, - [this](QAbstractSocket::SocketError error) { - m_error = error; - m_socket->disconnect(); - emit done(DoneResult::Error); - m_socket.release()->deleteLater(); - }); - connect(m_socket.get(), &QAbstractSocket::connected, this, [this] { - if (!m_writeData.isEmpty()) - m_socket->write(m_writeData); - emit started(); - }); - connect(m_socket.get(), &QAbstractSocket::disconnected, this, [this] { - m_socket->disconnect(); - emit done(DoneResult::Success); - m_socket.release()->deleteLater(); - }); - - m_socket->connectToHost(m_address, m_port); -} - -TcpSocket::~TcpSocket() -{ - if (m_socket) { - m_socket->disconnect(); - m_socket->abort(); - } -} - -} // namespace Tasking - -QT_END_NAMESPACE diff --git a/src/assets/downloader/tasking/tcpsocket.h b/src/assets/downloader/tasking/tcpsocket.h deleted file mode 100644 index ec0069bf130..00000000000 --- a/src/assets/downloader/tasking/tcpsocket.h +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2024 Jarek Kobus -// Copyright (C) 2024 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only - -#ifndef TASKING_TCPSOCKET_H -#define TASKING_TCPSOCKET_H - -#include "tasking_global.h" - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include "tasktree.h" - -#include <QtNetwork/QTcpSocket> - -#include <memory> - -QT_BEGIN_NAMESPACE - -namespace Tasking { - -// This class introduces the dependency to Qt::Network, otherwise Tasking namespace -// is independent on Qt::Network. -// Possibly, it could be placed inside Qt::Network library, as a wrapper around QTcpSocket. - -class TASKING_EXPORT TcpSocket final : public QObject -{ - Q_OBJECT - -public: - ~TcpSocket(); - void setAddress(const QHostAddress &address) { m_address = address; } - void setPort(quint16 port) { m_port = port; } - void setWriteData(const QByteArray &data) { m_writeData = data; } - QTcpSocket *socket() const { return m_socket.get(); } - void start(); - -Q_SIGNALS: - void started(); - void done(DoneResult result); - -private: - QHostAddress m_address; - quint16 m_port = 0; - QByteArray m_writeData; - std::unique_ptr<QTcpSocket> m_socket; - QAbstractSocket::SocketError m_error = QAbstractSocket::UnknownSocketError; -}; - -using TcpSocketTask = SimpleCustomTask<TcpSocket>; - -} // namespace Tasking - -QT_END_NAMESPACE - -#endif // TASKING_TCPSOCKET_H diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 1147205b79f..c4a3480b2f9 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -1028,7 +1028,7 @@ qt_internal_extend_target(Core qt_internal_extend_target(Core CONDITION - QT_FEATURE_timezone AND UNIX + QT_FEATURE_timezone AND UNIX AND NOT VXWORKS AND NOT ANDROID AND NOT APPLE AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_tz.cpp @@ -1037,7 +1037,7 @@ qt_internal_extend_target(Core qt_internal_extend_target(Core CONDITION QT_FEATURE_icu AND QT_FEATURE_timezone - AND NOT UNIX AND NOT QT_FEATURE_timezone_tzdb + AND (VXWORKS OR NOT UNIX) AND NOT QT_FEATURE_timezone_tzdb SOURCES time/qtimezoneprivate_icu.cpp ) diff --git a/src/corelib/configure.cmake b/src/corelib/configure.cmake index d951b85c147..edcfba0f6ce 100644 --- a/src/corelib/configure.cmake +++ b/src/corelib/configure.cmake @@ -1150,7 +1150,7 @@ qt_feature("timezone" PUBLIC SECTION "Utilities" LABEL "QTimeZone" PURPOSE "Provides support for time-zone handling." - CONDITION NOT WASM AND NOT VXWORKS + CONDITION NOT WASM ) qt_feature("timezone_locale" PRIVATE SECTION "Utilities" diff --git a/src/corelib/doc/images/javaiterators1.svg b/src/corelib/doc/images/javaiterators1.svg new file mode 100644 index 00000000000..468dbe5371c --- /dev/null +++ b/src/corelib/doc/images/javaiterators1.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="340" + height="110" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .line-style { stroke: red; fill: none } + svg .text-style { font: 20px arial; fill: black } + svg .fill-style { stroke: none; fill: red } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .line-style { stroke: red; fill: none } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .fill-style { stroke: none; fill: red } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .line-style { stroke: red; fill: none } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .fill-style { stroke: none; fill: red } +</style> + +<g transform="translate(10.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">A</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(90.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">B</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(170.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">C</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(250.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">D</text> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +<g transform="translate(330.5, 10.5)"> +<path d="M 0,60 v 30" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +</g> + +</svg> diff --git a/src/corelib/doc/images/javaiterators2.svg b/src/corelib/doc/images/javaiterators2.svg new file mode 100644 index 00000000000..df4c6b352a6 --- /dev/null +++ b/src/corelib/doc/images/javaiterators2.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + version="1.1" + width="340" + height="130" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + +<style> + svg .box-style { stroke: black; fill: white } + svg .line-style { stroke: red; fill: none } + svg .fill-style { stroke: none; fill: red } + svg .np-line-style { stroke: blue; fill: none } + svg .np-fill-style { stroke: none; fill: blue } + svg .text-style { font: 20px arial; fill: black } + svg .small-text-style { font: 12px monospace; fill: black } + + [data-theme="dark"] svg .box-style { stroke: #f2f2f2; fill: black } + [data-theme="dark"] svg .line-style { stroke: red; fill: none } + [data-theme="dark"] svg .fill-style { stroke: none; fill: red } + [data-theme="dark"] svg .np-line-style { stroke: #4080ff; fill: none; stroke-width: 1.5 } + [data-theme="dark"] svg .np-fill-style { stroke: none; fill: #4080ff } + [data-theme="dark"] svg .text-style { font: 20px arial; fill: #f2f2f2 } + [data-theme="dark"] svg .small-text-style { font: 12px monospace; fill: #f2f2f2 } + + [data-theme="light"] svg .box-style { stroke: black; fill: white } + [data-theme="light"] svg .line-style { stroke: red; fill: none } + [data-theme="light"] svg .fill-style { stroke: none; fill: red } + [data-theme="light"] svg .np-line-style { stroke: blue; fill: none } + [data-theme="light"] svg .np-fill-style { stroke: none; fill: blue } + [data-theme="light"] svg .text-style { font: 20px arial; fill: black } + [data-theme="light"] svg .small-text-style { font: 12px monospace; fill: black } +</style> + +<g transform="translate(10.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">A</text> +</g> + +<g transform="translate(90.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">B</text> +<path d="M 0,60 c 0,30 50,40 80,30" class="np-line-style" /> +<path d="M 0,60 l -2,14 l 11,-4 z" class="np-fill-style" /> +<text x="-15" y="110" class="small-text-style">previous()</text> +</g> + +<g transform="translate(170.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">C</text> +<path d="M 0,60 v 50" class="line-style" /> +<path d="M 0,60 l -5,10 l 10,0 z" class="fill-style" /> +<text x="30" y="110" class="small-text-style">next()</text> +</g> + +<g transform="translate(250.5, 10.5)"> +<path d="m 0,0 h 80 v 60 h -80 z" class="box-style" /> +<text x="35" y="36" class="text-style">D</text> +<path d="M 0,60 c 0,30 -50,40 -80,30" class="np-line-style" /> +<path d="M 0,60 l 2,14 l -11,-4 z" class="np-fill-style" /> +</g> + +</svg> diff --git a/src/corelib/doc/src/java-style-iterators.qdoc b/src/corelib/doc/src/java-style-iterators.qdoc index 856005cb66c..a0e5c53d633 100644 --- a/src/corelib/doc/src/java-style-iterators.qdoc +++ b/src/corelib/doc/src/java-style-iterators.qdoc @@ -42,7 +42,7 @@ diagram below shows the valid iterator positions as red arrows for a list containing four items: - \image javaiterators1.png + \image javaiterators1.svg Java-style iterators point between items Here's a typical loop for iterating through all the elements of a QList<QString> in order: @@ -70,7 +70,7 @@ \l{QListIterator::next()}{next()} and \l{QListIterator::previous()}{previous()} on an iterator: - \image javaiterators2.png + \image javaiterators2.png Iterating to the next and previous items The following table summarizes the QListIterator API: diff --git a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc index 9b3ea6ae660..5dc2e6dad12 100644 --- a/src/corelib/doc/src/objectmodel/bindableproperties.qdoc +++ b/src/corelib/doc/src/objectmodel/bindableproperties.qdoc @@ -172,7 +172,7 @@ The following illustrates this approach. - \badcode + \code void DerivedClass::setValue(int val) { // do something @@ -247,7 +247,7 @@ be called for the current value of the property, register your callback using subscribe() instead. - \section1 Interaction with Q_PROPERTYs + \section1 Interaction with Q_PROPERTY A \l {The Property System}{Q_PROPERTY} that defines \c BINDABLE can be bound and used in binding expressions. You can implement such properties using \l {QProperty}, diff --git a/src/corelib/io/qlockfile.cpp b/src/corelib/io/qlockfile.cpp index 075eb144e51..4d431b46213 100644 --- a/src/corelib/io/qlockfile.cpp +++ b/src/corelib/io/qlockfile.cpp @@ -379,8 +379,9 @@ QLockFilePrivate::~QLockFilePrivate() QByteArray QLockFilePrivate::lockFileContents() const { // Use operator% from the fast builder to avoid multiple memory allocations. - return QByteArray::number(QCoreApplication::applicationPid()) % '\n' - % processNameByPid(QCoreApplication::applicationPid()).toUtf8() % '\n' + qint64 pid = QCoreApplication::applicationPid(); + return QByteArray::number(pid) % '\n' + % processNameByPid(pid).toUtf8() % '\n' % machineName().toUtf8() % '\n' % QSysInfo::machineUniqueId() % '\n' % QSysInfo::bootUniqueId() % '\n'; diff --git a/src/corelib/itemmodels/qrangemodel.cpp b/src/corelib/itemmodels/qrangemodel.cpp index 05e3a39e589..b0c6c46b125 100644 --- a/src/corelib/itemmodels/qrangemodel.cpp +++ b/src/corelib/itemmodels/qrangemodel.cpp @@ -20,6 +20,8 @@ public: private: std::unique_ptr<QRangeModelImplBase, QRangeModelImplBase::Deleter> impl; + friend class QRangeModelImplBase; + mutable QHash<int, QByteArray> m_roleNames; }; @@ -28,6 +30,16 @@ QRangeModel::QRangeModel(QRangeModelImplBase *impl, QObject *parent) { } +QRangeModelImplBase *QRangeModelImplBase::getImplementation(QRangeModel *model) +{ + return model->d_func()->impl.get(); +} + +const QRangeModelImplBase *QRangeModelImplBase::getImplementation(const QRangeModel *model) +{ + return model->d_func()->impl.get(); +} + /*! \class QRangeModel \inmodule QtCore diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h index f38dc88c0d9..c2f473542d7 100644 --- a/src/corelib/itemmodels/qrangemodel_impl.h +++ b/src/corelib/itemmodels/qrangemodel_impl.h @@ -842,6 +842,11 @@ namespace QRangeModelDetails { using protocol = wrapped_t<Protocol>; using row = typename range_traits<wrapped_t<Range>>::value_type; + static constexpr bool is_tree = std::conjunction_v<protocol_parentRow<protocol, row>, + protocol_childRows<protocol, row>>; + static constexpr bool is_list = static_size_v<row> == 0 + && (!has_metaobject_v<row> || row_category<row>::isMultiRole); + static constexpr bool is_table = !is_list && !is_tree; static constexpr bool has_newRow = protocol_newRow<protocol>(); static constexpr bool has_deleteRow = protocol_deleteRow<protocol, row>(); @@ -1046,6 +1051,9 @@ public: typename C::MultiData >; + static Q_CORE_EXPORT QRangeModelImplBase *getImplementation(QRangeModel *model); + static Q_CORE_EXPORT const QRangeModelImplBase *getImplementation(const QRangeModel *model); + private: friend class QRangeModelPrivate; QRangeModel *m_rangeModel; @@ -1147,13 +1155,6 @@ protected: } } - static constexpr bool isMutable() - { - return range_features::is_mutable && row_features::is_mutable - && std::is_reference_v<row_reference> - && Structure::is_mutable_impl; - } - static constexpr int static_row_count = QRangeModelDetails::static_size_v<range_type>; static constexpr bool rows_are_raw_pointers = std::is_pointer_v<row_type>; static constexpr bool rows_are_owning_or_raw_pointers = @@ -1161,9 +1162,6 @@ protected: static constexpr int static_column_count = QRangeModelDetails::static_size_v<row_type>; static constexpr bool one_dimensional_range = static_column_count == 0; - static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; } - static constexpr bool dynamicColumns() { return static_column_count < 0; } - // A row might be a value (or range of values), or a pointer. // row_ptr is always a pointer, and const_row_ptr is a pointer to const. using row_ptr = wrapped_row_type *; @@ -1205,6 +1203,15 @@ protected: "The range holding a move-only row-type must support insert(pos, start, end)"); public: + static constexpr bool isMutable() + { + return range_features::is_mutable && row_features::is_mutable + && std::is_reference_v<row_reference> + && Structure::is_mutable_impl; + } + static constexpr bool dynamicRows() { return isMutable() && static_row_count < 0; } + static constexpr bool dynamicColumns() { return static_column_count < 0; } + explicit QRangeModelImpl(Range &&model, Protocol&& protocol, QRangeModel *itemModel) : Ancestor(itemModel) , ProtocolStorage{std::forward<Protocol>(protocol)} @@ -1236,7 +1243,7 @@ public: if (row == index.row() && column == index.column()) return index; - if (column < 0 || column >= this->itemModel().columnCount()) + if (column < 0 || column >= this->columnCount({})) return {}; if (row == index.row()) @@ -1974,8 +1981,8 @@ public: } if (sourceRow == destRow || sourceRow == destRow - 1 || count <= 0 - || sourceRow < 0 || sourceRow + count - 1 >= this->itemModel().rowCount(sourceParent) - || destRow < 0 || destRow > this->itemModel().rowCount(destParent)) { + || sourceRow < 0 || sourceRow + count - 1 >= this->rowCount(sourceParent) + || destRow < 0 || destRow > this->rowCount(destParent)) { return false; } @@ -1995,11 +2002,14 @@ public: } } - QModelIndex parent(const QModelIndex &child) const { return that().parent(child); } + const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); } + protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); } + + QModelIndex parent(const QModelIndex &child) const { return that().parentImpl(child); } - int rowCount(const QModelIndex &parent) const { return that().rowCount(parent); } + int rowCount(const QModelIndex &parent) const { return that().rowCountImpl(parent); } - int columnCount(const QModelIndex &parent) const { return that().columnCount(parent); } + int columnCount(const QModelIndex &parent) const { return that().columnCountImpl(parent); } void destroy() { delete std::addressof(that()); } @@ -2035,6 +2045,11 @@ public: protected: ~QRangeModelImpl() { + deleteOwnedRows(); + } + + void deleteOwnedRows() + { // We delete row objects if we are not operating on a reference or pointer // to a range, as in that case, the owner of the referenced/pointed to // range also owns the row entries. @@ -2335,9 +2350,6 @@ protected: } - const protocol_type& protocol() const { return QRangeModelDetails::refTo(ProtocolStorage::object()); } - protocol_type& protocol() { return QRangeModelDetails::refTo(ProtocolStorage::object()); } - ModelData m_data; }; @@ -2385,7 +2397,7 @@ protected: return this->createIndex(row, column, QRangeModelDetails::pointerTo(*it)); } - QModelIndex parent(const QModelIndex &child) const + QModelIndex parentImpl(const QModelIndex &child) const { if (!child.isValid()) return {}; @@ -2410,12 +2422,12 @@ protected: return {}; } - int rowCount(const QModelIndex &parent) const + int rowCountImpl(const QModelIndex &parent) const { return Base::size(this->childRange(parent)); } - int columnCount(const QModelIndex &) const + int columnCountImpl(const QModelIndex &) const { // all levels of a tree have to have the same, static, column count if constexpr (Base::one_dimensional_range) @@ -2662,6 +2674,9 @@ class QGenericTableItemModelImpl using Base = QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>; friend class QRangeModelImpl<QGenericTableItemModelImpl<Range>, Range>; + static constexpr bool is_mutable_impl = true; + +public: using range_type = typename Base::range_type; using range_features = typename Base::range_features; using row_type = typename Base::row_type; @@ -2669,9 +2684,6 @@ class QGenericTableItemModelImpl using row_traits = typename Base::row_traits; using row_features = typename Base::row_features; - static constexpr bool is_mutable_impl = true; - -public: explicit QGenericTableItemModelImpl(Range &&model, QRangeModel *itemModel) : Base(std::forward<Range>(model), {}, itemModel) {} @@ -2692,19 +2704,19 @@ protected: } } - QModelIndex parent(const QModelIndex &) const + QModelIndex parentImpl(const QModelIndex &) const { return {}; } - int rowCount(const QModelIndex &parent) const + int rowCountImpl(const QModelIndex &parent) const { if (parent.isValid()) return 0; return int(Base::size(*this->m_data.model())); } - int columnCount(const QModelIndex &parent) const + int columnCountImpl(const QModelIndex &parent) const { if (parent.isValid()) return 0; @@ -2760,7 +2772,7 @@ protected: // dynamically sized rows all have to have the same column count if constexpr (Base::dynamicColumns() && row_features::has_resize) { if (QRangeModelDetails::isValid(empty_row)) - QRangeModelDetails::refTo(empty_row).resize(this->itemModel().columnCount()); + QRangeModelDetails::refTo(empty_row).resize(this->columnCount({})); } return empty_row; diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 0c7e7db3188..de7c4df6d1d 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -2512,7 +2512,7 @@ QString QCoreApplication::applicationFilePath() Returns the current process ID for the application. */ -qint64 QCoreApplication::applicationPid() +qint64 QCoreApplication::applicationPid() noexcept { #if defined(Q_OS_WIN) return GetCurrentProcessId(); diff --git a/src/corelib/kernel/qcoreapplication.h b/src/corelib/kernel/qcoreapplication.h index 55c8097adc5..054e53a4a41 100644 --- a/src/corelib/kernel/qcoreapplication.h +++ b/src/corelib/kernel/qcoreapplication.h @@ -119,7 +119,7 @@ public: static QString applicationDirPath(); static QString applicationFilePath(); - Q_DECL_CONST_FUNCTION static qint64 applicationPid(); + Q_DECL_CONST_FUNCTION static qint64 applicationPid() noexcept; #if QT_CONFIG(permissions) || defined(Q_QDOC) Qt::PermissionStatus checkPermission(const QPermission &permission); diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index 8c95431d01f..bbcea8338ca 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -132,6 +132,10 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); \annotatedlist permissions + \note The available permission types cover core functionality of Qt modules + like Qt Multimedia and Qt Positioning, but do not encompass all platform-specific + permissions. Custom permission types are not currently supported. + \section1 Best Practices To ensure the best possible user experience for the end user we recommend diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp index 06dc614c352..93f905afc48 100644 --- a/src/corelib/mimetypes/qmimedatabase.cpp +++ b/src/corelib/mimetypes/qmimedatabase.cpp @@ -158,7 +158,7 @@ void QMimeDatabasePrivate::loadProviders() const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers() { -#ifndef Q_OS_WASM // stub implementation always returns true +#if QT_CONFIG(thread) // stub implementation always returns true Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex #endif if (m_providers.empty()) { @@ -289,7 +289,9 @@ QStringList QMimeDatabasePrivate::mimeParents(const QString &mimeName) QStringList QMimeDatabasePrivate::parents(const QString &mimeName) { +#if QT_CONFIG(thread) // stub implementation always returns true Q_ASSERT(!mutex.tryLock()); +#endif QStringList result; for (const auto &provider : providers()) provider->addParents(mimeName, result); diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index 4ccc58b3f39..de7043e8c1d 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -377,7 +377,6 @@ void QMimeBinaryProvider::findByMagic(const QByteArray &data, QMimeMagicResult & void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result) { - const QByteArray mimeStr = mime.toLatin1(); const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset); const int numEntries = m_cacheFile->getUint32(parentListOffset); @@ -388,7 +387,7 @@ void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result) const int off = parentListOffset + 4 + 8 * medium; const int mimeOffset = m_cacheFile->getUint32(off); const char *aMime = m_cacheFile->getCharStar(mimeOffset); - const int cmp = qstrcmp(aMime, mimeStr); + const int cmp = QLatin1StringView(aMime).compare(mime); if (cmp < 0) { begin = medium + 1; } else if (cmp > 0) { @@ -409,7 +408,6 @@ void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result) QString QMimeBinaryProvider::resolveAlias(const QString &name) { - const QByteArray input = name.toLatin1(); const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset); const int numEntries = m_cacheFile->getUint32(aliasListOffset); int begin = 0; @@ -419,7 +417,7 @@ QString QMimeBinaryProvider::resolveAlias(const QString &name) const int off = aliasListOffset + 4 + 8 * medium; const int aliasOffset = m_cacheFile->getUint32(off); const char *alias = m_cacheFile->getCharStar(aliasOffset); - const int cmp = qstrcmp(alias, input); + const int cmp = QLatin1StringView(alias).compare(name); if (cmp < 0) { begin = medium + 1; } else if (cmp > 0) { @@ -435,7 +433,6 @@ QString QMimeBinaryProvider::resolveAlias(const QString &name) void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result) { - const QByteArray input = name.toLatin1(); const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset); const int numEntries = m_cacheFile->getUint32(aliasListOffset); for (int pos = 0; pos < numEntries; ++pos) { @@ -443,7 +440,7 @@ void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result) const int mimeOffset = m_cacheFile->getUint32(off + 4); const char *mimeType = m_cacheFile->getCharStar(mimeOffset); - if (input == mimeType) { + if (name == QLatin1StringView(mimeType)) { const int aliasOffset = m_cacheFile->getUint32(off); const char *alias = m_cacheFile->getCharStar(aliasOffset); const QString strAlias = QString::fromLatin1(alias); @@ -586,7 +583,7 @@ QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName) // Binary search in the icons or generic-icons list QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset, - const QByteArray &inputMime) + QStringView inputMime) { const int iconsListOffset = cacheFile->getUint32(posListOffset); const int numIcons = cacheFile->getUint32(iconsListOffset); @@ -597,7 +594,7 @@ QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int pos const int off = iconsListOffset + 4 + 8 * medium; const int mimeOffset = cacheFile->getUint32(off); const char *mime = cacheFile->getCharStar(mimeOffset); - const int cmp = qstrcmp(mime, inputMime); + const int cmp = QLatin1StringView(mime).compare(inputMime); if (cmp < 0) begin = medium + 1; else if (cmp > 0) @@ -612,14 +609,12 @@ QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int pos QString QMimeBinaryProvider::icon(const QString &name) { - const QByteArray inputMime = name.toLatin1(); - return iconForMime(m_cacheFile.get(), PosIconsListOffset, inputMime); + return iconForMime(m_cacheFile.get(), PosIconsListOffset, name); } QString QMimeBinaryProvider::genericIcon(const QString &name) { - const QByteArray inputMime = name.toLatin1(); - return iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, inputMime); + return iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, name); } //// diff --git a/src/corelib/mimetypes/qmimeprovider_p.h b/src/corelib/mimetypes/qmimeprovider_p.h index aede727d710..d597651b362 100644 --- a/src/corelib/mimetypes/qmimeprovider_p.h +++ b/src/corelib/mimetypes/qmimeprovider_p.h @@ -111,7 +111,7 @@ private: bool caseSensitiveCheck); bool matchMagicRule(CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data); - QLatin1StringView iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime); + QLatin1StringView iconForMime(CacheFile *cacheFile, int posListOffset, QStringView inputMime); void loadMimeTypeList(); bool checkCacheChanged(); diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index d1fff5388c6..287138bb915 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -177,15 +177,13 @@ Blob Blob::slice(uint32_t begin, uint32_t end) const ArrayBuffer Blob::arrayBuffer_sync() const { - QEventLoop loop; emscripten::val buffer; - qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), { - .thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) { + uint32_t handlerIndex = qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), { + .thenFunc = [&buffer](emscripten::val arrayBuffer) { buffer = arrayBuffer; - loop.quit(); } }); - loop.exec(); + Promise::suspendExclusive(handlerIndex); return ArrayBuffer(buffer); } @@ -443,7 +441,7 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name, } -void Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks) +uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks) { Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc, "Promise::adoptPromise", "must provide at least one callback function"); @@ -499,13 +497,23 @@ void Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks) promise = promise.call<emscripten::val>("finally", suspendResume->jsEventHandlerAt(*finallyIndex)); + + return *finallyIndex; +} + +void Promise::suspendExclusive(uint32_t handlerIndex) +{ + QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get(); + Q_ASSERT(suspendResume); + suspendResume->suspendExclusive(handlerIndex); + suspendResume->sendPendingEvents(); } void Promise::all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks) { auto arr = emscripten::val::array(promises); auto all = val::global("Promise").call<emscripten::val>("all", arr); - return adoptPromise(all, callbacks); + adoptPromise(all, callbacks); } // Asyncify and thread blocking: Normally, it's not possible to block the main @@ -630,6 +638,249 @@ qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize) return size; } +FileSystemWritableFileStreamIODevice::FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream) + : m_stream(std::move(stream)) +{ +} + +bool FileSystemWritableFileStreamIODevice::open(QIODevice::OpenMode mode) +{ + if (mode.testFlag(QIODevice::ReadOnly)) + return false; + return QIODevice::open(mode); +} + +void FileSystemWritableFileStreamIODevice::close() +{ + if (!isOpen()) { + QIODevice::close(); + return; + } + + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("close"), { + .thenFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + QIODevice::close(); +} + +bool FileSystemWritableFileStreamIODevice::isSequential() const +{ + return false; +} + +qint64 FileSystemWritableFileStreamIODevice::size() const +{ + return m_size; +} + +bool FileSystemWritableFileStreamIODevice::seek(qint64 pos) +{ + bool success = false; + + emscripten::val seekParams = emscripten::val::object(); + seekParams.set("type", std::string("seek")); + seekParams.set("position", static_cast<double>(pos)); + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), { + .thenFunc = [&success](emscripten::val) { + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }, seekParams); + Promise::suspendExclusive(handlerIndex); + + if (!success) + return false; + + return QIODevice::seek(pos); +} + +qint64 FileSystemWritableFileStreamIODevice::readData(char *, qint64) +{ + Q_UNREACHABLE(); +} + +qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64 size) +{ + bool success = false; + + Uint8Array array = Uint8Array::copyFrom(data, size); + uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), { + .thenFunc = [&success](emscripten::val) { + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }, array.val()); + Promise::suspendExclusive(handlerIndex); + + if (success) { + qint64 newPos = pos() + size; + m_size = std::max(m_size, newPos); + return size; + } + return -1; +} + +FileSystemWritableFileStream::FileSystemWritableFileStream(const emscripten::val &writableStream) + : m_writableStream(writableStream) +{ +} + +emscripten::val FileSystemWritableFileStream::val() const +{ + return m_writableStream; +} + +FileSystemFileHandle::FileSystemFileHandle(const emscripten::val &fileHandle) + : m_fileHandle(fileHandle) +{ +} + +std::string FileSystemFileHandle::name() const +{ + return m_fileHandle["name"].as<std::string>(); +} + +std::string FileSystemFileHandle::kind() const +{ + return m_fileHandle["kind"].as<std::string>(); +} + +emscripten::val FileSystemFileHandle::val() const +{ + return m_fileHandle; +} + +FileSystemFileIODevice::FileSystemFileIODevice(FileSystemFileHandle fileHandle) + : m_fileHandle(fileHandle) +{ +} + +bool FileSystemFileIODevice::open(QIODevice::OpenMode mode) +{ + if (isOpen()) + return false; + + // Read mode: get the File and create a BlobIODevice + if (mode & QIODevice::ReadOnly) { + File file; + bool success = false; + + uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("getFile"), { + .thenFunc = [&file, &success](emscripten::val fileVal) { + file = File(fileVal); + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + if (success) { + m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size())); + m_size = file.size(); + + if (!m_blobDevice->open(mode)) + return false; + } else { + return false; + } + } + + // Write mode: create a writable stream + if (mode & QIODevice::WriteOnly) { + FileSystemWritableFileStream writableStream; + bool success = false; + + uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("createWritable"), { + .thenFunc = [&writableStream, &success](emscripten::val writable) { + writableStream = FileSystemWritableFileStream(writable); + success = true; + }, + .catchFunc = [](emscripten::val) { + } + }); + Promise::suspendExclusive(handlerIndex); + + if (success) { + m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream); + if (!m_writableDevice->open(mode)) + return false; + } else { + return false; + } + } + + return QIODevice::open(mode); +} + +void FileSystemFileIODevice::close() +{ + if (!isOpen()) { + QIODevice::close(); + return; + } + + if (m_writableDevice) { + m_writableDevice->close(); + m_writableDevice.reset(); + } + if (m_blobDevice) { + m_blobDevice->close(); + m_blobDevice.reset(); + } + + QIODevice::close(); +} + +bool FileSystemFileIODevice::isSequential() const +{ + return false; +} + +qint64 FileSystemFileIODevice::size() const +{ + return m_size; +} + +bool FileSystemFileIODevice::seek(qint64 pos) +{ + if (m_blobDevice) { + if (!m_blobDevice->seek(pos)) + return false; + } + if (m_writableDevice) { + if (!m_writableDevice->seek(pos)) + return false; + } + return QIODevice::seek(pos); +} + +qint64 FileSystemFileIODevice::readData(char *data, qint64 maxSize) +{ + if (!m_blobDevice) + return -1; + + return m_blobDevice->read(data, maxSize); +} + +qint64 FileSystemFileIODevice::writeData(const char *data, qint64 size) +{ + if (!m_writableDevice) + return -1; + + qint64 written = m_writableDevice->write(data, size); + if (written > 0) { + qint64 newPos = pos() + written; + m_size = std::max(m_size, newPos); + } + return written; +} + } // namespace qstdweb QT_END_NAMESPACE diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index 711751f65ab..9a97370448e 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -195,6 +195,30 @@ namespace qstdweb { emscripten::val m_uint8Array = emscripten::val::undefined(); }; + class Q_CORE_EXPORT FileSystemWritableFileStream { + public: + FileSystemWritableFileStream() = default; + explicit FileSystemWritableFileStream(const emscripten::val &writableStream); + emscripten::val val() const; + + private: + emscripten::val m_writableStream = emscripten::val::undefined(); + }; + + class Q_CORE_EXPORT FileSystemFileHandle { + public: + FileSystemFileHandle() = default; + explicit FileSystemFileHandle(const emscripten::val &fileHandle); + + std::string name() const; + std::string kind() const; + + emscripten::val val() const; + + private: + emscripten::val m_fileHandle = emscripten::val::undefined(); + }; + // EventCallback here for source compatibility; prefer using QWasmEventHandler directly class Q_CORE_EXPORT EventCallback : public QWasmEventHandler { @@ -214,13 +238,13 @@ namespace qstdweb { }; namespace Promise { - void Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks); + uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks); template<typename... Args> - void make(emscripten::val target, - QString methodName, - PromiseCallbacks callbacks, - Args... args) + uint32_t make(emscripten::val target, + QString methodName, + PromiseCallbacks callbacks, + Args... args) { emscripten::val promiseObject = target.call<emscripten::val>( methodName.toStdString().c_str(), std::forward<Args>(args)...); @@ -228,9 +252,10 @@ namespace qstdweb { qFatal("This function did not return a promise"); } - adoptPromise(std::move(promiseObject), std::move(callbacks)); + return adoptPromise(std::move(promiseObject), std::move(callbacks)); } + void Q_CORE_EXPORT suspendExclusive(uint32_t handlerIndex); void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; @@ -274,6 +299,46 @@ namespace qstdweb { Uint8Array m_array; }; + class Q_CORE_EXPORT FileSystemWritableFileStreamIODevice: public QIODevice + { + public: + FileSystemWritableFileStreamIODevice(FileSystemWritableFileStream stream); + bool open(QIODevice::OpenMode mode) override; + void close() override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 size) override; + + private: + FileSystemWritableFileStream m_stream; + qint64 m_size = 0; + }; + + class Q_CORE_EXPORT FileSystemFileIODevice: public QIODevice + { + public: + FileSystemFileIODevice(FileSystemFileHandle fileHandle); + bool open(QIODevice::OpenMode mode) override; + void close() override; + bool isSequential() const override; + qint64 size() const override; + bool seek(qint64 pos) override; + + protected: + qint64 readData(char *data, qint64 maxSize) override; + qint64 writeData(const char *data, qint64 size) override; + + private: + FileSystemFileHandle m_fileHandle; + std::unique_ptr<BlobIODevice> m_blobDevice; + std::unique_ptr<FileSystemWritableFileStreamIODevice> m_writableDevice; + qint64 m_size = 0; + }; + inline emscripten::val window() { static emscripten::val savedWindow = emscripten::val::global("window"); diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp index 961bbb53e54..093898c520a 100644 --- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp +++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp @@ -49,6 +49,7 @@ void qtSuspendResumeControlClearJs() { asyncifyEnabled: false, // asyncify 1 or JSPI enabled eventHandlers: {}, pendingEvents: [], + exclusiveEventHandler: 0, }); }); } @@ -118,7 +119,15 @@ void qtRegisterEventHandlerJs(int index) { }); // Handle the event based on instance state and asyncify flag - if (control.resume) { + if (control.exclusiveEventHandler > 0) { + // In exclusive mode, resume on exclusive event handler match only + if (index != control.exclusiveEventHandler) + return; + + const resume = control.resume; + control.resume = null; + resume(); + } else if (control.resume) { // The instance is suspended in processEvents(), resume and process the event const resume = control.resume; control.resume = null; @@ -148,14 +157,12 @@ QWasmSuspendResumeControl::QWasmSuspendResumeControl() #endif qtSuspendResumeControlClearJs(); suspendResumeControlJs().set("asyncifyEnabled", qstdweb::haveAsyncify()); - Q_ASSERT(!QWasmSuspendResumeControl::s_suspendResumeControl); QWasmSuspendResumeControl::s_suspendResumeControl = this; } QWasmSuspendResumeControl::~QWasmSuspendResumeControl() { qtSuspendResumeControlClearJs(); - Q_ASSERT(QWasmSuspendResumeControl::s_suspendResumeControl); QWasmSuspendResumeControl::s_suspendResumeControl = nullptr; } @@ -199,13 +206,24 @@ void QWasmSuspendResumeControl::suspend() qtSuspendJs(); } -// Sends any pending events. Returns true if an event was sent, false otherwise. +void QWasmSuspendResumeControl::suspendExclusive(uint32_t eventHandlerIndex) +{ + suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndex); + qtSuspendJs(); +} + +// Sends any pending events. Returns the number of sent events. int QWasmSuspendResumeControl::sendPendingEvents() { #if QT_CONFIG(thread) Q_ASSERT(emscripten_is_main_runtime_thread()); #endif - emscripten::val pendingEvents = suspendResumeControlJs()["pendingEvents"]; + emscripten::val control = suspendResumeControlJs(); + emscripten::val pendingEvents = control["pendingEvents"]; + + if (control["exclusiveEventHandler"].as<int>() > 0) + return sendPendingExclusiveEvent(); + if (pendingEvents["length"].as<int>() == 0) return 0; @@ -214,13 +232,28 @@ int QWasmSuspendResumeControl::sendPendingEvents() // Grab one event (handler and arg), and call it emscripten::val event = pendingEvents.call<val>("shift"); auto it = m_eventHandlers.find(event["index"].as<int>()); - Q_ASSERT(it != m_eventHandlers.end()); - it->second(event["arg"]); + if (it != m_eventHandlers.end()) + it->second(event["arg"]); ++count; } return count; } +// Sends the pending exclusive event, and resets the "exclusive" state +int QWasmSuspendResumeControl::sendPendingExclusiveEvent() +{ + emscripten::val control = suspendResumeControlJs(); + int exclusiveHandlerIndex = control["exclusiveEventHandler"].as<int>(); + control.set("exclusiveEventHandler", 0); + emscripten::val event = control["pendingEvents"].call<val>("pop"); + int eventHandlerIndex = event["index"].as<int>(); + Q_ASSERT(exclusiveHandlerIndex == eventHandlerIndex); + auto it = m_eventHandlers.find(eventHandlerIndex); + Q_ASSERT(it != m_eventHandlers.end()); + it->second(event["arg"]); + return 1; +} + void qtSendPendingEvents() { if (QWasmSuspendResumeControl::s_suspendResumeControl) diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h index 2b9962e4be1..37c71ed8123 100644 --- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h +++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h @@ -38,7 +38,9 @@ public: static emscripten::val suspendResumeControlJs(); void suspend(); + void suspendExclusive(uint32_t eventHandlerIndex); int sendPendingEvents(); + int sendPendingExclusiveEvent(); private: friend void qtSendPendingEvents(); diff --git a/src/corelib/plugin/qlibrary.h b/src/corelib/plugin/qlibrary.h index f31047b214a..95c14178b22 100644 --- a/src/corelib/plugin/qlibrary.h +++ b/src/corelib/plugin/qlibrary.h @@ -63,6 +63,7 @@ private: Loaded }; + friend class QLibraryPrivate; QTaggedPointer<QLibraryPrivate, LoadStatusTag> d = nullptr; Q_DISABLE_COPY(QLibrary) }; diff --git a/src/corelib/plugin/qlibrary_p.h b/src/corelib/plugin/qlibrary_p.h index b4e29a79e28..2331c86d844 100644 --- a/src/corelib/plugin/qlibrary_p.h +++ b/src/corelib/plugin/qlibrary_p.h @@ -67,7 +67,7 @@ public: bool load(); QtPluginInstanceFunction loadPlugin(); // loads and resolves instance - bool unload(UnloadFlag flag = UnloadSys); + Q_AUTOTEST_EXPORT bool unload(UnloadFlag flag = UnloadSys); void release(); QFunctionPointer resolve(const char *); @@ -103,6 +103,11 @@ public: void updatePluginState(); bool isPlugin(); + static QLibraryPrivate* get(QLibrary* lib) + { + return lib->d.data(); + } + private: explicit QLibraryPrivate(const QString &canonicalFileName, const QString &version, QLibrary::LoadHints loadHints); ~QLibraryPrivate(); diff --git a/src/corelib/text/qstring.cpp b/src/corelib/text/qstring.cpp index 711b70ebf8f..c01c1a9999d 100644 --- a/src/corelib/text/qstring.cpp +++ b/src/corelib/text/qstring.cpp @@ -3681,95 +3681,24 @@ QString &QString::remove(QChar ch, Qt::CaseSensitivity cs) \sa remove() */ - -/*! \internal - Instead of detaching, or reallocating if "before" is shorter than "after" - and there isn't enough capacity, create a new string, copy characters to it - as needed, then swap it with "str". -*/ -static void replace_with_copy(QString &str, QSpan<size_t> indices, qsizetype blen, - QStringView after) -{ - const qsizetype alen = after.size(); - const char16_t *after_b = after.utf16(); - - const QString::DataPointer &str_d = str.data_ptr(); - auto src_start = str_d.begin(); - const qsizetype newSize = str_d.size + indices.size() * (alen - blen); - QString copy{ newSize, Qt::Uninitialized }; - QString::DataPointer ©_d = copy.data_ptr(); - auto dst = copy_d.begin(); - for (size_t index : indices) { - auto hit = str_d.begin() + index; - dst = std::copy(src_start, hit, dst); - dst = std::copy_n(after_b, alen, dst); - src_start = hit + blen; - } - dst = std::copy(src_start, str_d.end(), dst); - str.swap(copy); -} - -// No detaching or reallocation is needed -static void replace_in_place(QString &str, QSpan<size_t> indices, - qsizetype blen, QStringView after) -{ - const qsizetype alen = after.size(); - const char16_t *after_b = after.utf16(); - const char16_t *after_e = after.utf16() + after.size(); - - if (blen == alen) { // Replace in place - for (size_t index : indices) - std::copy_n(after_b, alen, str.data_ptr().begin() + index); - } else if (blen > alen) { // Replace from front - char16_t *begin = str.data_ptr().begin(); - char16_t *hit = begin + indices.front(); - char16_t *to = hit; - to = std::copy_n(after_b, alen, to); - char16_t *movestart = hit + blen; - for (size_t index : indices.sliced(1)) { - hit = begin + index; - to = std::move(movestart, hit, to); - to = std::copy_n(after_b, alen, to); - movestart = hit + blen; - } - to = std::move(movestart, str.data_ptr().end(), to); - str.resize(std::distance(begin, to)); - } else { // blen < alen, Replace from back - const qsizetype oldSize = str.data_ptr().size; - const qsizetype adjust = indices.size() * (alen - blen); - const qsizetype newSize = oldSize + adjust; - - str.resize(newSize); - char16_t *begin = str.data_ptr().begin(); - char16_t *moveend = begin + oldSize; - char16_t *to = str.data_ptr().end(); - - for (auto it = indices.rbegin(), end = indices.rend(); it != end; ++it) { - char16_t *hit = begin + *it; - char16_t *movestart = hit + blen; - to = std::move_backward(movestart, moveend, to); - to = std::copy_backward(after_b, after_e, to); - moveend = hit; - } - } -} - -static void replace_helper(QString &str, QSpan<size_t> indices, qsizetype blen, QStringView after) +static void replace_helper(QString &str, QSpan<qsizetype> indices, qsizetype blen, QStringView after) { const qsizetype oldSize = str.data_ptr().size; const qsizetype adjust = indices.size() * (after.size() - blen); const qsizetype newSize = oldSize + adjust; + using A = QStringAlgorithms<QString>; if (str.data_ptr().needsDetach() || needsReallocate(str, newSize)) { - replace_with_copy(str, indices, blen, after); + A::replace_helper(str, blen, after, indices); return; } - if (QtPrivate::q_points_into_range(after.begin(), str)) + if (QtPrivate::q_points_into_range(after.begin(), str)) { // Copy after if it lies inside our own d.b area (which we could // possibly invalidate via a realloc or modify by replacement) - replace_in_place(str, indices, blen, QVarLengthArray(after.begin(), after.end())); - else - replace_in_place(str, indices, blen, after); + A::replace_helper(str, blen, QVarLengthArray(after.begin(), after.end()), indices); + } else { + A::replace_helper(str, blen, after, indices); + } } /*! @@ -3811,8 +3740,8 @@ QString &QString::replace(qsizetype pos, qsizetype len, const QChar *after, qsiz if (len > this->size() - pos) len = this->size() - pos; - size_t index = pos; - replace_helper(*this, QSpan(&index, 1), len, QStringView{after, alen}); + qsizetype indices[] = {pos}; + replace_helper(*this, indices, len, QStringView{after, alen}); return *this; } @@ -3890,7 +3819,7 @@ QString &QString::replace(const QChar *before, qsizetype blen, qsizetype index = 0; - QVarLengthArray<size_t> indices; + QVarLengthArray<qsizetype> indices; while ((index = matcher.indexIn(*this, index)) != -1) { indices.push_back(index); if (blen) // Step over before: @@ -3925,7 +3854,7 @@ QString& QString::replace(QChar ch, const QString &after, Qt::CaseSensitivity cs const char16_t cc = (cs == Qt::CaseSensitive ? ch.unicode() : ch.toCaseFolded().unicode()); - QVarLengthArray<size_t> indices; + QVarLengthArray<qsizetype> indices; if (cs == Qt::CaseSensitive) { const char16_t *begin = d.begin(); const char16_t *end = d.end(); diff --git a/src/corelib/thread/qthread.h b/src/corelib/thread/qthread.h index bb70678a958..6e4e532fd7d 100644 --- a/src/corelib/thread/qthread.h +++ b/src/corelib/thread/qthread.h @@ -172,27 +172,15 @@ inline Qt::HANDLE QThread::currentThreadId() noexcept #elif defined(Q_PROCESSOR_X86_64) && ((defined(Q_OS_LINUX) && defined(__GLIBC__)) || defined(Q_OS_FREEBSD)) // x86_64 Linux, BSD uses FS __asm__("mov %%fs:%c1, %0" : "=r" (tid) : "i" (2 * sizeof(void*)) : ); -#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) +#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) && defined(Q_CC_MSVC) // See https://en.wikipedia.org/wiki/Win32_Thread_Information_Block - // First get the pointer to the TIB - quint8 *tib; -# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics - __asm__("movq %%gs:0x30, %0" : "=r" (tib) : :); -# else - tib = reinterpret_cast<quint8 *>(__readgsqword(0x30)); -# endif - // Then read the thread ID - tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x48); -#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) - // First get the pointer to the TIB - quint8 *tib; -# if defined(Q_CC_MINGW) // internal compiler error when using the intrinsics - __asm__("movl %%fs:0x18, %0" : "=r" (tib) : :); -# else - tib = reinterpret_cast<quint8 *>(__readfsdword(0x18)); -# endif - // Then read the thread ID - tid = *reinterpret_cast<Qt::HANDLE *>(tib + 0x24); + tid = reinterpret_cast<Qt::HANDLE>(__readgsqword(0x48)); +#elif defined(Q_PROCESSOR_X86_64) && defined(Q_OS_WIN) // !Q_CC_MSVC + __asm__("mov %%gs:0x48, %0" : "=r" (tid)); +#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) && defined(Q_CC_MSVC) + tid = reinterpret_cast<Qt::HANDLE>(__readfsdword(0x24)); +#elif defined(Q_PROCESSOR_X86_32) && defined(Q_OS_WIN) // !Q_CC_MSVC + __asm__("mov %%fs:0x24, %0" : "=r" (tid)); #else #undef QT_HAS_FAST_CURRENT_THREAD_ID tid = currentThreadIdImpl(); diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp index 03eeed84465..974c486b915 100644 --- a/src/corelib/time/qdatetime.cpp +++ b/src/corelib/time/qdatetime.cpp @@ -517,6 +517,7 @@ QDate::QDate(int y, int m, int d, QCalendar cal) */ /*! + \overload primary \fn bool QDate::isValid() const Returns \c true if this date is valid; otherwise returns \c false. @@ -525,6 +526,8 @@ QDate::QDate(int y, int m, int d, QCalendar cal) */ /*! + \overload primary + Returns the year of this date. Uses \a cal as calendar, if supplied, else the Gregorian calendar. @@ -557,8 +560,8 @@ int QDate::year(QCalendar cal) const } /*! - \overload - */ + \overload year() +*/ int QDate::year() const { @@ -571,6 +574,8 @@ int QDate::year() const } /*! + \overload primary + Returns the month-number for the date. Numbers the months of the year starting with 1 for the first. Uses \a cal @@ -609,8 +614,8 @@ int QDate::month(QCalendar cal) const } /*! - \overload - */ + \overload month() +*/ int QDate::month() const { @@ -623,6 +628,8 @@ int QDate::month() const } /*! + \overload primary + Returns the day of the month for this date. Uses \a cal as calendar if supplied, else the Gregorian calendar (for which @@ -642,8 +649,8 @@ int QDate::day(QCalendar cal) const } /*! - \overload - */ + \overload day() +*/ int QDate::day() const { @@ -656,6 +663,8 @@ int QDate::day() const } /*! + \overload primary + Returns the weekday (1 = Monday to 7 = Sunday) for this date. Uses \a cal as calendar if supplied, else the Gregorian calendar. Returns 0 @@ -674,8 +683,8 @@ int QDate::dayOfWeek(QCalendar cal) const } /*! - \overload - */ + \overload dayOfWeek() +*/ int QDate::dayOfWeek() const { @@ -683,6 +692,8 @@ int QDate::dayOfWeek() const } /*! + \overload primary + Returns the day of the year (1 for the first day) for this date. Uses \a cal as calendar if supplied, else the Gregorian calendar. @@ -702,8 +713,8 @@ int QDate::dayOfYear(QCalendar cal) const } /*! - \overload - */ + \overload dayOfYear() +*/ int QDate::dayOfYear() const { @@ -715,6 +726,8 @@ int QDate::dayOfYear() const } /*! + \overload primary + Returns the number of days in the month for this date. Uses \a cal as calendar if supplied, else the Gregorian calendar (for which @@ -735,8 +748,8 @@ int QDate::daysInMonth(QCalendar cal) const } /*! - \overload - */ + \overload daysInMonth() +*/ int QDate::daysInMonth() const { @@ -749,6 +762,8 @@ int QDate::daysInMonth() const } /*! + \overload primary + Returns the number of days in the year for this date. Uses \a cal as calendar if supplied, else the Gregorian calendar (for which @@ -766,8 +781,8 @@ int QDate::daysInYear(QCalendar cal) const } /*! - \overload - */ + \overload daysInYear() +*/ int QDate::daysInYear() const { @@ -918,6 +933,7 @@ static QDateTime toEarliest(QDate day, const QTimeZone &zone) /*! \since 5.14 + \overload primary Returns the start-moment of the day. @@ -972,8 +988,8 @@ QDateTime QDate::startOfDay(const QTimeZone &zone) const } /*! - \overload \since 6.5 + \overload startOfDay() */ QDateTime QDate::startOfDay() const { @@ -982,8 +998,8 @@ QDateTime QDate::startOfDay() const #if QT_DEPRECATED_SINCE(6, 9) /*! - \overload \since 5.14 + \overload startOfDay() \deprecated [6.9] Use \c{startOfDay(const QTimeZone &)} instead. Returns the start-moment of the day. @@ -1073,6 +1089,7 @@ static QDateTime toLatest(QDate day, const QTimeZone &zone) /*! \since 5.14 + \overload primary Returns the end-moment of the day. @@ -1127,8 +1144,8 @@ QDateTime QDate::endOfDay(const QTimeZone &zone) const } /*! - \overload \since 6.5 + \overload endOfDay() */ QDateTime QDate::endOfDay() const { @@ -1137,8 +1154,8 @@ QDateTime QDate::endOfDay() const #if QT_DEPRECATED_SINCE(6, 9) /*! - \overload \since 5.14 + \overload endOfDay() \deprecated [6.9] Use \c{endOfDay(const QTimeZone &) instead. Returns the end-moment of the day. @@ -1199,7 +1216,7 @@ static QString toStringIsoDate(QDate date) } /*! - \overload + \overload toString() Returns the date as a string. The \a format parameter determines the format of the string. @@ -1245,9 +1262,10 @@ QString QDate::toString(Qt::DateFormat format) const } /*! + \since 5.14 + \overload primary \fn QString QDate::toString(const QString &format, QCalendar cal) const \fn QString QDate::toString(QStringView format, QCalendar cal) const - \since 5.14 Returns the date as a string. The \a format parameter determines the format of the result string. If \a cal is supplied, it determines the calendar used @@ -1313,8 +1331,8 @@ QString QDate::toString(QStringView format, QCalendar cal) const // Out-of-line no-calendar overloads, since QCalendar is a non-trivial type /*! - \overload \since 5.10 + \overload toString() */ QString QDate::toString(QStringView format) const { @@ -1322,8 +1340,8 @@ QString QDate::toString(QStringView format) const } /*! - \overload \since 4.6 + \overload toString() */ QString QDate::toString(const QString &format) const { @@ -1414,9 +1432,8 @@ QDate QDate::addDays(qint64 ndays) const } /*! - \fn QDate QDate::addDuration(std::chrono::days ndays) const - \since 6.4 + \fn QDate QDate::addDuration(std::chrono::days ndays) const Returns a QDate object containing a date \a ndays later than the date of this object (or earlier if \a ndays is negative). @@ -1436,6 +1453,8 @@ QDate QDate::addDays(qint64 ndays) const */ /*! + \overload primary + Returns a QDate object containing a date \a nmonths later than the date of this object (or earlier if \a nmonths is negative). @@ -1477,7 +1496,7 @@ QDate QDate::addMonths(int nmonths, QCalendar cal) const } /*! - \overload + \overload addMonths() */ QDate QDate::addMonths(int nmonths) const @@ -1509,6 +1528,8 @@ QDate QDate::addMonths(int nmonths) const } /*! + \overload primary + Returns a QDate object containing a date \a nyears later than the date of this object (or earlier if \a nyears is negative). @@ -1542,7 +1563,7 @@ QDate QDate::addYears(int nyears, QCalendar cal) const } /*! - \overload + \overload addYears() */ QDate QDate::addYears(int nyears) const @@ -1638,6 +1659,7 @@ qint64 QDate::daysTo(QDate d) const #if QT_CONFIG(datestring) // depends on, so implies, textdate /*! + \overload \fn QDate QDate::fromString(const QString &string, Qt::DateFormat format) Returns the QDate represented by the \a string, using the @@ -1651,8 +1673,8 @@ qint64 QDate::daysTo(QDate d) const */ /*! - \overload \since 6.0 + \overload fromString() */ QDate QDate::fromString(QStringView string, Qt::DateFormat format) { @@ -1702,6 +1724,7 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format) } /*! + \overload primary \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal) Returns the QDate represented by the \a string, using the \a @@ -1833,14 +1856,14 @@ QDate QDate::fromString(QStringView string, Qt::DateFormat format) */ /*! - \fn QDate QDate::fromString(QStringView string, QStringView format, QCalendar cal) - \overload \since 6.0 + \overload fromString() + \fn QDate QDate::fromString(QStringView string, QStringView format, QCalendar cal) */ /*! - \overload \since 6.0 + \overload fromString() */ QDate QDate::fromString(const QString &string, QStringView format, int baseYear, QCalendar cal) { @@ -1860,34 +1883,34 @@ QDate QDate::fromString(const QString &string, QStringView format, int baseYear, } /*! - \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal) - \overload \since 5.14 + \overload fromString() + \fn QDate QDate::fromString(const QString &string, const QString &format, QCalendar cal) */ /*! - \fn QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal) - \overload \since 6.0 + \overload fromString() + \fn QDate QDate::fromString(const QString &string, QStringView format, QCalendar cal) */ /*! - \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal) - \overload \since 6.7 + \overload fromString() + \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal) */ /*! - \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear) - \overload \since 6.7 + \overload fromString() + \fn QDate QDate::fromString(QStringView string, QStringView format, int baseYear) Uses a default-constructed QCalendar. */ /*! - \overload \since 6.7 + \overload fromString() Uses a default-constructed QCalendar. */ @@ -1897,16 +1920,16 @@ QDate QDate::fromString(const QString &string, QStringView format, int baseYear) } /*! - \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear) - \overload \since 6.7 + \overload fromString() + \fn QDate QDate::fromString(const QString &string, const QString &format, int baseYear) Uses a default-constructed QCalendar. */ #endif // datestring /*! - \overload + \overload isValid() Returns \c true if the specified date (\a year, \a month, and \a day) is valid in the Gregorian calendar; otherwise returns \c false. @@ -2037,6 +2060,8 @@ QTime::QTime(int h, int m, int s, int ms) */ /*! + \overload primary + Returns \c true if the time is valid; otherwise returns \c false. For example, the time 23:30:55.746 is valid, but 24:12:30 is invalid. @@ -2115,7 +2140,7 @@ int QTime::msec() const #if QT_CONFIG(datestring) // depends on, so implies, textdate /*! - \overload + \overload toString() Returns the time as a string. The \a format parameter determines the format of the string. @@ -2155,6 +2180,7 @@ QString QTime::toString(Qt::DateFormat format) const } /*! + \overload primary \fn QString QTime::toString(const QString &format) const \fn QString QTime::toString(QStringView format) const @@ -2261,11 +2287,11 @@ QString QTime::toString(Qt::DateFormat format) const \sa fromString(), QDate::toString(), QDateTime::toString(), QLocale::toString() */ -// ### Qt 7 The 't' format specifiers should be specific to QDateTime (compare fromString). QString QTime::toString(QStringView format) const { return QLocale::c().toString(*this, format); } +// ### Qt 7 The 't' format specifiers should be specific to QDateTime (compare fromString). #endif // datestring /*! @@ -2557,6 +2583,7 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * } /*! + \overload \fn QTime QTime::fromString(const QString &string, Qt::DateFormat format) Returns the time represented in the \a string as a QTime using the @@ -2566,8 +2593,8 @@ static QTime fromIsoTimeString(QStringView string, Qt::DateFormat format, bool * */ /*! - \overload \since 6.0 + \overload fromString() */ QTime QTime::fromString(QStringView string, Qt::DateFormat format) { @@ -2586,6 +2613,7 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format) } /*! + \overload primary \fn QTime QTime::fromString(const QString &string, const QString &format) Returns the QTime represented by the \a string, using the \a @@ -2664,14 +2692,14 @@ QTime QTime::fromString(QStringView string, Qt::DateFormat format) */ /*! - \fn QTime QTime::fromString(QStringView string, QStringView format) - \overload \since 6.0 + \overload fromString() + \fn QTime QTime::fromString(QStringView string, QStringView format) */ /*! - \overload \since 6.0 + \overload fromString() */ QTime QTime::fromString(const QString &string, QStringView format) { @@ -2691,7 +2719,7 @@ QTime QTime::fromString(const QString &string, QStringView format) /*! - \overload + \overload isValid() Returns \c true if the specified time is valid; otherwise returns false. @@ -4012,6 +4040,7 @@ QDateTime::QDateTime(QDate date, QTime time, Qt::TimeSpec spec, int offsetSecond /*! \since 5.2 + \overload primary Constructs a datetime with the given \a date and \a time, using the time representation described by \a timeZone. @@ -4620,7 +4649,7 @@ void QDateTime::setSecsSinceEpoch(qint64 secs) #if QT_CONFIG(datestring) // depends on, so implies, textdate /*! - \overload + \overload toString() Returns the datetime as a string in the \a format given. @@ -4712,9 +4741,10 @@ QString QDateTime::toString(Qt::DateFormat format) const } /*! + \since 5.14 + \overload primary \fn QString QDateTime::toString(const QString &format, QCalendar cal) const \fn QString QDateTime::toString(QStringView format, QCalendar cal) const - \since 5.14 Returns the datetime as a string. The \a format parameter determines the format of the result string. If \a cal is supplied, it determines the @@ -4761,8 +4791,8 @@ QString QDateTime::toString(QStringView format, QCalendar cal) const // Out-of-line no-calendar overloads, since QCalendar is a non-trivial type /*! - \overload \since 5.10 + \overload toString() */ QString QDateTime::toString(QStringView format) const { @@ -4770,8 +4800,8 @@ QString QDateTime::toString(QStringView format) const } /*! - \overload \since 4.6 + \overload toString() */ QString QDateTime::toString(const QString &format) const { @@ -5370,6 +5400,7 @@ Qt::weak_ordering compareThreeWay(const QDateTime &lhs, const QDateTime &rhs) /*! \since 6.5 + \overload primary \fn QDateTime QDateTime::currentDateTime(const QTimeZone &zone) Returns the system clock's current datetime, using the time representation @@ -5379,8 +5410,8 @@ Qt::weak_ordering compareThreeWay(const QDateTime &lhs, const QDateTime &rhs) */ /*! - \overload \since 0.90 + \overload currentDateTime() */ QDateTime QDateTime::currentDateTime() { @@ -5426,8 +5457,9 @@ QDateTime QDateTime::currentDateTimeUtc() */ /*! - \fn template <typename Clock, typename Duration> QDateTime QDateTime::fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time) \since 6.4 + \overload primary + \fn template <typename Clock, typename Duration> QDateTime QDateTime::fromStdTimePoint(const std::chrono::time_point<Clock, Duration> &time) Constructs a datetime representing the same point in time as \a time, using Qt::UTC as its time representation. @@ -5451,7 +5483,7 @@ QDateTime QDateTime::currentDateTimeUtc() /*! \since 6.4 - \overload + \overload fromStdTimePoint() Constructs a datetime representing the same point in time as \a time, using Qt::UTC as its time representation. @@ -5627,7 +5659,7 @@ qint64 QDateTime::currentSecsSinceEpoch() noexcept #if QT_DEPRECATED_SINCE(6, 9) /*! \since 5.2 - \overload + \overload fromMSecsSinceEpoch() \deprecated [6.9] Pass a \l QTimeZone instead, or omit \a spec and \a offsetSeconds. Returns a datetime representing a moment the given number \a msecs of @@ -5656,7 +5688,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, Qt::TimeSpec spec, int of /*! \since 5.8 - \overload + \overload fromSecsSinceEpoch \deprecated [6.9] Pass a \l QTimeZone instead, or omit \a spec and \a offsetSeconds. Returns a datetime representing a moment the given number \a secs of seconds @@ -5686,6 +5718,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, Qt::TimeSpec spec, int offs /*! \since 5.2 + \overload primary Returns a datetime representing a moment the given number \a msecs of milliseconds after the start, in UTC, of the year 1970, described as @@ -5707,7 +5740,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs, const QTimeZone &timeZone } /*! - \overload + \overload fromMSecsSinceEpoch() */ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs) { @@ -5716,6 +5749,7 @@ QDateTime QDateTime::fromMSecsSinceEpoch(qint64 msecs) /*! \since 5.8 + \overload primary Returns a datetime representing a moment the given number \a secs of seconds after the start, in UTC, of the year 1970, described as specified by \a @@ -5737,7 +5771,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs, const QTimeZone &timeZone) } /*! - \overload + \overload fromSecsSinceEpoch() */ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs) { @@ -5747,6 +5781,7 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs) #if QT_CONFIG(datestring) // depends on, so implies, textdate /*! + \overload \fn QDateTime QDateTime::fromString(const QString &string, Qt::DateFormat format) Returns the QDateTime represented by the \a string, using the @@ -5759,8 +5794,8 @@ QDateTime QDateTime::fromSecsSinceEpoch(qint64 secs) */ /*! - \overload \since 6.0 + \overload fromString() */ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format) { @@ -5901,6 +5936,7 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format) } /*! + \overload primary \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear, QCalendar cal) Returns the QDateTime represented by the \a string, using the \a @@ -5984,14 +6020,14 @@ QDateTime QDateTime::fromString(QStringView string, Qt::DateFormat format) */ /*! - \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, QCalendar cal) - \overload \since 6.0 + \overload fromString() + \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, QCalendar cal) */ /*! - \overload \since 6.0 + \overload fromString() */ QDateTime QDateTime::fromString(const QString &string, QStringView format, int baseYear, QCalendar cal) @@ -6015,34 +6051,34 @@ QDateTime QDateTime::fromString(const QString &string, QStringView format, int b } /*! - \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal) - \overload \since 5.14 + \overload fromString() + \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, QCalendar cal) */ /*! - \fn QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal) - \overload \since 6.0 + \overload fromString() + \fn QDateTime QDateTime::fromString(const QString &string, QStringView format, QCalendar cal) */ /*! - \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal) - \overload \since 6.7 + \overload fromString() + \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear, QCalendar cal) */ /*! - \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear) - \overload \since 6.7 + \overload fromString() + \fn QDateTime QDateTime::fromString(QStringView string, QStringView format, int baseYear) Uses a default-constructed QCalendar. */ /*! - \overload \since 6.7 + \overload fromString() Uses a default-constructed QCalendar. */ @@ -6052,9 +6088,9 @@ QDateTime QDateTime::fromString(const QString &string, QStringView format, int b } /*! - \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear) - \overload \since 6.7 + \overload fromString() + \fn QDateTime QDateTime::fromString(const QString &string, const QString &format, int baseYear) Uses a default-constructed QCalendar. */ diff --git a/src/corelib/time/qgregoriancalendar.cpp b/src/corelib/time/qgregoriancalendar.cpp index d46d24ac30d..dfb99c5073d 100644 --- a/src/corelib/time/qgregoriancalendar.cpp +++ b/src/corelib/time/qgregoriancalendar.cpp @@ -31,6 +31,7 @@ static_assert(qDivMod<86400>(-172800).remainder == 0); /*! \since 5.14 + \internal \class QGregorianCalendar \inmodule QtCore diff --git a/src/corelib/time/qjalalicalendar.cpp b/src/corelib/time/qjalalicalendar.cpp index 8bc9fe125e7..683ce6e7712 100644 --- a/src/corelib/time/qjalalicalendar.cpp +++ b/src/corelib/time/qjalalicalendar.cpp @@ -39,6 +39,7 @@ qint64 firstDayOfYear(int year, int cycleNo) /*! \since 5.14 + \internal \class QJalaliCalendar \inmodule QtCore diff --git a/src/corelib/time/qjuliancalendar.cpp b/src/corelib/time/qjuliancalendar.cpp index 47da952b84a..cf3718f471d 100644 --- a/src/corelib/time/qjuliancalendar.cpp +++ b/src/corelib/time/qjuliancalendar.cpp @@ -13,6 +13,7 @@ using namespace QRoundingDown; /*! \since 5.14 + \internal \class QJulianCalendar \inmodule QtCore diff --git a/src/corelib/time/qmilankoviccalendar.cpp b/src/corelib/time/qmilankoviccalendar.cpp index a3ffa2a3053..14aef83afe3 100644 --- a/src/corelib/time/qmilankoviccalendar.cpp +++ b/src/corelib/time/qmilankoviccalendar.cpp @@ -13,6 +13,7 @@ using namespace QRoundingDown; /*! \since 5.14 + \internal \class QMilankovicCalendar \inmodule QtCore diff --git a/src/corelib/time/qromancalendar.cpp b/src/corelib/time/qromancalendar.cpp index ae113cf8323..a8ce027275a 100644 --- a/src/corelib/time/qromancalendar.cpp +++ b/src/corelib/time/qromancalendar.cpp @@ -9,6 +9,7 @@ QT_BEGIN_NAMESPACE /*! \since 5.14 + \internal \class QRomanCalendar \inmodule QtCore diff --git a/src/corelib/time/qtimezone.cpp b/src/corelib/time/qtimezone.cpp index f44c681ea80..7b43aab22d1 100644 --- a/src/corelib/time/qtimezone.cpp +++ b/src/corelib/time/qtimezone.cpp @@ -29,7 +29,7 @@ static QTimeZonePrivate *newBackendTimeZone() return new QMacTimeZonePrivate(); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(); -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) return new QTzTimeZonePrivate(); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(); @@ -50,7 +50,7 @@ static QTimeZonePrivate *newBackendTimeZone(const QByteArray &ianaId) return new QMacTimeZonePrivate(ianaId); #elif defined(Q_OS_ANDROID) return new QAndroidTimeZonePrivate(ianaId); -#elif defined(Q_OS_UNIX) +#elif defined(Q_OS_UNIX) && !defined(Q_OS_VXWORKS) return new QTzTimeZonePrivate(ianaId); #elif QT_CONFIG(icu) return new QIcuTimeZonePrivate(ianaId); @@ -1482,7 +1482,8 @@ QTimeZone QTimeZone::utc() bool QTimeZone::isTimeZoneIdAvailable(const QByteArray &ianaId) { -#if defined(Q_OS_UNIX) && !(defined(Q_OS_ANDROID) || defined(Q_OS_DARWIN)) +#if defined(Q_OS_UNIX) && !(QT_CONFIG(timezone_tzdb) || defined(Q_OS_DARWIN) \ + || defined(Q_OS_ANDROID) || defined(Q_OS_VXWORKS)) // Keep #if-ery consistent with selection of QTzTimeZonePrivate in // newBackendTimeZone(). Skip the pre-check, as the TZ backend accepts POSIX // zone IDs, which need not be valid IANA IDs. See also QTBUG-112006. diff --git a/src/corelib/time/qtimezoneprivate_p.h b/src/corelib/time/qtimezoneprivate_p.h index 804c28af372..0f19d1fd025 100644 --- a/src/corelib/time/qtimezoneprivate_p.h +++ b/src/corelib/time/qtimezoneprivate_p.h @@ -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; 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/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/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/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp index 15f5cd8f7e8..81d2719117e 100644 --- a/src/gui/rhi/qrhigles2.cpp +++ b/src/gui/rhi/qrhigles2.cpp @@ -3959,6 +3959,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); @@ -4881,6 +4882,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..ea061f9d218 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; diff --git a/src/gui/text/qfont_p.h b/src/gui/text/qfont_p.h index d1509e4a251..75550439521 100644 --- a/src/gui/text/qfont_p.h +++ b/src/gui/text/qfont_p.h @@ -166,6 +166,7 @@ public: QFontPrivate(); QFontPrivate(const QFontPrivate &other); + QFontPrivate &operator=(const QFontPrivate &) = delete; ~QFontPrivate(); QFontEngine *engineForScript(int script) const; @@ -206,9 +207,6 @@ public: void setVariableAxis(QFont::Tag tag, float value); void unsetVariableAxis(QFont::Tag tag); bool hasVariableAxis(QFont::Tag tag, float value) const; - -private: - QFontPrivate &operator=(const QFontPrivate &) { return *this; } }; diff --git a/src/gui/text/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/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp index 62d24eefd33..e5ada714ba5 100644 --- a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp +++ b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp @@ -110,7 +110,8 @@ QGlibNetworkInformationBackend::QGlibNetworkInformationBackend() connectivityHandlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity", G_CALLBACK(updateConnectivity), this); - networkHandlerId = g_signal_connect_swapped(networkMonitor, "network-changed", + // needed until GLib 2.86 for netlink support + networkHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-available", G_CALLBACK(updateConnectivity), this); meteredHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-metered", diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index 08c9f5d5ba2..2989b4d6df3 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -161,6 +161,7 @@ static void populateRoleMap() roleMap[QAccessible::Graphic] = NSAccessibilityImageRole; roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole; roleMap[QAccessible::BlockQuote] = NSAccessibilityGroupRole; + roleMap[QAccessible::LayeredPane] = NSAccessibilityGroupRole; } /* diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm index e0ef6cec794..4c4e5fac962 100644 --- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm +++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm @@ -491,7 +491,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions; return; if (m_panel.visible) { - const QString selection = QString::fromNSString(m_panel.URL.path); + const QString selection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C); if (selection != m_currentSelection) { m_currentSelection = selection; emit m_helper->currentChanged(QUrl::fromLocalFile(selection)); diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h index b7ff3bb2a58..7af9cf80355 100644 --- a/src/plugins/platforms/ios/qiostheme.h +++ b/src/plugins/platforms/ios/qiostheme.h @@ -26,6 +26,7 @@ public: Qt::ColorScheme colorScheme() const override; void requestColorScheme(Qt::ColorScheme scheme) override; + Qt::ContrastPreference contrastPreference() const override; #if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS) QPlatformMenuItem* createPlatformMenuItem() const override; diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm index cdf669f921c..435c21bf579 100644 --- a/src/plugins/platforms/ios/qiostheme.mm +++ b/src/plugins/platforms/ios/qiostheme.mm @@ -202,6 +202,12 @@ void QIOSTheme::requestColorScheme(Qt::ColorScheme scheme) #endif } +Qt::ContrastPreference QIOSTheme::contrastPreference() const +{ + return UIAccessibilityDarkerSystemColorsEnabled() ? Qt::ContrastPreference::HighContrast : Qt::ContrastPreference::NoPreference; +} + + void QIOSTheme::applyTheme(UIWindow *window) { const UIUserInterfaceStyle style = []{ diff --git a/src/plugins/platforms/ios/quiwindow.mm b/src/plugins/platforms/ios/quiwindow.mm index bb4268e1c88..62fd9c5d770 100644 --- a/src/plugins/platforms/ios/quiwindow.mm +++ b/src/plugins/platforms/ios/quiwindow.mm @@ -54,7 +54,9 @@ if (self.screen == UIScreen.mainScreen) { // Check if the current userInterfaceStyle reports a different appearance than // the platformTheme's appearance. We might have set that one based on the UIScreen + // Check for changes in the "Increase contrast" setting too. if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle + || previousTraitCollection.accessibilityContrast != self.traitCollection.accessibilityContrast || QGuiApplicationPrivate::platformTheme()->colorScheme() != colorScheme) { QIOSTheme::initializeSystemPalette(); QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>(); diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp index a87c33c8346..5807d157636 100644 --- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp +++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp @@ -586,14 +586,7 @@ void QWasmAccessibility::linkToParent(QAccessibleInterface *iface) void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, bool visible) { emscripten::val element = getHtmlElement(iface); - - if (visible) { - setAttribute(element, "aria-hidden", false); - setAttribute(element, "tabindex", ""); - } else { - setAttribute(element, "aria-hidden", true); // aria-hidden mean completely hidden; maybe some sort of soft-hidden should be used. - setAttribute(element, "tabindex", "-1"); - } + setAttribute(element, "aria-hidden", !visible); } void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface) diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp index 18a457198f1..a0546fdc215 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.cpp +++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp @@ -40,8 +40,23 @@ void QWasmInputContext::inputCallback(emscripten::val event) // Some of them should be implemented here later. qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "inputType : " << inputTypeString; if (!inputTypeString.compare("deleteContentBackward")) { - QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier); - QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_Backspace, Qt::NoModifier); + + QInputMethodQueryEvent queryEvent(Qt::ImQueryAll); + QCoreApplication::sendEvent(m_focusObject, &queryEvent); + int cursorPosition = queryEvent.value(Qt::ImCursorPosition).toInt(); + + int deleteLength = rangesPair.second - rangesPair.first; + int deleteFrom = -1; + if (cursorPosition > rangesPair.first) { + deleteFrom = -(cursorPosition - rangesPair.first); + } + QInputMethodEvent e; + e.setCommitString(QString(), deleteFrom, deleteLength); + QCoreApplication::sendEvent(m_focusObject, &e); + + rangesPair.first = 0; + rangesPair.second = 0; + event.call<void>("stopImmediatePropagation"); return; } else if (!inputTypeString.compare("deleteContentForward")) { @@ -138,41 +153,23 @@ void QWasmInputContext::compositionUpdateCallback(emscripten::val event) const auto compositionStr = QString::fromEcmaString(event["data"]); qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << compositionStr; - // WA for IOS. - // Not sure now because I cannot test it anymore. -// int replaceSize = 0; -// emscripten::val win = emscripten::val::global("window"); -// emscripten::val sel = win.call<emscripten::val>("getSelection"); -// if (!sel.isNull() && !sel.isUndefined() -// && sel["rangeCount"].as<int>() > 0) { -// QInputMethodQueryEvent queryEvent(Qt::ImQueryAll); -// QCoreApplication::sendEvent(QGuiApplication::focusObject(), &queryEvent); -// qCDebug(qLcQpaWasmInputContext) << "Qt surrounding text: " << queryEvent.value(Qt::ImSurroundingText).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt current selection: " << queryEvent.value(Qt::ImCurrentSelection).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt text before cursor: " << queryEvent.value(Qt::ImTextBeforeCursor).toString(); -// qCDebug(qLcQpaWasmInputContext) << "Qt text after cursor: " << queryEvent.value(Qt::ImTextAfterCursor).toString(); -// -// const QString &selectedStr = QString::fromEcmaString(sel.call<emscripten::val>("toString")); -// const auto &preeditStr = preeditString(); -// qCDebug(qLcQpaWasmInputContext) << "Selection.type : " << sel["type"].as<std::string>(); -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Selected: " << selectedStr; -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "PreeditString: " << preeditStr; -// if (!sel["type"].as<std::string>().compare("Range")) { -// QString surroundingTextBeforeCursor = queryEvent.value(Qt::ImTextBeforeCursor).toString(); -// if (surroundingTextBeforeCursor.endsWith(selectedStr)) { -// replaceSize = selectedStr.size(); -// qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO << "Current Preedit: " << preeditStr << replaceSize; -// } -// } -// emscripten::val range = sel.call<emscripten::val>("getRangeAt", 0); -// qCDebug(qLcQpaWasmInputContext) << "Range.startOffset : " << range["startOffset"].as<int>(); -// qCDebug(qLcQpaWasmInputContext) << "Range.endOffset : " << range["endOffset"].as<int>(); -// } -// -// setPreeditString(compositionStr, replaceSize); setPreeditString(compositionStr, 0); } +void QWasmInputContext::beforeInputCallback(emscripten::val event) +{ + emscripten::val ranges = event.call<emscripten::val>("getTargetRanges"); + + auto length = ranges["length"].as<int>(); + for (auto i = 0; i < length; i++) { + emscripten::val range = ranges[i]; + qCDebug(qLcQpaWasmInputContext) << "startOffset" << range["startOffset"].as<int>(); + qCDebug(qLcQpaWasmInputContext) << "endOffset" << range["endOffset"].as<int>(); + rangesPair.first = range["startOffset"].as<int>(); + rangesPair.second = range["endOffset"].as<int>(); + } +} + QWasmInputContext::QWasmInputContext() { qCDebug(qLcQpaWasmInputContext) << Q_FUNC_INFO; diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h index 6d24c7fea0d..97415451b2a 100644 --- a/src/plugins/platforms/wasm/qwasminputcontext.h +++ b/src/plugins/platforms/wasm/qwasminputcontext.h @@ -45,6 +45,7 @@ public: void compositionEndCallback(emscripten::val event); void compositionStartCallback(emscripten::val event); void compositionUpdateCallback(emscripten::val event); + void beforeInputCallback(emscripten::val event); void updateGeometry(); @@ -62,6 +63,7 @@ private: bool m_inputMethodAccepted = false; QObject *m_focusObject = nullptr; emscripten::val m_inputElement = emscripten::val::null(); + QPair<int, int> rangesPair; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp index 04f52ac9b1b..e49c1dc49f1 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.cpp +++ b/src/plugins/platforms/wasm/qwasmwindow.cpp @@ -110,6 +110,7 @@ QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, // Set up m_inputElement, which takes focus whenever a Qt text input UI element has // foucus. m_inputElement["classList"].call<void>("add", emscripten::val("qt-window-input-element")); + m_inputElement.call<void>("setAttribute", std::string("contenteditable"), std::string("true")); m_inputElement.set("type", "text"); m_inputElement["style"].set("position", "absolute"); m_inputElement["style"].set("left", 0); @@ -227,6 +228,8 @@ void QWasmWindow::registerEventHandlers() [this](emscripten::val event){ handleCompositionStartEvent(event); }); m_compositionEndCallback = QWasmEventHandler(m_window, "compositionend", [this](emscripten::val event){ handleCompositionEndEvent(event); }); + m_beforeInputCallback = QWasmEventHandler(m_window, "beforeinput", + [this](emscripten::val event){ handleBeforeInputEvent(event); }); } QWasmWindow::~QWasmWindow() @@ -789,6 +792,16 @@ void QWasmWindow::handleCompositionEndEvent(emscripten::val event) m_focusHelper.set("innerHTML", std::string()); } +void QWasmWindow::handleBeforeInputEvent(emscripten::val event) +{ + qWarning() << Q_FUNC_INFO; + + if (QWasmInputContext *inputContext = QWasmIntegration::get()->wasmInputContext(); inputContext->isActive()) + inputContext->beforeInputCallback(event); + // else + // m_focusHelper.set("innerHTML", std::string()); +} + void QWasmWindow::handlePointerEnterLeaveEvent(const PointerEvent &event) { if (processPointerEnterLeave(event)) diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h index cbe930dce89..8e6e5021dcf 100644 --- a/src/plugins/platforms/wasm/qwasmwindow.h +++ b/src/plugins/platforms/wasm/qwasmwindow.h @@ -145,6 +145,7 @@ private: void handleCompositionStartEvent(emscripten::val event); void handleCompositionUpdateEvent(emscripten::val event); void handleCompositionEndEvent(emscripten::val event); + void handleBeforeInputEvent(emscripten::val event); void handlePointerEnterLeaveEvent(const PointerEvent &event); bool processPointerEnterLeave(const PointerEvent &event); @@ -183,6 +184,7 @@ private: QWasmEventHandler m_compositionStartCallback; QWasmEventHandler m_compositionUpdateCallback; QWasmEventHandler m_compositionEndCallback; + QWasmEventHandler m_beforeInputCallback; QWasmEventHandler m_pointerDownCallback; QWasmEventHandler m_pointerMoveCallback; diff --git a/src/plugins/platforms/wayland/CMakeLists.txt b/src/plugins/platforms/wayland/CMakeLists.txt index 7e3589def6b..0ce9a4b091c 100644 --- a/src/plugins/platforms/wayland/CMakeLists.txt +++ b/src/plugins/platforms/wayland/CMakeLists.txt @@ -17,14 +17,6 @@ qt_internal_add_module(WaylandGlobalPrivate NO_GENERATE_CPP_EXPORTS ) -# Work around 115101. -# If nothing depends on the WaylandGlobalPrivate target it doesn't run custom commands that the -# target depends on. WaylandGlobalPrivate_ensure_sync_headers makes sure that 'all' depends on -# WaylandGlobalPrivate_sync_headers. -# TODO: This needs to be removed once the fix for QTBUG-115101 is merged in qtbase. -add_custom_target(WaylandGlobalPrivate_ensure_sync_headers ALL) -add_dependencies(WaylandGlobalPrivate_ensure_sync_headers WaylandGlobalPrivate_sync_headers) - # special case begin # TODO: Ideally these macros would be part of the qtwaylandscanner tool, and not the compositor/client include(../../../../src/tools/qtwaylandscanner/Qt6WaylandClientMacros.cmake) diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp index 71103183183..edc1ea3a9dc 100644 --- a/src/plugins/platforms/windows/qwindowsiconengine.cpp +++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp @@ -63,8 +63,8 @@ static QString getGlyphs(QStringView iconName) {"go-home"_L1, u"\ue80f"}, // {"go-jump"_L1, u"\uf719"}, //{"go-last"_L1, u"\ue5dd"}, - {"go-next"_L1, u"\ue893"}, - {"go-previous"_L1, u"\ue892"}, + {"go-next"_L1, u"\ue72a"}, + {"go-previous"_L1, u"\ue72b"}, //{"go-top"_L1, u"\ue25a"}, {"go-up"_L1, u"\ue74a"}, {"help-about"_L1, u"\ue946"}, @@ -100,7 +100,7 @@ static QString getGlyphs(QStringView iconName) //{"object-flip-vertical"_L1, u"\u"}, {"object-rotate-left"_L1, u"\ue80c"}, {"object-rotate-right"_L1, u"\ue80d"}, - //{"process-stop"_L1, u"\ue5c9"}, + {"process-stop"_L1, u"\uf140"}, {"system-lock-screen"_L1, u"\uee3f"}, {"system-log-out"_L1, u"\uf3b1"}, //{"system-run"_L1, u"\u"}, diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp index 482810d5b7e..7908f22d99d 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.cpp +++ b/src/plugins/platforms/windows/qwindowsscreen.cpp @@ -874,9 +874,8 @@ const QWindowsScreen *QWindowsScreenManager::screenAtDp(const QPoint &p) const return nullptr; } -const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const +const QWindowsScreen *QWindowsScreenManager::screenForMonitor(HMONITOR hMonitor) const { - HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); if (hMonitor == nullptr) return nullptr; const auto it = @@ -889,4 +888,18 @@ const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const return it != m_screens.cend() ? *it : nullptr; } +const QWindowsScreen *QWindowsScreenManager::screenForHwnd(HWND hwnd) const +{ + HMONITOR hMonitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); + return screenForMonitor(hMonitor); +} + +const QWindowsScreen *QWindowsScreenManager::screenForRect(const RECT *rect) const +{ + if (rect == nullptr) + return nullptr; + HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_DEFAULTTONULL); + return screenForMonitor(hMonitor); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h index 8d555998388..0032fd462cb 100644 --- a/src/plugins/platforms/windows/qwindowsscreen.h +++ b/src/plugins/platforms/windows/qwindowsscreen.h @@ -115,12 +115,14 @@ public: const QWindowsScreen *screenAtDp(const QPoint &p) const; const QWindowsScreen *screenForHwnd(HWND hwnd) const; + const QWindowsScreen *screenForRect(const RECT *rect) const; static bool isSingleScreen(); private: void addScreen(const QWindowsScreenData &screenData); void removeScreen(int index); + const QWindowsScreen *screenForMonitor(HMONITOR monitor) const; HWND m_displayChangeObserver = nullptr; WindowsScreenList m_screens; diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp index 01716fba60c..72daffb56b1 100644 --- a/src/plugins/platforms/windows/qwindowswindow.cpp +++ b/src/plugins/platforms/windows/qwindowswindow.cpp @@ -466,15 +466,17 @@ static bool applyBlurBehindWindow(HWND hwnd) return result; } +static bool shouldShowTitlebarButton(Qt::WindowFlags flags, Qt::WindowFlags button) +{ + return !flags.testFlag(Qt::CustomizeWindowHint) || flags.testFlags(Qt::CustomizeWindowHint | button); +} + // from qwidget_win.cpp, pass flags separately in case they have been "autofixed". static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags) { - if ((flags & Qt::MSWindowsFixedSizeDialogHint) || !(flags & Qt::WindowMaximizeButtonHint)) - return false; - // if the user explicitly asked for the maximize button, we try to add - // it even if the window has fixed size. - return (flags & Qt::CustomizeWindowHint) || - w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX); + return !flags.testFlag(Qt::MSWindowsFixedSizeDialogHint) && + (shouldShowTitlebarButton(flags, Qt::WindowMaximizeButtonHint) || + w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX)); } bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags) @@ -805,6 +807,7 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag if (topLevel) { if ((type == Qt::Window || dialog || tool)) { + const bool defaultTitlebar = !flags.testFlag(Qt::CustomizeWindowHint); if (!(flags & Qt::FramelessWindowHint)) { style |= WS_POPUP; if (flags & Qt::MSWindowsFixedSizeDialogHint) { @@ -812,16 +815,16 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag } else { style |= WS_THICKFRAME; } - if (flags & Qt::WindowTitleHint) + if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint)) style |= WS_CAPTION; // Contains WS_DLGFRAME } - if (flags & Qt::WindowSystemMenuHint) + if (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowSystemMenuHint)) style |= WS_SYSMENU; - else if (dialog && (flags & Qt::WindowCloseButtonHint) && !(flags & Qt::FramelessWindowHint)) { + else if (dialog && (defaultTitlebar || flags.testFlags(Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint)) && !(flags & Qt::FramelessWindowHint)) { style |= WS_SYSMENU | WS_BORDER; // QTBUG-2027, dialogs without system menu. exStyle |= WS_EX_DLGMODALFRAME; } - const bool showMinimizeButton = flags & Qt::WindowMinimizeButtonHint; + const bool showMinimizeButton = shouldShowTitlebarButton(flags, Qt::WindowMinimizeButtonHint); if (showMinimizeButton) style |= WS_MINIMIZEBOX; const bool showMaximizeButton = shouldShowMaximizeButton(w, flags); @@ -2113,7 +2116,8 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) QWindowsThemeCache::clearThemeCache(hwnd); // Send screen change first, so that the new screen is set during any following resize - checkForScreenChanged(QWindowsWindow::FromDpiChange); + const auto prcNewWindow = reinterpret_cast<const RECT *>(lParam); + checkForScreenChanged(QWindowsWindow::FromDpiChange, !m_inSetgeometry ? prcNewWindow : nullptr); if (!IsZoomed(hwnd)) m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale); @@ -2136,7 +2140,6 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam) // making the SetWindowPos() call. if (!m_inSetgeometry) { updateFullFrameMargins(); - const auto prcNewWindow = reinterpret_cast<RECT *>(lParam); SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); @@ -2351,14 +2354,15 @@ static inline bool equalDpi(const QDpi &d1, const QDpi &d2) return qFuzzyCompare(d1.first, d2.first) && qFuzzyCompare(d1.second, d2.second); } -void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode) +void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode, const RECT *suggestedRect) { if ((parent() && !parent()->isForeignWindow()) || QWindowsScreenManager::isSingleScreen()) return; QPlatformScreen *currentScreen = screen(); auto topLevel = isTopLevel_sys() ? m_data.hwnd : GetAncestor(m_data.hwnd, GA_ROOT); - const QWindowsScreen *newScreen = + const QWindowsScreen *newScreen = suggestedRect ? + QWindowsContext::instance()->screenManager().screenForRect(suggestedRect) : QWindowsContext::instance()->screenManager().screenForHwnd(topLevel); if (newScreen == nullptr || newScreen == currentScreen) diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h index 499b2a9ca86..ee348f1ac16 100644 --- a/src/plugins/platforms/windows/qwindowswindow.h +++ b/src/plugins/platforms/windows/qwindowswindow.h @@ -342,7 +342,7 @@ public: void stopAlertWindow(); enum ScreenChangeMode { FromGeometryChange, FromDpiChange, FromScreenAdded }; - void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange); + void checkForScreenChanged(ScreenChangeMode mode = FromGeometryChange, const RECT *suggestedRect = nullptr); void registerTouchWindow(); static void setHasBorderInFullScreenStatic(QWindow *window, bool border); diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp index b2675d5b884..835499d3554 100644 --- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp +++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp @@ -154,6 +154,7 @@ long roleToControlTypeId(QAccessible::Role role) {QAccessible::WebDocument, UIA_DocumentControlTypeId}, {QAccessible::Heading, UIA_TextControlTypeId}, {QAccessible::BlockQuote, UIA_GroupControlTypeId}, + {QAccessible::LayeredPane, UIA_PaneControlTypeId}, }; long controlType = mapping.value(role, UIA_CustomControlTypeId); diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt index 492d274c2fd..204acd72ba0 100644 --- a/src/plugins/platforms/xcb/CMakeLists.txt +++ b/src/plugins/platforms/xcb/CMakeLists.txt @@ -120,30 +120,6 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION CLANG -ftemplate-depth=1024 ) -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting - SOURCES - nativepainting/qbackingstore_x11.cpp nativepainting/qbackingstore_x11_p.h - nativepainting/qcolormap_x11.cpp nativepainting/qcolormap_x11_p.h - nativepainting/qpaintengine_x11.cpp nativepainting/qpaintengine_x11_p.h - nativepainting/qpixmap_x11.cpp nativepainting/qpixmap_x11_p.h - nativepainting/qpolygonclipper_p.h - nativepainting/qt_x11_p.h - nativepainting/qtessellator.cpp nativepainting/qtessellator_p.h - nativepainting/qxcbnativepainting.cpp nativepainting/qxcbnativepainting.h - INCLUDE_DIRECTORIES - nativepainting -) - -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_native_painting AND QT_FEATURE_xrender - PUBLIC_LIBRARIES - PkgConfig::XRender -) - -qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_FEATURE_xcb_native_painting - LIBRARIES - WrapFreetype::WrapFreetype -) - if(QT_FEATURE_system_xcb_xinput) qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT) else() diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp deleted file mode 100644 index 1ac5f6a64bc..00000000000 --- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright (C) 2018 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -// Qt-Security score:significant reason:default - -#include "qbackingstore_x11_p.h" -#include "qxcbwindow.h" -#include "qpixmap_x11_p.h" - -#include <private/qhighdpiscaling_p.h> -#include <QPainter> - -#if QT_CONFIG(xrender) -# include <X11/extensions/Xrender.h> -#endif - -#define register /* C++17 deprecated register */ -#include <X11/Xlib.h> -#undef register - -QT_BEGIN_NAMESPACE - -QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window) - : QPlatformBackingStore(window) - , m_translucentBackground(false) -{ - if (QXcbWindow *w = static_cast<QXcbWindow *>(window->handle())) { - m_translucentBackground = w->connection()->hasXRender() && - QImage::toPixelFormat(w->imageFormat()).alphaUsage() == QPixelFormat::UsesAlpha; - } -} - -QXcbNativeBackingStore::~QXcbNativeBackingStore() -{} - -QPaintDevice *QXcbNativeBackingStore::paintDevice() -{ - return &m_pixmap; -} - -void QXcbNativeBackingStore::flush(QWindow *window, const QRegion ®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/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp index 0ed8e2890d9..2944c02fd79 100644 --- a/src/plugins/styles/modernwindows/qwindows11style.cpp +++ b/src/plugins/styles/modernwindows/qwindows11style.cpp @@ -580,21 +580,18 @@ void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOpt if (combobox->frame) drawLineEditFrame(painter, frameRect, combobox, combobox->editable); - const bool isMouseOver = state & State_MouseOver; const bool hasFocus = state & State_HasFocus; - if (isMouseOver && !hasFocus && !highContrastTheme) - drawRoundedRect(painter, frameRect, Qt::NoPen, winUI3Color(subtleHighlightColor)); + QStyleOption opt(*option); + opt.state.setFlag(QStyle::State_On, false); + drawRoundedRect(painter, frameRect, Qt::NoPen, controlFillBrush(&opt, ControlType::Control)); if (sub & SC_ComboBoxArrow) { QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget).adjusted(4, 0, -4, 1); painter->setFont(d->assetFont); - painter->setPen(combobox->palette.text().color()); + painter->setPen(controlTextColor(option)); painter->drawText(rect, Qt::AlignCenter, ChevronDownMed); } - if (state & State_HasFocus) { - drawPrimitive(PE_FrameFocusRect, option, painter, widget); - } - if (state & State_KeyboardFocusChange && state & State_HasFocus) { + if (state & State_KeyboardFocusChange && hasFocus) { QStyleOptionFocusRect fropt; fropt.QStyleOption::operator=(*option); proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget); @@ -1048,12 +1045,27 @@ void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption if (rect.width() <= 0) break; - painter->setPen(Qt::NoPen); - if (vopt->features & QStyleOptionViewItem::Alternate) - painter->setBrush(vopt->palette.alternateBase()); - else - painter->setBrush(vopt->palette.base()); - painter->drawRect(rect); + if (vopt->features & QStyleOptionViewItem::Alternate) { + QPalette::ColorGroup cg = + (widget ? widget->isEnabled() : (vopt->state & QStyle::State_Enabled)) + ? QPalette::Normal + : QPalette::Disabled; + if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active)) + cg = QPalette::Inactive; + painter->fillRect(rect, option->palette.brush(cg, QPalette::AlternateBase)); + } + + if (option->state & State_Selected && !highContrastTheme) { + // keep in sync with CE_ItemViewItem QListView indicator painting + const auto col = option->palette.accent().color(); + painter->setBrush(col); + painter->setPen(col); + const auto xPos = isRtl ? rect.right() - 4.5f : rect.left() + 3.5f; + const auto yOfs = rect.height() / 4.; + QRectF r(QPointF(xPos, rect.y() + yOfs), + QPointF(xPos + 1, rect.y() + rect.height() - yOfs)); + painter->drawRoundedRect(r, 1, 1); + } const bool isTreeDecoration = vopt->features.testFlag( QStyleOptionViewItem::IsDecorationForRootColumn); @@ -1182,11 +1194,14 @@ void QWindows11Style::drawControl(ControlElement element, const QStyleOption *op painter->setRenderHint(QPainter::Antialiasing); switch (element) { case QStyle::CE_ComboBoxLabel: +#if QT_CONFIG(combobox) if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { + painter->setPen(controlTextColor(option)); QStyleOptionComboBox newOption = *cb; newOption.rect.adjust(4,0,-4,0); QCommonStyle::drawControl(element, &newOption, painter, widget); } +#endif // QT_CONFIG(combobox) break; case QStyle::CE_TabBarTabShape: #if QT_CONFIG(tabbar) @@ -2062,6 +2077,20 @@ QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOption } break; } + case CC_ComboBox: { + if (subControl == SC_ComboBoxArrow) { + const auto indicatorWidth = + proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget); + const int endX = option->rect.right() - contentHMargin - 2; + const int startX = endX - indicatorWidth; + const QRect rect(QPoint(startX, option->rect.top()), + QPoint(endX, option->rect.bottom())); + ret = visualRect(option->direction, option->rect, rect); + } else { + ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget); + } + break; + } default: ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget); } @@ -2152,14 +2181,18 @@ QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *o break; } #endif +#if QT_CONFIG(combobox) case CT_ComboBox: if (const auto *comboBoxOpt = qstyleoption_cast<const QStyleOptionComboBox *>(option)) { contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget); // don't rely on QWindowsThemeData contentSize += QSize(4, 4); // default win11 style margins - if (comboBoxOpt->subControls & SC_ComboBoxArrow) - contentSize += QSize(8, 0); // arrow margins + if (comboBoxOpt->subControls & SC_ComboBoxArrow) { + const auto w = proxy()->pixelMetric(PM_MenuButtonIndicator, option, widget); + contentSize.rwidth() += w + contentItemHMargin; + } } break; +#endif case CT_HeaderSection: // windows vista does not honor the indicator (as it was drawn above the text, not on the // side) so call QWindowsStyle::styleHint directly to get the correct size hint @@ -2290,6 +2323,9 @@ int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option, case PM_ButtonShiftVertical: res = 0; break; + case PM_TreeViewIndentation: + res = 30; + break; default: res = QWindowsVistaStyle::pixelMetric(metric, option, widget); } @@ -2377,6 +2413,13 @@ void QWindows11Style::unpolish(QWidget *widget) widget->setProperty("_q_original_menubar_maxheight", QVariant()); } #endif + const auto comboBoxContainer = qobject_cast<const QComboBoxPrivateContainer *>(widget); + if (comboBoxContainer) { + widget->setAttribute(Qt::WA_OpaquePaintEvent, true); + widget->setAttribute(Qt::WA_TranslucentBackground, false); + widget->setWindowFlag(Qt::FramelessWindowHint, false); + widget->setWindowFlag(Qt::NoDropShadowWindowHint, false); + } if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget); scrollarea diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp index abe0bde540f..22ca18b10bf 100644 --- a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp +++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp @@ -1626,6 +1626,12 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt break; case PE_Frame: + if (widget && widget->inherits("QComboBoxPrivateContainer")){ + QStyleOption copy = *option; + copy.state |= State_Raised; + proxy()->drawPrimitive(PE_PanelMenu, ©, painter, widget); + break; + } #if QT_CONFIG(accessibility) if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText) || QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) || @@ -1704,6 +1710,14 @@ void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOpt return; } + case PE_PanelMenu: + if (widget && widget->inherits("QComboBoxPrivateContainer")){ + //fill combobox popup background + QWindowsThemeData popupbackgroundTheme(widget, painter, QWindowsVistaStylePrivate::MenuTheme, + MENU_POPUPBACKGROUND, stateId, option->rect); + d->drawBackground(popupbackgroundTheme); + } + case PE_PanelMenuBar: break; diff --git a/src/tools/windeployqt/main.cpp b/src/tools/windeployqt/main.cpp index e35330ebeb3..6cb935ef47d 100644 --- a/src/tools/windeployqt/main.cpp +++ b/src/tools/windeployqt/main.cpp @@ -592,15 +592,17 @@ static inline int parseArguments(const QStringList &arguments, QCommandLineParse } // default to deployment of compiler runtime for windows desktop configurations - if (options->platform == WindowsDesktopMinGW || options->platform.testFlags(WindowsDesktopMsvc) - || parser->isSet(compilerRunTimeOption)) + if (options->platform == WindowsDesktopMinGW || options->platform == WindowsDesktopClangMinGW + || options->platform.testFlags(WindowsDesktopMsvc) || parser->isSet(compilerRunTimeOption)) options->compilerRunTime = true; if (parser->isSet(noCompilerRunTimeOption)) options->compilerRunTime = false; if (options->compilerRunTime && options->platform != WindowsDesktopMinGW + && options->platform != WindowsDesktopClangMinGW && !options->platform.testFlags(WindowsDesktopMsvc)) { - *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for Desktop MSVC/g++ only."); + *errorMessage = QStringLiteral("Deployment of the compiler runtime is implemented for " + "Desktop MSVC and MinGW (g++ and Clang) only."); return CommandLineParseError; } diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt index ce30f50616b..c0be3debe49 100644 --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -681,6 +681,7 @@ qt_internal_extend_target(Widgets CONDITION MACOS AND (QT_FEATURE_menu OR QT_FEA qt_internal_extend_target(Widgets CONDITION QT_FEATURE_colordialog SOURCES dialogs/qcolordialog.cpp dialogs/qcolordialog.h + dialogs/qcolorwell_p.h ) qt_internal_extend_target(Widgets CONDITION QT_FEATURE_dialog diff --git a/src/widgets/accessible/itemviews.cpp b/src/widgets/accessible/itemviews.cpp index fc969e17380..265c523eae0 100644 --- a/src/widgets/accessible/itemviews.cpp +++ b/src/widgets/accessible/itemviews.cpp @@ -60,7 +60,7 @@ int QAccessibleTable::logicalIndex(const QModelIndex &index) const } QAccessibleTable::QAccessibleTable(QWidget *w) - : QAccessibleObject(w) + : QAccessibleWidgetV2(w) { Q_ASSERT(view()); @@ -677,7 +677,7 @@ void *QAccessibleTable::interface_cast(QAccessible::InterfaceType t) return static_cast<QAccessibleSelectionInterface*>(this); if (t == QAccessible::TableInterface) return static_cast<QAccessibleTableInterface*>(this); - return nullptr; + return QAccessibleWidgetV2::interface_cast(t); } void QAccessibleTable::modelChange(QAccessibleTableModelChangeEvent *event) diff --git a/src/widgets/accessible/itemviews_p.h b/src/widgets/accessible/itemviews_p.h index 9b30f36ced3..79f9a7f2f05 100644 --- a/src/widgets/accessible/itemviews_p.h +++ b/src/widgets/accessible/itemviews_p.h @@ -31,7 +31,9 @@ QT_BEGIN_NAMESPACE class QAccessibleTableCell; class QAccessibleTableHeaderCell; -class QAccessibleTable :public QAccessibleTableInterface, public QAccessibleSelectionInterface, public QAccessibleObject +class QAccessibleTable : public QAccessibleTableInterface, + public QAccessibleSelectionInterface, + public QAccessibleWidgetV2 { public: explicit QAccessibleTable(QWidget *w); diff --git a/src/widgets/accessible/rangecontrols.cpp b/src/widgets/accessible/rangecontrols.cpp index c0de5357c9a..1f7b20833dd 100644 --- a/src/widgets/accessible/rangecontrols.cpp +++ b/src/widgets/accessible/rangecontrols.cpp @@ -65,6 +65,16 @@ QAccessibleInterface *QAccessibleAbstractSpinBox::lineEditIface() const #endif } +QAccessible::State QAccessibleAbstractSpinBox::state() const +{ + QAccessible::State state = QAccessibleWidgetV2::state(); + if (abstractSpinBox()->isReadOnly()) + state.readOnly = true; + else + state.editable = true; + return state; +} + QString QAccessibleAbstractSpinBox::text(QAccessible::Text t) const { if (t == QAccessible::Value) diff --git a/src/widgets/accessible/rangecontrols_p.h b/src/widgets/accessible/rangecontrols_p.h index dd5a6a4531c..5a023d2f00b 100644 --- a/src/widgets/accessible/rangecontrols_p.h +++ b/src/widgets/accessible/rangecontrols_p.h @@ -42,6 +42,7 @@ public: explicit QAccessibleAbstractSpinBox(QWidget *w); virtual ~QAccessibleAbstractSpinBox(); + QAccessible::State state() const override; QString text(QAccessible::Text t) const override; void *interface_cast(QAccessible::InterfaceType t) override; diff --git a/src/widgets/dialogs/qcolordialog.cpp b/src/widgets/dialogs/qcolordialog.cpp index eac5de33d32..f8125204045 100644 --- a/src/widgets/dialogs/qcolordialog.cpp +++ b/src/widgets/dialogs/qcolordialog.cpp @@ -39,6 +39,7 @@ #include "qwindow.h" #include "private/qdialog_p.h" +#include "private/qcolorwell_p.h" #include <qpa/qplatformintegration.h> #include <qpa/qplatformservices.h> @@ -56,16 +57,12 @@ namespace QtPrivate { class QColorLuminancePicker; class QColorPicker; class QColorShower; -class QWellArray; -class QColorWell; class QColorPickingEventFilter; } // namespace QtPrivate using QColorLuminancePicker = QtPrivate::QColorLuminancePicker; using QColorPicker = QtPrivate::QColorPicker; using QColorShower = QtPrivate::QColorShower; -using QWellArray = QtPrivate::QWellArray; -using QColorWell = QtPrivate::QColorWell; using QColorPickingEventFilter = QtPrivate::QColorPickingEventFilter; class QColorDialogPrivate : public QDialogPrivate @@ -162,95 +159,6 @@ private: //////////// QWellArray BEGIN -namespace QtPrivate { - -class QWellArray : public QWidget -{ - Q_OBJECT - Q_PROPERTY(int selectedColumn READ selectedColumn) - Q_PROPERTY(int selectedRow READ selectedRow) - -public: - QWellArray(int rows, int cols, QWidget* parent=nullptr); - ~QWellArray() {} - - int selectedColumn() const { return selCol; } - int selectedRow() const { return selRow; } - - virtual void setCurrent(int row, int col); - virtual void setSelected(int row, int col); - - QSize sizeHint() const override; - - inline int cellWidth() const - { return cellw; } - - inline int cellHeight() const - { return cellh; } - - inline int rowAt(int y) const - { return y / cellh; } - - inline int columnAt(int x) const - { if (isRightToLeft()) return ncols - (x / cellw) - 1; return x / cellw; } - - inline int rowY(int row) const - { return cellh * row; } - - inline int columnX(int column) const - { if (isRightToLeft()) return cellw * (ncols - column - 1); return cellw * column; } - - inline int numRows() const - { return nrows; } - - inline int numCols() const - {return ncols; } - - inline QRect cellRect() const - { return QRect(0, 0, cellw, cellh); } - - inline QSize gridSize() const - { return QSize(ncols * cellw, nrows * cellh); } - - QRect cellGeometry(int row, int column) - { - QRect r; - if (row >= 0 && row < nrows && column >= 0 && column < ncols) - r.setRect(columnX(column), rowY(row), cellw, cellh); - return r; - } - - inline void updateCell(int row, int column) { update(cellGeometry(row, column)); } - -signals: - void selected(int row, int col); - void currentChanged(int row, int col); - void colorChanged(int index, QRgb color); - -protected: - virtual void paintCell(QPainter *, int row, int col, const QRect&); - virtual void paintCellContents(QPainter *, int row, int col, const QRect&); - - void mousePressEvent(QMouseEvent*) override; - void mouseReleaseEvent(QMouseEvent*) override; - void keyPressEvent(QKeyEvent*) override; - void focusInEvent(QFocusEvent*) override; - void focusOutEvent(QFocusEvent*) override; - void paintEvent(QPaintEvent *) override; - -private: - Q_DISABLE_COPY(QWellArray) - - int nrows; - int ncols; - int cellw; - int cellh; - int curRow; - int curCol; - int selRow; - int selCol; -}; - void QWellArray::paintEvent(QPaintEvent *e) { QRect r = e->rect(); @@ -475,11 +383,12 @@ void QWellArray::keyPressEvent(QKeyEvent* e) e->ignore(); // we don't accept the event return; } - -} // namespace QtPrivate +} //////////// QWellArray END +namespace QtPrivate { + // Event filter to be installed on the dialog while in color-picking mode. class QColorPickingEventFilter : public QObject { public: @@ -510,7 +419,7 @@ private: QColorDialogPrivate *m_dp; }; -} // unnamed namespace +} // namespace QtPrivate /*! Returns the number of custom colors supported by QColorDialog. All @@ -570,35 +479,6 @@ static inline void rgb2hsv(QRgb rgb, int &h, int &s, int &v) c.getHsv(&h, &s, &v); } -namespace QtPrivate { - -class QColorWell : public QWellArray -{ -public: - QColorWell(QWidget *parent, int r, int c, const QRgb *vals) - :QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1) - { setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); } - -protected: - void paintCellContents(QPainter *, int row, int col, const QRect&) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; -#if QT_CONFIG(draganddrop) - void dragEnterEvent(QDragEnterEvent *e) override; - void dragLeaveEvent(QDragLeaveEvent *e) override; - void dragMoveEvent(QDragMoveEvent *e) override; - void dropEvent(QDropEvent *e) override; -#endif - -private: - const QRgb *values; - bool mousePressed; - QPoint pressPos; - QPoint oldCurrent; - -}; - void QColorWell::paintCellContents(QPainter *p, int row, int col, const QRect &r) { int i = row + col*numRows(); @@ -686,6 +566,8 @@ void QColorWell::mouseReleaseEvent(QMouseEvent *e) mousePressed = false; } +namespace QtPrivate { + class QColorPicker : public QFrame { Q_OBJECT @@ -703,18 +585,21 @@ signals: protected: QSize sizeHint() const override; void paintEvent(QPaintEvent*) override; + void keyPressEvent(QKeyEvent *event) override; void mouseMoveEvent(QMouseEvent *) override; void mousePressEvent(QMouseEvent *) override; void resizeEvent(QResizeEvent *) override; private: - int hue; - int sat; + QPoint m_pos; - QPoint colPt(); - int huePt(const QPoint &pt); - int satPt(const QPoint &pt); - void setCol(const QPoint &pt); + QPixmap createColorsPixmap(); + QPoint colPt(int hue, int sat); + int huePt(const QPoint &pt, const QSize &widgetSize); + int huePt(const QPoint &pt) { return huePt(pt, size()); } + int satPt(const QPoint &pt, const QSize &widgetSize); + int satPt(const QPoint &pt) { return satPt(pt, size()); } + void setCol(const QPoint &pt, bool notify = true); QPixmap pix; bool crossVisible; @@ -743,6 +628,7 @@ signals: protected: void paintEvent(QPaintEvent*) override; + void keyPressEvent(QKeyEvent *event) override; void mouseMoveEvent(QMouseEvent *) override; void mousePressEvent(QMouseEvent *) override; @@ -778,6 +664,7 @@ QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) hue = 100; val = 100; sat = 100; pix = nullptr; // setAttribute(WA_NoErase, true); + setFocusPolicy(Qt::StrongFocus); } QColorLuminancePicker::~QColorLuminancePicker() @@ -785,6 +672,21 @@ QColorLuminancePicker::~QColorLuminancePicker() delete pix; } +void QColorLuminancePicker::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Down: + setVal(std::clamp(val - 1, 0, 255)); + break; + case Qt::Key_Up: + setVal(std::clamp(val + 1, 0, 255)); + break; + default: + QWidget::keyPressEvent(event); + break; + } +} + void QColorLuminancePicker::mouseMoveEvent(QMouseEvent *m) { if (m->buttons() == Qt::NoButton) { @@ -855,38 +757,53 @@ void QColorLuminancePicker::setCol(int h, int s , int v) repaint(); } -QPoint QColorPicker::colPt() +QPoint QColorPicker::colPt(int hue, int sat) { QRect r = contentsRect(); return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255); } -int QColorPicker::huePt(const QPoint &pt) +int QColorPicker::huePt(const QPoint &pt, const QSize &widgetSize) { - QRect r = contentsRect(); - return 360 - pt.x() * 360 / (r.width() - 1); + QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins(); + return std::clamp(360 - pt.x() * 360 / (r.width() - 1), 0, 359); } -int QColorPicker::satPt(const QPoint &pt) +int QColorPicker::satPt(const QPoint &pt, const QSize &widgetSize) { - QRect r = contentsRect(); - return 255 - pt.y() * 255 / (r.height() - 1); + QRect r = QRect(QPoint(0, 0), widgetSize) - contentsMargins(); + return std::clamp(255 - pt.y() * 255 / (r.height() - 1), 0, 255); } -void QColorPicker::setCol(const QPoint &pt) +void QColorPicker::setCol(const QPoint &pt, bool notify) { - setCol(huePt(pt), satPt(pt)); + if (pt == m_pos) + return; + + QRect r(m_pos, QSize(20, 20)); + m_pos.setX(std::clamp(pt.x(), 0, pix.width() - 1)); + m_pos.setY(std::clamp(pt.y(), 0, pix.height() - 1)); + r = r.united(QRect(m_pos, QSize(20, 20))); + r.translate(contentsRect().x() - 9, contentsRect().y() - 9); + // update(r); + repaint(r); + + if (notify) + emit newCol(huePt(m_pos), satPt(m_pos)); } QColorPicker::QColorPicker(QWidget* parent) : QFrame(parent) , crossVisible(true) { - hue = 0; sat = 0; - setCol(150, 255); - setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy(Qt::StrongFocus); setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed) ); + adjustSize(); + + pix = createColorsPixmap(); + + setCol(150, 255); } QColorPicker::~QColorPicker() @@ -910,15 +827,31 @@ void QColorPicker::setCol(int h, int s) { int nhue = qMin(qMax(0,h), 359); int nsat = qMin(qMax(0,s), 255); - if (nhue == hue && nsat == sat) + if (nhue == huePt(m_pos) && nsat == satPt(m_pos)) return; - QRect r(colPt(), QSize(20,20)); - hue = nhue; sat = nsat; - r = r.united(QRect(colPt(), QSize(20,20))); - r.translate(contentsRect().x()-9, contentsRect().y()-9); - // update(r); - repaint(r); + setCol(colPt(nhue, nsat), false); +} + +void QColorPicker::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Down: + setCol(m_pos + QPoint(0, 1)); + break; + case Qt::Key_Left: + setCol(m_pos + QPoint(-1, 0)); + break; + case Qt::Key_Right: + setCol(m_pos + QPoint(1, 0)); + break; + case Qt::Key_Up: + setCol(m_pos + QPoint(0, -1)); + break; + default: + QFrame::keyPressEvent(event); + break; + } } void QColorPicker::mouseMoveEvent(QMouseEvent *m) @@ -929,14 +862,12 @@ void QColorPicker::mouseMoveEvent(QMouseEvent *m) return; } setCol(p); - emit newCol(hue, sat); } void QColorPicker::mousePressEvent(QMouseEvent *m) { QPoint p = m->position().toPoint() - contentsRect().topLeft(); setCol(p); - emit newCol(hue, sat); } void QColorPicker::paintEvent(QPaintEvent* ) @@ -948,7 +879,7 @@ void QColorPicker::paintEvent(QPaintEvent* ) p.drawPixmap(r.topLeft(), pix); if (crossVisible) { - QPoint pt = colPt() + r.topLeft(); + QPoint pt = m_pos + r.topLeft(); p.setPen(Qt::black); p.fillRect(pt.x()-9, pt.y(), 20, 2, Qt::black); p.fillRect(pt.x(), pt.y()-9, 2, 20, Qt::black); @@ -959,6 +890,21 @@ void QColorPicker::resizeEvent(QResizeEvent *ev) { QFrame::resizeEvent(ev); + pix = createColorsPixmap(); + + const QSize &oldSize = ev->oldSize(); + if (!oldSize.isValid()) + return; + + // calculate hue/saturation based on previous widget size + // and update position accordingly + const int hue = huePt(m_pos, oldSize); + const int sat = satPt(m_pos, oldSize); + setCol(hue, sat); +} + +QPixmap QColorPicker::createColorsPixmap() +{ int w = width() - frameWidth() * 2; int h = height() - frameWidth() * 2; QImage img(w, h, QImage::Format_RGB32); @@ -976,10 +922,9 @@ void QColorPicker::resizeEvent(QResizeEvent *ev) ++x; } } - pix = QPixmap::fromImage(img); + return QPixmap::fromImage(img); } - class QColSpinBox : public QSpinBox { public: diff --git a/src/widgets/dialogs/qcolorwell_p.h b/src/widgets/dialogs/qcolorwell_p.h new file mode 100644 index 00000000000..31d69fabb13 --- /dev/null +++ b/src/widgets/dialogs/qcolorwell_p.h @@ -0,0 +1,142 @@ +// Copyright (C) 2025 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QCOLORWELL_P_H +#define QCOLORWELL_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qrect.h> +#include <QtWidgets/qwidget.h> + +QT_REQUIRE_CONFIG(colordialog); + +QT_BEGIN_NAMESPACE + +class QWellArray : public QWidget +{ + Q_OBJECT + Q_PROPERTY(int selectedColumn READ selectedColumn) + Q_PROPERTY(int selectedRow READ selectedRow) + +public: + QWellArray(int rows, int cols, QWidget *parent = nullptr); + ~QWellArray() { } + + int selectedColumn() const { return selCol; } + int selectedRow() const { return selRow; } + + virtual void setCurrent(int row, int col); + virtual void setSelected(int row, int col); + + QSize sizeHint() const override; + + inline int cellWidth() const { return cellw; } + + inline int cellHeight() const { return cellh; } + + inline int rowAt(int y) const { return y / cellh; } + + inline int columnAt(int x) const + { + if (isRightToLeft()) + return ncols - (x / cellw) - 1; + return x / cellw; + } + + inline int rowY(int row) const { return cellh * row; } + + inline int columnX(int column) const + { + if (isRightToLeft()) + return cellw * (ncols - column - 1); + return cellw * column; + } + + inline int numRows() const { return nrows; } + + inline int numCols() const { return ncols; } + + inline QRect cellRect() const { return QRect(0, 0, cellw, cellh); } + + inline QSize gridSize() const { return QSize(ncols * cellw, nrows * cellh); } + + QRect cellGeometry(int row, int column) + { + QRect r; + if (row >= 0 && row < nrows && column >= 0 && column < ncols) + r.setRect(columnX(column), rowY(row), cellw, cellh); + return r; + } + + inline void updateCell(int row, int column) { update(cellGeometry(row, column)); } + +signals: + void selected(int row, int col); + void currentChanged(int row, int col); + void colorChanged(int index, QRgb color); + +protected: + virtual void paintCell(QPainter *, int row, int col, const QRect &); + virtual void paintCellContents(QPainter *, int row, int col, const QRect &); + + void mousePressEvent(QMouseEvent *) override; + void mouseReleaseEvent(QMouseEvent *) override; + void keyPressEvent(QKeyEvent *) override; + void focusInEvent(QFocusEvent *) override; + void focusOutEvent(QFocusEvent *) override; + void paintEvent(QPaintEvent *) override; + +private: + Q_DISABLE_COPY(QWellArray) + + int nrows; + int ncols; + int cellw; + int cellh; + int curRow; + int curCol; + int selRow; + int selCol; +}; + +class QColorWell : public QWellArray +{ +public: + QColorWell(QWidget *parent, int r, int c, const QRgb *vals) + : QWellArray(r, c, parent), values(vals), mousePressed(false), oldCurrent(-1, -1) + { + setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + } + +protected: + void paintCellContents(QPainter *, int row, int col, const QRect &) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; +#if QT_CONFIG(draganddrop) + void dragEnterEvent(QDragEnterEvent *e) override; + void dragLeaveEvent(QDragLeaveEvent *e) override; + void dragMoveEvent(QDragMoveEvent *e) override; + void dropEvent(QDropEvent *e) override; +#endif + +private: + const QRgb *values; + bool mousePressed; + QPoint pressPos; + QPoint oldCurrent; +}; + +QT_END_NAMESPACE + +#endif // QCOLORWELL_P_H diff --git a/src/widgets/dialogs/qfiledialog.ui b/src/widgets/dialogs/qfiledialog.ui index f275e20c633..97f39fa6194 100644 --- a/src/widgets/dialogs/qfiledialog.ui +++ b/src/widgets/dialogs/qfiledialog.ui @@ -20,7 +20,10 @@ <item row="0" column="0"> <widget class="QLabel" name="lookInLabel"> <property name="text"> - <string>Look in:</string> + <string>&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/styles/qcommonstyle.cpp b/src/widgets/styles/qcommonstyle.cpp index c4b78539114..bb9f7ab27fc 100644 --- a/src/widgets/styles/qcommonstyle.cpp +++ b/src/widgets/styles/qcommonstyle.cpp @@ -5,15 +5,12 @@ #include "qcommonstyle.h" #include "qcommonstyle_p.h" -#include <qfile.h> #if QT_CONFIG(itemviews) #include <qabstractitemview.h> #endif #include <qapplication.h> #include <private/qguiapplication_p.h> #include <qpa/qplatformtheme.h> -#include <qbitmap.h> -#include <qcache.h> #if QT_CONFIG(dockwidget) #include <qdockwidget.h> #endif @@ -61,7 +58,6 @@ #endif #include <private/qcommonstylepixmaps_p.h> #include <private/qmath_p.h> -#include <qdebug.h> #include <qtextformat.h> #if QT_CONFIG(wizard) #include <qwizard.h> @@ -69,11 +65,6 @@ #if QT_CONFIG(filedialog) #include <qsidebar_p.h> #endif -#include <qfileinfo.h> -#include <qdir.h> -#if QT_CONFIG(settings) -#include <qsettings.h> -#endif #include <qvariant.h> #include <qpixmapcache.h> #if QT_CONFIG(animation) @@ -6199,17 +6190,17 @@ QPixmap QCommonStyle::generatedIconPixmap(QIcon::Mode iconMode, const QPixmap &p return QPixmap::fromImage(std::move(im)); } case QIcon::Selected: { - QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); QColor color = opt->palette.color(QPalette::Normal, QPalette::Highlight); color.setAlphaF(0.3f); - QPainter painter(&img); + QPixmap ret(pixmap); + QPainter painter(&ret); painter.setCompositionMode(QPainter::CompositionMode_SourceAtop); - painter.fillRect(0, 0, img.width(), img.height(), color); + painter.fillRect(0, 0, pixmap.width(), pixmap.height(), color); painter.end(); - return QPixmap::fromImage(std::move(img)); } + return ret; + } case QIcon::Active: - return pixmap; - default: + case QIcon::Normal: break; } return pixmap; diff --git a/src/widgets/styles/qwindowsstyle.cpp b/src/widgets/styles/qwindowsstyle.cpp index 9b06822c218..b9143a59ee7 100644 --- a/src/widgets/styles/qwindowsstyle.cpp +++ b/src/widgets/styles/qwindowsstyle.cpp @@ -851,6 +851,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, } #ifndef QT_NO_FRAME case PE_Frame: + if (w && w->inherits("QComboBoxPrivateContainer")){ + QStyleOption copy = *opt; + copy.state |= State_Raised; + proxy()->drawPrimitive(PE_PanelMenu, ©, p, w); + break; + } case PE_FrameMenu: if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(opt)) { if (frame->lineWidth == 2 || pe == PE_Frame) { @@ -873,6 +879,7 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, } } else { QPalette popupPal = opt->palette; + p->drawRect(opt->rect); popupPal.setColor(QPalette::Light, opt->palette.window().color()); popupPal.setColor(QPalette::Midlight, opt->palette.light().color()); qDrawWinPanel(p, opt->rect, popupPal, opt->state & State_Sunken); @@ -899,6 +906,12 @@ void QWindowsStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, p->drawRect(opt->rect); } break; } + case PE_PanelMenu: + if (w && w->inherits("QComboBoxPrivateContainer")){ + const QBrush menuBackground = opt->palette.base().color(); + QColor borderColor = opt->palette.window().color(); + qDrawPlainRect(p, opt->rect, borderColor, 1, &menuBackground); + } case PE_FrameWindow: { QPalette popupPal = opt->palette; popupPal.setColor(QPalette::Light, opt->palette.window().color()); diff --git a/src/widgets/widgets/qlineedit_p.cpp b/src/widgets/widgets/qlineedit_p.cpp index 55e6137dba9..ee80cca649c 100644 --- a/src/widgets/widgets/qlineedit_p.cpp +++ b/src/widgets/widgets/qlineedit_p.cpp @@ -353,17 +353,16 @@ QLineEditPrivate *QLineEditIconButton::lineEditPrivate() const void QLineEditIconButton::paintEvent(QPaintEvent *) { QPainter painter(this); - QIcon::Mode state = QIcon::Disabled; + QIcon::Mode mode = QIcon::Disabled; if (isEnabled()) - state = isDown() ? QIcon::Active : QIcon::Normal; + mode = isDown() ? QIcon::Active : QIcon::Normal; const QLineEditPrivate *lep = lineEditPrivate(); const int iconWidth = lep ? lep->sideWidgetParameters().iconSize : 16; const QSize iconSize(iconWidth, iconWidth); - const QPixmap iconPixmap = icon().pixmap(iconSize, devicePixelRatio(), state, QIcon::Off); QRect pixmapRect = QRect(QPoint(0, 0), iconSize); pixmapRect.moveCenter(rect().center()); painter.setOpacity(m_opacity); - painter.drawPixmap(pixmapRect, iconPixmap); + icon().paint(&painter, pixmapRect, Qt::AlignCenter, mode, QIcon::Off); } void QLineEditIconButton::actionEvent(QActionEvent *e) diff --git a/src/widgets/widgets/qtabbar.cpp b/src/widgets/widgets/qtabbar.cpp index 0b562a47879..8e6f497d7f5 100644 --- a/src/widgets/widgets/qtabbar.cpp +++ b/src/widgets/widgets/qtabbar.cpp @@ -1003,7 +1003,7 @@ int QTabBar::insertTab(int index, const QIcon& icon, const QString &text) ++tab->lastTab; } - if (tabAt(d->mousePosition) == index) { + if (isVisible() && tabAt(d->mousePosition) == index) { d->hoverIndex = index; d->hoverRect = tabRect(index); } |
