diff options
Diffstat (limited to 'src/gui/util')
-rw-r--r-- | src/gui/util/qastchandler.cpp | 8 | ||||
-rw-r--r-- | src/gui/util/qdesktopservices.cpp | 16 | ||||
-rw-r--r-- | src/gui/util/qedidparser.cpp | 21 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture.cpp | 123 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture_p.h | 55 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapture_p_p.h | 67 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturemetal.mm | 169 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturemetal_p_p.h | 57 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturerenderdoc.cpp | 310 | ||||
-rw-r--r-- | src/gui/util/qgraphicsframecapturerenderdoc_p_p.h | 53 | ||||
-rw-r--r-- | src/gui/util/qgridlayoutengine.cpp | 98 | ||||
-rw-r--r-- | src/gui/util/qgridlayoutengine_p.h | 11 | ||||
-rw-r--r-- | src/gui/util/qktxhandler.cpp | 231 | ||||
-rw-r--r-- | src/gui/util/qktxhandler_p.h | 4 | ||||
-rw-r--r-- | src/gui/util/qpkmhandler.cpp | 6 | ||||
-rw-r--r-- | src/gui/util/qtexturefiledata.cpp | 70 | ||||
-rw-r--r-- | src/gui/util/qtexturefiledata_p.h | 6 | ||||
-rw-r--r-- | src/gui/util/qundostack.cpp | 12 | ||||
-rw-r--r-- | src/gui/util/qvalidator.cpp | 153 |
19 files changed, 1290 insertions, 180 deletions
diff --git a/src/gui/util/qastchandler.cpp b/src/gui/util/qastchandler.cpp index 6c05e0e248..ec8b92f557 100644 --- a/src/gui/util/qastchandler.cpp +++ b/src/gui/util/qastchandler.cpp @@ -37,7 +37,7 @@ quint32 QAstcHandler::astcGLFormat(quint8 xBlockDim, quint8 yBlockDim) const static const quint32 glFormatRGBABase = 0x93B0; // GL_COMPRESSED_RGBA_ASTC_4x4_KHR static const quint32 glFormatSRGBBase = 0x93D0; // GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR - static QSize dims[14] = { + Q_CONSTINIT static QSize dims[14] = { { 4, 4 }, // GL_COMPRESSED_xxx_ASTC_4x4_KHR { 5, 4 }, // GL_COMPRESSED_xxx_ASTC_5x4_KHR { 5, 5 }, // GL_COMPRESSED_xxx_ASTC_5x5_KHR @@ -109,9 +109,9 @@ QTextureFileData QAstcHandler::read() int zBlocks = (zSz + header->blockDimZ - 1) / header->blockDimZ; int byteCount = 0; - bool oob = mul_overflow(xBlocks, yBlocks, &byteCount) - || mul_overflow(byteCount, zBlocks, &byteCount) - || mul_overflow(byteCount, 16, &byteCount); + bool oob = qMulOverflow(xBlocks, yBlocks, &byteCount) + || qMulOverflow(byteCount, zBlocks, &byteCount) + || qMulOverflow(byteCount, 16, &byteCount); res.setDataOffset(sizeof(AstcHeader)); diff --git a/src/gui/util/qdesktopservices.cpp b/src/gui/util/qdesktopservices.cpp index ae452885fc..379d18dd60 100644 --- a/src/gui/util/qdesktopservices.cpp +++ b/src/gui/util/qdesktopservices.cpp @@ -162,6 +162,13 @@ void QOpenUrlHandlerRegistry::handlerDestroyed(QObject *handler) \snippet code/src_gui_util_qdesktopservices.cpp 3 + \note For Android Nougat (SDK 24) and above, URLs with a \c file scheme + are opened using \l {Android: FileProvider}{FileProvider} which tries to obtain + a shareable \c content scheme URI first. For that reason, Qt for Android defines + a file provider with the authority \c ${applicationId}.qtprovider, with \c applicationId + being the app's package name to avoid name conflicts. For more information, also see + \l {Android: Setting up file sharing}{Setting up file sharing}. + \sa setUrlHandler() */ bool QDesktopServices::openUrl(const QUrl &url) @@ -231,10 +238,11 @@ bool QDesktopServices::openUrl(const QUrl &url) the destruction of the handler object does not overlap with concurrent invocations of openUrl() using it. - \section1 iOS + \section1 iOS and \macos - To use this function for receiving data from other apps on iOS you also need to - add the custom scheme to the \c CFBundleURLSchemes list in your Info.plist file: + To use this function for receiving data from other apps on iOS/\macos + you also need to add the custom scheme to the \c CFBundleURLSchemes + list in your Info.plist file: \snippet code/src_gui_util_qdesktopservices.cpp 4 @@ -249,7 +257,7 @@ bool QDesktopServices::openUrl(const QUrl &url) \snippet code/src_gui_util_qdesktopservices.cpp 7 - iOS will search for /.well-known/apple-app-site-association on your domain, + iOS/\macos will search for /.well-known/apple-app-site-association on your domain, when the application is installed. If you want to listen to \c{https://your.domain.com/help?topic=ABCDEF} you need to provide the following content there: diff --git a/src/gui/util/qedidparser.cpp b/src/gui/util/qedidparser.cpp index b989503ec2..4dae151e6a 100644 --- a/src/gui/util/qedidparser.cpp +++ b/src/gui/util/qedidparser.cpp @@ -12,6 +12,7 @@ #define EDID_DESCRIPTOR_PRODUCT_NAME 0xfc #define EDID_DESCRIPTOR_SERIAL_NUMBER 0xff +#define EDID_DATA_BLOCK_COUNT 4 #define EDID_OFFSET_DATA_BLOCKS 0x36 #define EDID_OFFSET_LAST_BLOCK 0x6c #define EDID_OFFSET_PNP_ID 0x08 @@ -71,7 +72,7 @@ static QString lookupVendorIdInSystemDatabase(QByteArrayView id) bool QEdidParser::parse(const QByteArray &blob) { const quint8 *data = reinterpret_cast<const quint8 *>(blob.constData()); - const size_t length = blob.length(); + const size_t length = blob.size(); // Verify header if (length < 128) @@ -104,7 +105,7 @@ bool QEdidParser::parse(const QByteArray &blob) serialNumber = QString(); // Parse EDID data - for (int i = 0; i < 5; ++i) { + for (int i = 0; i < EDID_DATA_BLOCK_COUNT; ++i) { const uint offset = EDID_OFFSET_DATA_BLOCKS + i * 18; if (data[offset] != 0 || data[offset + 1] != 0 || data[offset + 2] != 0) @@ -234,16 +235,22 @@ QString QEdidParser::parseEdidString(const quint8 *data) { QByteArray buffer(reinterpret_cast<const char *>(data), 13); - // Erase carriage return and line feed - buffer = buffer.replace('\r', '\0').replace('\n', '\0'); - - // Replace non-printable characters with dash for (int i = 0; i < buffer.size(); ++i) { + // If there are less than 13 characters in the string, the string + // is terminated with the ASCII code ‘0Ah’ (line feed) and padded + // with ASCII code ‘20h’ (space). See EDID 1.4, sections 3.10.3.1, + // 3.10.3.2, and 3.10.3.4. + if (buffer[i] == '\n') { + buffer.truncate(i); + break; + } + + // Replace non-printable characters with dash if (buffer[i] < '\040' || buffer[i] > '\176') buffer[i] = '-'; } - return QString::fromLatin1(buffer.trimmed()); + return QString::fromLatin1(buffer); } QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapture.cpp b/src/gui/util/qgraphicsframecapture.cpp new file mode 100644 index 0000000000..e49fb83008 --- /dev/null +++ b/src/gui/util/qgraphicsframecapture.cpp @@ -0,0 +1,123 @@ +// Copyright (C) 2023 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 "qgraphicsframecapture_p.h" +#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library) +#include "qgraphicsframecapturerenderdoc_p_p.h" +#elif QT_CONFIG(metal) +#include "qgraphicsframecapturemetal_p_p.h" +#else +#include "qgraphicsframecapture_p_p.h" +#endif + +#include <QtCore/qstandardpaths.h> +#include <QtCore/qcoreapplication.h> + +QT_BEGIN_NAMESPACE + +QGraphicsFrameCapturePrivate::QGraphicsFrameCapturePrivate() + : m_capturePath(QStandardPaths::writableLocation(QStandardPaths::TempLocation) + + QStringLiteral("/") + QCoreApplication::applicationName() + + QStringLiteral("/captures")), + m_capturePrefix(QCoreApplication::applicationName()) +{ + +} + +QGraphicsFrameCapture::QGraphicsFrameCapture() +{ +#if (defined (Q_OS_WIN) || defined(Q_OS_LINUX)) && QT_CONFIG(library) + d.reset(new QGraphicsFrameCaptureRenderDoc); +#elif QT_CONFIG(metal) + d.reset(new QGraphicsFrameCaptureMetal); +#endif +} + +QGraphicsFrameCapture::~QGraphicsFrameCapture() +{ + +} + +void QGraphicsFrameCapture::setRhi(QRhi *rhi) +{ + if (!d.isNull()) + d->setRhi(rhi); +} + +void QGraphicsFrameCapture::startCaptureFrame() +{ + if (!d.isNull()) + d->startCaptureFrame(); +} + +void QGraphicsFrameCapture::endCaptureFrame() +{ + if (!d.isNull()) + d->endCaptureFrame(); +} + +QString QGraphicsFrameCapture::capturePath() const +{ + if (!d.isNull()) + return d->capturePath(); + return QString(); +} + +void QGraphicsFrameCapture::setCapturePath(const QString &path) +{ + if (!d.isNull()) + d->setCapturePath(path); +} + +QString QGraphicsFrameCapture::capturePrefix() const +{ + if (!d.isNull()) + return d->capturePrefix(); + return QString(); +} + +void QGraphicsFrameCapture::setCapturePrefix(const QString &prefix) +{ + if (!d.isNull()) + d->setCapturePrefix(prefix); +} + +QString QGraphicsFrameCapture::capturedFileName() +{ + if (!d.isNull()) + return d->capturedFileName(); + + return QString(); +} + +QStringList QGraphicsFrameCapture::capturedFilesNames() +{ + if (!d.isNull()) + return d->capturedFilesNames(); + + return QStringList(); +} + +bool QGraphicsFrameCapture::isLoaded() const +{ + if (!d.isNull()) + return d->initialized(); + + return false; +} + +bool QGraphicsFrameCapture::isCapturing() const +{ + if (!d.isNull()) + return d->isCapturing(); + + return false; +} + +void QGraphicsFrameCapture::openCapture() const +{ + if (!d.isNull()) + d->openCapture(); +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapture_p.h b/src/gui/util/qgraphicsframecapture_p.h new file mode 100644 index 0000000000..fef37d2869 --- /dev/null +++ b/src/gui/util/qgraphicsframecapture_p.h @@ -0,0 +1,55 @@ +// Copyright (C) 2023 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 QGRAPHICSFRAMECAPTURE_P_H +#define QGRAPHICSFRAMECAPTURE_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/qscopedpointer.h> +#include <QtGui/qtguiglobal.h> + +QT_BEGIN_NAMESPACE + +class QGraphicsFrameCapturePrivate; +class QRhi; + +class Q_GUI_EXPORT QGraphicsFrameCapture +{ +public: + QGraphicsFrameCapture(); + ~QGraphicsFrameCapture(); + + void setRhi(QRhi *rhi); + void startCaptureFrame(); + void endCaptureFrame(); + + QString capturePath() const; + void setCapturePath(const QString &path); + + QString capturePrefix() const; + void setCapturePrefix(const QString &prefix); + + QString capturedFileName(); + QStringList capturedFilesNames(); + + bool isLoaded() const; + bool isCapturing() const; + void openCapture() const; + +private: + QScopedPointer<QGraphicsFrameCapturePrivate> d; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURE_P_H diff --git a/src/gui/util/qgraphicsframecapture_p_p.h b/src/gui/util/qgraphicsframecapture_p_p.h new file mode 100644 index 0000000000..f8dbd2ca1d --- /dev/null +++ b/src/gui/util/qgraphicsframecapture_p_p.h @@ -0,0 +1,67 @@ +// Copyright (C) 2023 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 QGRAPHICSFRAMECAPTURE_P_P_H +#define QGRAPHICSFRAMECAPTURE_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qnamespace.h> +#include <QtCore/qstring.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qstringlist.h> + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(lcGraphicsFrameCapture) + +class QRhi; +struct QRhiNativeHandles; + +class QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCapturePrivate() ; + virtual ~QGraphicsFrameCapturePrivate() = default; + + virtual void setRhi(QRhi *rhi) = 0; + virtual void startCaptureFrame() = 0; + virtual void endCaptureFrame() = 0; + + QString capturePath() const { return m_capturePath; }; + virtual void setCapturePath(const QString &path) { m_capturePath = path; } + + QString capturePrefix() const { return m_capturePrefix; } + virtual void setCapturePrefix(const QString &prefix) { m_capturePrefix = prefix; } + + virtual QString capturedFileName() const + { + return !m_capturedFilesNames.isEmpty() ? m_capturedFilesNames.last() : QString(); + } + virtual QStringList capturedFilesNames() const { return m_capturedFilesNames; } + + virtual bool initialized() const = 0; + virtual bool isCapturing() const = 0; + virtual void openCapture() = 0; + +protected: + QRhi *m_rhi = nullptr; + QRhiNativeHandles *m_rhiHandles = nullptr; + void *m_nativeHandle = nullptr; + QString m_capturePath; + QString m_capturePrefix; + QStringList m_capturedFilesNames; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURE_P_P_H diff --git a/src/gui/util/qgraphicsframecapturemetal.mm b/src/gui/util/qgraphicsframecapturemetal.mm new file mode 100644 index 0000000000..b0ff0bab2b --- /dev/null +++ b/src/gui/util/qgraphicsframecapturemetal.mm @@ -0,0 +1,169 @@ +// Copyright (C) 2023 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 "qgraphicsframecapturemetal_p_p.h" +#include <QtCore/qurl.h> +#include "Metal/Metal.h" +#include "qglobal.h" +#include <QtGui/rhi/qrhi.h> +#include <QtGui/rhi/qrhi_platform.h> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture") + +#if __has_feature(objc_arc) +#error ARC not supported +#endif + +uint QGraphicsFrameCaptureMetal::frameNumber = 0; + +QGraphicsFrameCaptureMetal::QGraphicsFrameCaptureMetal() +{ + qputenv("METAL_CAPTURE_ENABLED", QByteArrayLiteral("1")); + + m_captureDescriptor = [MTLCaptureDescriptor new]; +} + +QGraphicsFrameCaptureMetal::~QGraphicsFrameCaptureMetal() +{ +#if defined(Q_OS_MACOS) && QT_CONFIG(process) + if (m_process) { + m_process->terminate(); + delete m_process; + } +#endif + [m_captureDescriptor release]; +} + +void QGraphicsFrameCaptureMetal::setRhi(QRhi *rhi) +{ + if (!rhi) + return; + + QRhi::Implementation backend = rhi->backend(); + const QRhiNativeHandles *nh = rhi->nativeHandles(); + + switch (backend) { + case QRhi::Implementation::Metal: { + const QRhiMetalNativeHandles *mtlnh = static_cast<const QRhiMetalNativeHandles *>(nh); + if (mtlnh->cmdQueue) { + m_captureDescriptor.captureObject = mtlnh->cmdQueue; + } else if (mtlnh->dev) { + m_captureDescriptor.captureObject = mtlnh->dev; + } else { + qCWarning(lcGraphicsFrameCapture) << "No valid Metal Device or Metal Command Queue found"; + m_initialized = false; + return; + } + break; + } + default: { + qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided. MTLCaptureManager works only with Metal API"; + m_initialized = false; + return; + break; + } + } + + if (!m_captureManager) { + m_captureManager = MTLCaptureManager.sharedCaptureManager; + bool supportDocs = [m_captureManager supportsDestination:MTLCaptureDestinationGPUTraceDocument]; + if (supportDocs) { + m_captureDescriptor.destination = MTLCaptureDestinationGPUTraceDocument; + m_initialized = true; + } + } +} + +void QGraphicsFrameCaptureMetal::startCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Starting capturing can not be done."; + return; + } + + if (isCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress," + "will not initiate another one until QGraphicsFrameCapture::endCaptureFrame is called."; + return; + } + + updateCaptureFileName(); + NSError *error; + if (![m_captureManager startCaptureWithDescriptor:m_captureDescriptor error:&error]) { + QString errorMsg = QString::fromNSString(error.localizedDescription); + qCWarning(lcGraphicsFrameCapture, "Failed to start capture : %s", qPrintable(errorMsg)); + } +} + +void QGraphicsFrameCaptureMetal::endCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. End capturing can not be done."; + return; + } + + if (!isCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done" + " without a call to QGraphicsFrameCapture::startCaptureFrame"; + return; + } + + [m_captureManager stopCapture]; + m_capturedFilesNames.append(QString::fromNSString(m_traceURL.path)); + frameNumber++; +} + +bool QGraphicsFrameCaptureMetal::initialized() const +{ + return m_initialized; +} + +bool QGraphicsFrameCaptureMetal::isCapturing() const +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not query if capturing is in progress or not."; + return false; + } + + return [m_captureManager isCapturing]; +} + +void QGraphicsFrameCaptureMetal::openCapture() +{ +#if defined(Q_OS_MACOS) +#if !QT_CONFIG(process) + qFatal("QGraphicsFrameCapture requires QProcess on macOS"); +#else + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "Capturing on Metal was not initialized. Can not open XCode with a valid capture."; + return; + } + + if (!m_process) { + m_process = new QProcess(); + m_process->setProgram(QStringLiteral("xed")); + QStringList args; + args.append(QUrl::fromNSURL(m_traceURL).toLocalFile()); + m_process->setArguments(args); + } + + m_process->kill(); + m_process->start(); +#endif +#endif +} + +void QGraphicsFrameCaptureMetal::updateCaptureFileName() +{ + m_traceURL = QUrl::fromLocalFile(m_capturePath + "/" + m_capturePrefix + "_" + QString::number(frameNumber) + ".gputrace").toNSURL(); + // We need to remove the trace file if it already existed else MTLCaptureManager + // will fail to. + if ([NSFileManager.defaultManager fileExistsAtPath:m_traceURL.path]) + [NSFileManager.defaultManager removeItemAtURL:m_traceURL error:nil]; + + m_captureDescriptor.outputURL = m_traceURL; +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapturemetal_p_p.h b/src/gui/util/qgraphicsframecapturemetal_p_p.h new file mode 100644 index 0000000000..d703949de2 --- /dev/null +++ b/src/gui/util/qgraphicsframecapturemetal_p_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 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 QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H +#define QGRAPHICSFRAMEDECAPTUREMETAL_P_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 "qgraphicsframecapture_p_p.h" +#include <QtCore/qmutex.h> +#ifdef Q_OS_MACOS +#include <QtCore/qprocess.h> +#endif + +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureManager); +Q_FORWARD_DECLARE_OBJC_CLASS(MTLCaptureDescriptor); +Q_FORWARD_DECLARE_OBJC_CLASS(NSURL); + +QT_BEGIN_NAMESPACE + +class QGraphicsFrameCaptureMetal : public QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCaptureMetal(); + ~QGraphicsFrameCaptureMetal(); + + void setRhi(QRhi *rhi) override; + void startCaptureFrame() override; + void endCaptureFrame() override; + bool initialized() const override; + bool isCapturing() const override; + void openCapture() override; + +private: + void updateCaptureFileName(); +#if defined(Q_OS_MACOS) && QT_CONFIG(process) + QProcess *m_process = nullptr; +#endif + MTLCaptureManager *m_captureManager = nullptr; + MTLCaptureDescriptor *m_captureDescriptor = nullptr; + NSURL *m_traceURL = nullptr; + bool m_initialized = false; + static uint frameNumber; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMEDECAPTUREMETAL_P_P_H diff --git a/src/gui/util/qgraphicsframecapturerenderdoc.cpp b/src/gui/util/qgraphicsframecapturerenderdoc.cpp new file mode 100644 index 0000000000..88ba9d839f --- /dev/null +++ b/src/gui/util/qgraphicsframecapturerenderdoc.cpp @@ -0,0 +1,310 @@ +// Copyright (C) 2023 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 "qgraphicsframecapturerenderdoc_p_p.h" +#include <QtCore/qcoreapplication.h> +#include <QtCore/qfile.h> +#include <QtCore/qlibrary.h> +#include <QtCore/qmutex.h> +#include "QtGui/rhi/qrhi.h" +#include "QtGui/rhi/qrhi_platform.h" +#include "QtGui/qopenglcontext.h" + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcGraphicsFrameCapture, "qt.gui.graphicsframecapture") + +RENDERDOC_API_1_6_0 *QGraphicsFrameCaptureRenderDoc::s_rdocApi = nullptr; +#if QT_CONFIG(thread) +QBasicMutex QGraphicsFrameCaptureRenderDoc::s_frameCaptureMutex; +#endif + +#if QT_CONFIG(opengl) +static void *glNativeContext(QOpenGLContext *context) { + void *nctx = nullptr; + if (context != nullptr && context->isValid()) { +#ifdef Q_OS_WIN + nctx = context->nativeInterface<QNativeInterface::QWGLContext>()->nativeContext(); +#endif + +#ifdef Q_OS_LINUX +#if QT_CONFIG(egl) + QNativeInterface::QEGLContext *eglItf = context->nativeInterface<QNativeInterface::QEGLContext>(); + if (eglItf) + nctx = eglItf->nativeContext(); +#endif + +#if QT_CONFIG(xcb_glx_plugin) + QNativeInterface::QGLXContext *glxItf = context->nativeInterface<QNativeInterface::QGLXContext>(); + if (glxItf) + nctx = glxItf->nativeContext(); +#endif +#endif + +#if QT_CONFIG(metal) + nctx = context->nativeInterface<QNativeInterface::QCocoaGLContext>()->nativeContext(); +#endif + } + return nctx; +} +#endif // QT_CONFIG(opengl) + +/*! + \class QGraphicsFrameCaptureRenderDoc + \internal + \brief The QGraphicsFrameCaptureRenderDoc class provides a way to capture a record of draw calls + for different graphics APIs. + \since 6.6 + \inmodule QtGui + + For applications that render using graphics APIs like Vulkan or OpenGL, it would be + convenient to have a way to check the draw calls done by the application. Specially + for applications that make a large amount of draw calls and the output is different + from what is expected. + + This class acts as a wrapper over \l {https://renderdoc.org/}{RenderDoc} that allows + applications to capture a rendered frame either programmatically, by clicking a key + on the keyboard or both. The captured frame could be viewed later using RenderDoc GUI. + + Read the \l {https://renderdoc.org/docs/index.html} {RenderDoc Documentation} + for more information. + + \section1 API device handle + + The functions that capture a frame like QGraphicsFrameCaptureRenderDoc::startCaptureFrame takes a device + pointer as argument. This pointer is unique for each graphics API and is associated + with the window that will be captured. This pointer has a default value of \c nullptr. + If no value is passed to the function the underlying API will try to find the device to + use, but it is not guaranteed specially in a multi-window applications. + + For OpenGL, the pointer should be the OpenGL context on the platform OpenGL is being + used. For example, on Windows it should be \c HGLRC. + + For Vulkan, the pointer should point to the dispatch table within the \c VkInstance. +*/ + + + +/*! + Creates a new object of this class. The constructor will load RenderDoc library + from the default path. + + Only one instance of RenderDoc library is loaded at runtime which means creating + several instances of this class will not affect the RenderDoc initialization. +*/ + +QGraphicsFrameCaptureRenderDoc::QGraphicsFrameCaptureRenderDoc() + : m_nativeHandlesSet(false) +{ + if (!s_rdocApi) + init(); +} + +void QGraphicsFrameCaptureRenderDoc::setRhi(QRhi *rhi) +{ + if (!rhi) + return; + + QRhi::Implementation backend = rhi->backend(); + const QRhiNativeHandles *nh = rhi->nativeHandles(); + + switch (backend) { + case QRhi::Implementation::D3D11: { +#ifdef Q_OS_WIN + const QRhiD3D11NativeHandles *d3d11nh = static_cast<const QRhiD3D11NativeHandles *>(nh); + m_nativeHandle = d3d11nh->dev; + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D11. Check platform support"; + break; + } + case QRhi::Implementation::D3D12: { +#ifdef Q_OS_WIN + const QRhiD3D12NativeHandles *d3d12nh = static_cast<const QRhiD3D12NativeHandles *>(nh); + m_nativeHandle = d3d12nh->dev; + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for D3D12. Check platform support"; + break; + } + case QRhi::Implementation::Vulkan: { +#if QT_CONFIG(vulkan) + const QRhiVulkanNativeHandles *vknh = static_cast<const QRhiVulkanNativeHandles *>(nh); + m_nativeHandle = RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(vknh->inst->vkInstance()); + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for Vulkan. Check platform support"; + break; + } + case QRhi::Implementation::OpenGLES2: { +#ifndef QT_NO_OPENGL + const QRhiGles2NativeHandles *glnh = static_cast<const QRhiGles2NativeHandles *>(nh); + m_nativeHandle = glNativeContext(glnh->context); + if (m_nativeHandle) + break; +#endif + qCWarning(lcGraphicsFrameCapture) << "Could not find valid handles for OpenGL. Check platform support"; + break; + } + case QRhi::Implementation::Metal: + case QRhi::Implementation::Null: + qCWarning(lcGraphicsFrameCapture) << "Invalid handles were provided." + " Metal and Null backends are not supported with RenderDoc"; + break; + } + + if (m_nativeHandle) + m_nativeHandlesSet = true; +} + +/*! + Starts a frame capture using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi + \a device. This function must be called before QGraphicsFrameCaptureRenderDoc::endCaptureFrame. + \sa {API device handle} +*/ +void QGraphicsFrameCaptureRenderDoc::startCaptureFrame() +{ + + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Starting capturing can not be done."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (s_rdocApi->IsFrameCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A frame capture is already in progress, " + "will not initiate another one until" + " QGraphicsFrameCapture::endCaptureFrame is called."; + return; + } + + qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to start."; + updateCapturePathAndTemplate(); + s_rdocApi->StartFrameCapture(m_nativeHandle, nullptr); +} + +/*! + Ends a frame capture started by a call to QGraphicsFrameCaptureRenderDoc::startCaptureFrame + using the set native handles provided through QGraphicsFrameCaptureRenderDoc::setRhi. + This function must be called after QGraphicsFrameCaptureRenderDoc::startCaptureFrame. + Otherwise, a warning message will be printend and nothing will happen. + \sa {API device handle} +*/ +void QGraphicsFrameCaptureRenderDoc::endCaptureFrame() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " End capturing can not be done."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (!s_rdocApi->IsFrameCapturing()) { + qCWarning(lcGraphicsFrameCapture) << "A call to QGraphicsFrameCapture::endCaptureFrame can not be done" + " without a call to QGraphicsFrameCapture::startCaptureFrame"; + return; + } + + qCInfo(lcGraphicsFrameCapture) << "A frame capture is going to end."; + uint32_t result = s_rdocApi->EndFrameCapture(m_nativeHandle, nullptr); + + if (result) { + uint32_t count = s_rdocApi->GetNumCaptures(); + uint32_t pathLength = 0; + s_rdocApi->GetCapture(count - 1, nullptr, &pathLength, nullptr); + if (pathLength > 0) { + QVarLengthArray<char> name(pathLength, 0); + s_rdocApi->GetCapture(count - 1, name.data(), &pathLength, nullptr); + m_capturedFilesNames.append(QString::fromUtf8(name.data(), -1)); + } + } +} + +void QGraphicsFrameCaptureRenderDoc::updateCapturePathAndTemplate() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Updating save location can not be done."; + return; + } + + + QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix; + s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData()); +} + +/*! + Returns true if the API is loaded and can capture frames or not. +*/ +bool QGraphicsFrameCaptureRenderDoc::initialized() const +{ + return s_rdocApi && m_nativeHandlesSet; +} + +bool QGraphicsFrameCaptureRenderDoc::isCapturing() const +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Can not query if capturing is in progress or not."; + return false; + } + + return s_rdocApi->IsFrameCapturing(); +} + +void QGraphicsFrameCaptureRenderDoc::openCapture() +{ + if (!initialized()) { + qCWarning(lcGraphicsFrameCapture) << "RenderDoc was not initialized." + " Can not open RenderDoc UI tool."; + return; + } + +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + if (s_rdocApi->IsTargetControlConnected()) + s_rdocApi->ShowReplayUI(); + else + s_rdocApi->LaunchReplayUI(1, nullptr); +} + +void QGraphicsFrameCaptureRenderDoc::init() +{ +#if QT_CONFIG(thread) + // There is a single instance of RenderDoc library and it needs mutex for multithreading access. + QMutexLocker locker(&s_frameCaptureMutex); +#endif + + QLibrary renderDocLib(QStringLiteral("renderdoc")); + pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI) renderDocLib.resolve("RENDERDOC_GetAPI"); + if (!renderDocLib.isLoaded() || (RENDERDOC_GetAPI == nullptr)) { + qCWarning(lcGraphicsFrameCapture) << renderDocLib.errorString().toLatin1(); + return; + } + + int ret = RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_6_0, static_cast<void **>(static_cast<void *>(&s_rdocApi))); + + if (ret == 0) { + qCWarning(lcGraphicsFrameCapture) << "The requested RenderDoc API is invalid or not supported"; + return; + } + + s_rdocApi->MaskOverlayBits(RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None, + RENDERDOC_OverlayBits::eRENDERDOC_Overlay_None); + s_rdocApi->SetCaptureKeys(nullptr, 0); + s_rdocApi->SetFocusToggleKeys(nullptr, 0); + + QString rdocFilePathTemplate = m_capturePath + QStringLiteral("/") + m_capturePrefix; + s_rdocApi->SetCaptureFilePathTemplate(rdocFilePathTemplate.toUtf8().constData()); +} + +QT_END_NAMESPACE diff --git a/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h new file mode 100644 index 0000000000..c0d92ea733 --- /dev/null +++ b/src/gui/util/qgraphicsframecapturerenderdoc_p_p.h @@ -0,0 +1,53 @@ +// Copyright (C) 2023 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 QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H +#define QGRAPHICSFRAMECAPTURERENDERDOC_P_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 <renderdoc_app.h> +#include "qgraphicsframecapture_p_p.h" + +QT_BEGIN_NAMESPACE + +#if QT_CONFIG(thread) +class QBasicMutex; +#endif + +class QGraphicsFrameCaptureRenderDoc : public QGraphicsFrameCapturePrivate +{ +public: + QGraphicsFrameCaptureRenderDoc(); + ~QGraphicsFrameCaptureRenderDoc() = default; + + void setRhi(QRhi *rhi) override; + void startCaptureFrame() override; + void endCaptureFrame() override; + bool initialized() const override; + bool isCapturing() const override; + void openCapture() override; + +private: + void init(); + void updateCapturePathAndTemplate(); + static RENDERDOC_API_1_5_0 *s_rdocApi; +#if QT_CONFIG(thread) + static QBasicMutex s_frameCaptureMutex; +#endif + bool m_nativeHandlesSet; +}; + +QT_END_NAMESPACE + +#endif // QGRAPHICSFRAMECAPTURERENDERDOC_P_P_H diff --git a/src/gui/util/qgridlayoutengine.cpp b/src/gui/util/qgridlayoutengine.cpp index 25c02cba51..07981e8388 100644 --- a/src/gui/util/qgridlayoutengine.cpp +++ b/src/gui/util/qgridlayoutengine.cpp @@ -13,10 +13,12 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +#define LAYOUTITEMSIZE_MAX (1 << 24) + template<typename T> static void insertOrRemoveItems(QList<T> &items, int index, int delta) { - int count = items.count(); + int count = items.size(); if (index < count) { if (delta > 0) { items.insert(index, delta, T()); @@ -194,7 +196,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz sumAvailable = targetSize - totalBox.q_minimumSize; if (sumAvailable > 0.0) { - qreal sumDesired = totalBox.q_preferredSize - totalBox.q_minimumSize; + const qreal totalBox_preferredSize = qMin(totalBox.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX)); + qreal sumDesired = totalBox_preferredSize - totalBox.q_minimumSize; for (int i = 0; i < n; ++i) { if (ignore.testBit(start + i)) { @@ -203,7 +206,8 @@ void QGridLayoutRowData::calculateGeometries(int start, int end, qreal targetSiz } const QGridLayoutBox &box = boxes.at(start + i); - qreal desired = box.q_preferredSize - box.q_minimumSize; + const qreal box_preferredSize = qMin(box.q_preferredSize, qreal(LAYOUTITEMSIZE_MAX)); + qreal desired = box_preferredSize - box.q_minimumSize; factors[i] = growthFactorBelowPreferredSize(desired, sumAvailable, sumDesired); sumFactors += factors[i]; } @@ -716,7 +720,7 @@ void QGridLayoutItem::dump(int indent) const if (q_alignment != 0) qDebug("%*s Alignment: %x", indent, "", uint(q_alignment)); qDebug("%*s Horizontal size policy: %x Vertical size policy: %x", - indent, "", sizePolicy(Qt::Horizontal), sizePolicy(Qt::Vertical)); + indent, "", (unsigned int)sizePolicy(Qt::Horizontal), (unsigned int)sizePolicy(Qt::Vertical)); } #endif @@ -758,6 +762,8 @@ QGridLayoutEngine::QGridLayoutEngine(Qt::Alignment defaultAlignment, bool snapTo m_visualDirection = Qt::LeftToRight; m_defaultAlignment = defaultAlignment; m_snapToPixelGrid = snapToPixelGrid; + m_uniformCellWidths = false; + m_uniformCellHeights = false; invalidate(); } @@ -773,7 +779,7 @@ int QGridLayoutEngine::columnCount(Qt::Orientation orientation) const int QGridLayoutEngine::itemCount() const { - return q_items.count(); + return q_items.size(); } QGridLayoutItem *QGridLayoutEngine::itemAt(int index) const @@ -818,7 +824,7 @@ void QGridLayoutEngine::setRowSpacing(int row, qreal spacing, Qt::Orientation or Q_ASSERT(row >= 0); QGridLayoutRowInfo &rowInfo = q_infos[orientation]; - if (row >= rowInfo.spacings.count()) + if (row >= rowInfo.spacings.size()) rowInfo.spacings.resize(row + 1); if (spacing >= 0) rowInfo.spacings[row].setUserValue(spacing); @@ -843,7 +849,7 @@ void QGridLayoutEngine::setRowStretchFactor(int row, int stretch, Qt::Orientatio maybeExpandGrid(row, -1, orientation); QGridLayoutRowInfo &rowInfo = q_infos[orientation]; - if (row >= rowInfo.stretches.count()) + if (row >= rowInfo.stretches.size()) rowInfo.stretches.resize(row + 1); rowInfo.stretches[row].setUserValue(stretch); } @@ -865,7 +871,7 @@ void QGridLayoutEngine::setRowSizeHint(Qt::SizeHint which, int row, qreal size, maybeExpandGrid(row, -1, orientation); QGridLayoutRowInfo &rowInfo = q_infos[orientation]; - if (row >= rowInfo.boxes.count()) + if (row >= rowInfo.boxes.size()) rowInfo.boxes.resize(row + 1); rowInfo.boxes[row].q_sizes(which) = size; } @@ -875,6 +881,34 @@ qreal QGridLayoutEngine::rowSizeHint(Qt::SizeHint which, int row, Qt::Orientatio return q_infos[orientation].boxes.value(row).q_sizes(which); } +bool QGridLayoutEngine::uniformCellWidths() const +{ + return m_uniformCellWidths; +} + +void QGridLayoutEngine::setUniformCellWidths(bool uniformCellWidths) +{ + if (m_uniformCellWidths == uniformCellWidths) + return; + + m_uniformCellWidths = uniformCellWidths; + invalidate(); +} + +bool QGridLayoutEngine::uniformCellHeights() const +{ + return m_uniformCellHeights; +} + +void QGridLayoutEngine::setUniformCellHeights(bool uniformCellHeights) +{ + if (m_uniformCellHeights == uniformCellHeights) + return; + + m_uniformCellHeights = uniformCellHeights; + invalidate(); +} + void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation) { @@ -883,7 +917,7 @@ void QGridLayoutEngine::setRowAlignment(int row, Qt::Alignment alignment, maybeExpandGrid(row, -1, orientation); QGridLayoutRowInfo &rowInfo = q_infos[orientation]; - if (row >= rowInfo.alignments.count()) + if (row >= rowInfo.alignments.size()) rowInfo.alignments.resize(row + 1); rowInfo.alignments[row] = alignment; } @@ -930,8 +964,11 @@ void QGridLayoutEngine::insertItem(QGridLayoutItem *item, int index) for (int i = item->firstRow(); i <= item->lastRow(); ++i) { for (int j = item->firstColumn(); j <= item->lastColumn(); ++j) { - if (itemAt(i, j)) - qWarning("QGridLayoutEngine::addItem: Cell (%d, %d) already taken", i, j); + const auto existingItem = itemAt(i, j); + if (existingItem) { + qWarning("QGridLayoutEngine::addItem: Can't add %s at cell (%d, %d) because it's already taken by %s", + qPrintable(item->toString()), i, j, qPrintable(existingItem->toString())); + } setItemAt(i, j, item); } } @@ -992,7 +1029,7 @@ void QGridLayoutEngine::setGeometries(const QRectF &contentsGeometry, const QAbs ensureGeometries(contentsGeometry.size(), styleInfo); - for (int i = q_items.count() - 1; i >= 0; --i) { + for (int i = q_items.size() - 1; i >= 0; --i) { QGridLayoutItem *item = q_items.at(i); qreal x = q_xx.at(item->firstColumn()); @@ -1121,7 +1158,7 @@ void QGridLayoutEngine::transpose() { invalidate(); - for (int i = q_items.count() - 1; i >= 0; --i) + for (int i = q_items.size() - 1; i >= 0; --i) q_items.at(i)->transpose(); q_defaultSpacings.transpose(); @@ -1145,7 +1182,7 @@ void QGridLayoutEngine::dump(int indent) const { qDebug("%*sEngine", indent, ""); - qDebug("%*s Items (%d)", indent, "", q_items.count()); + qDebug("%*s Items (%lld)", indent, "", q_items.count()); int i; for (i = 0; i < q_items.count(); ++i) q_items.at(i)->dump(indent + 2); @@ -1210,7 +1247,7 @@ void QGridLayoutEngine::maybeExpandGrid(int row, int column, Qt::Orientation ori int newGridColumnCount = internalGridColumnCount(); int newGridSize = newGridRowCount * newGridColumnCount; - if (newGridSize != q_grid.count()) { + if (newGridSize != q_grid.size()) { q_grid.resize(newGridSize); if (newGridColumnCount != oldGridColumnCount) { @@ -1232,7 +1269,7 @@ void QGridLayoutEngine::regenerateGrid() { q_grid.fill(nullptr); - for (int i = q_items.count() - 1; i >= 0; --i) { + for (int i = q_items.size() - 1; i >= 0; --i) { QGridLayoutItem *item = q_items.at(i); for (int j = item->firstRow(); j <= item->lastRow(); ++j) { @@ -1265,7 +1302,7 @@ void QGridLayoutEngine::insertOrRemoveRows(int row, int delta, Qt::Orientation o q_infos[orientation].insertOrRemoveRows(row, delta); - for (int i = q_items.count() - 1; i >= 0; --i) + for (int i = q_items.size() - 1; i >= 0; --i) q_items.at(i)->insertOrRemoveRows(row, delta, orientation); q_grid.resize(internalGridRowCount() * internalGridColumnCount()); @@ -1406,7 +1443,7 @@ void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData, } } } - if (row < rowInfo.boxes.count()) { + if (row < rowInfo.boxes.size()) { QGridLayoutBox rowBoxInfo = rowInfo.boxes.at(row); rowBoxInfo.normalize(); rowBox.q_minimumSize = qMax(rowBox.q_minimumSize, rowBoxInfo.q_minimumSize); @@ -1499,6 +1536,27 @@ void QGridLayoutEngine::fillRowData(QGridLayoutRowData *rowData, rowSpacing = qMax(windowMargin, rowSpacing); } } + + if (rowData->boxes.size() > 1 && + ((orientation == Qt::Horizontal && m_uniformCellWidths) || + (orientation == Qt::Vertical && m_uniformCellHeights))) { + qreal averagePreferredSize = 0.; + qreal minimumMaximumSize = std::numeric_limits<qreal>::max(); + qreal maximumMinimumSize = 0.; + for (const auto &box : rowData->boxes) { + averagePreferredSize += box.q_preferredSize; + minimumMaximumSize = qMin(minimumMaximumSize, box.q_maximumSize); + maximumMinimumSize = qMax(maximumMinimumSize, box.q_minimumSize); + } + averagePreferredSize /= rowData->boxes.size(); + minimumMaximumSize = qMax(minimumMaximumSize, maximumMinimumSize); + averagePreferredSize = qBound(maximumMinimumSize, averagePreferredSize, minimumMaximumSize); + for (auto &box : rowData->boxes) { + box.q_preferredSize = averagePreferredSize; + box.q_minimumSize = maximumMinimumSize; + box.q_maximumSize = minimumMaximumSize; + } + } } void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const @@ -1510,7 +1568,7 @@ void QGridLayoutEngine::ensureEffectiveFirstAndLastRows() const q_cachedEffectiveFirstRows = {columnCount, rowCount}; q_cachedEffectiveLastRows = {-1, -1}; - for (int i = q_items.count() - 1; i >= 0; --i) { + for (int i = q_items.size() - 1; i >= 0; --i) { const QGridLayoutItem *item = q_items.at(i); for (Qt::Orientation o : {Qt::Horizontal, Qt::Vertical}) { @@ -1556,7 +1614,7 @@ void QGridLayoutEngine::ensureColumnAndRowData(QGridLayoutRowData *rowData, QGri bool QGridLayoutEngine::ensureDynamicConstraint() const { if (q_cachedConstraintOrientation == UnknownConstraint) { - for (int i = q_items.count() - 1; i >= 0; --i) { + for (int i = q_items.size() - 1; i >= 0; --i) { QGridLayoutItem *item = q_items.at(i); if (item->hasDynamicConstraint()) { Qt::Orientation itemConstraintOrientation = item->dynamicConstraintOrientation(); diff --git a/src/gui/util/qgridlayoutengine_p.h b/src/gui/util/qgridlayoutengine_p.h index 8344d488c1..2f60cb99fd 100644 --- a/src/gui/util/qgridlayoutengine_p.h +++ b/src/gui/util/qgridlayoutengine_p.h @@ -24,6 +24,7 @@ #include <QtCore/qpair.h> #include <QtCore/qsize.h> #include <QtCore/qrect.h> +#include <QtCore/qdebug.h> #include <float.h> #include "qlayoutpolicy_p.h" @@ -283,6 +284,8 @@ public: virtual QLayoutPolicy::ControlTypes controlTypes(LayoutSide side) const; + inline virtual QString toString() const { return QDebug::toString(this); } + QRectF geometryWithin(qreal x, qreal y, qreal width, qreal height, qreal rowDescent, Qt::Alignment align, bool snapToPixelGrid) const; QGridLayoutBox box(Qt::Orientation orientation, bool snapToPixelGrid, qreal constraint = -1.0) const; @@ -334,6 +337,12 @@ public: qreal rowSizeHint(Qt::SizeHint which, int row, Qt::Orientation orientation = Qt::Vertical) const; + bool uniformCellWidths() const; + void setUniformCellWidths(bool uniformCellWidths); + + bool uniformCellHeights() const; + void setUniformCellHeights(bool uniformCellHeights); + void setRowAlignment(int row, Qt::Alignment alignment, Qt::Orientation orientation); Qt::Alignment rowAlignment(int row, Qt::Orientation orientation) const; @@ -415,6 +424,8 @@ private: // Configuration Qt::Alignment m_defaultAlignment; unsigned m_snapToPixelGrid : 1; + unsigned m_uniformCellWidths : 1; + unsigned m_uniformCellHeights : 1; // Lazily computed from the above user input mutable QHVContainer<int> q_cachedEffectiveFirstRows; diff --git a/src/gui/util/qktxhandler.cpp b/src/gui/util/qktxhandler.cpp index 85e3428bf0..35f1df1185 100644 --- a/src/gui/util/qktxhandler.cpp +++ b/src/gui/util/qktxhandler.cpp @@ -41,7 +41,7 @@ struct KTXHeader { quint32 bytesOfKeyValueData; }; -static const quint32 headerSize = sizeof(KTXHeader); +static constexpr quint32 qktxh_headerSize = sizeof(KTXHeader); // Currently unused, declared for future reference struct KTXKeyValuePairItem { @@ -71,11 +71,24 @@ struct KTXMipmapLevel { */ }; -// Returns the nearest multiple of 'rounding' greater than or equal to 'value' -constexpr quint32 withPadding(quint32 value, quint32 rounding) +// Returns the nearest multiple of 4 greater than or equal to 'value' +static const std::optional<quint32> nearestMultipleOf4(quint32 value) { - Q_ASSERT(rounding > 1); - return value + (rounding - 1) - ((value + (rounding - 1)) % rounding); + constexpr quint32 rounding = 4; + quint32 result = 0; + if (qAddOverflow(value, rounding - 1, &result)) + return std::nullopt; + result &= ~(rounding - 1); + return result; +} + +// Returns a view with prechecked bounds +static QByteArrayView safeView(QByteArrayView view, quint32 start, quint32 length) +{ + quint32 end = 0; + if (qAddOverflow(start, length, &end) || end > quint32(view.length())) + return {}; + return view.sliced(start, length); } QKtxHandler::~QKtxHandler() = default; @@ -83,8 +96,7 @@ QKtxHandler::~QKtxHandler() = default; bool QKtxHandler::canRead(const QByteArray &suffix, const QByteArray &block) { Q_UNUSED(suffix); - - return (qstrncmp(block.constData(), ktxIdentifier, KTX_IDENTIFIER_LENGTH) == 0); + return block.startsWith(ktxIdentifier); } QTextureFileData QKtxHandler::read() @@ -93,55 +105,122 @@ QTextureFileData QKtxHandler::read() return QTextureFileData(); const QByteArray buf = device()->readAll(); - const quint32 dataSize = quint32(buf.size()); - if (dataSize < headerSize || !canRead(QByteArray(), buf)) { - qCDebug(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); + if (static_cast<size_t>(buf.size()) > std::numeric_limits<quint32>::max()) { + qWarning(lcQtGuiTextureIO, "Too big KTX file %s", logName().constData()); + return QTextureFileData(); + } + + if (!canRead(QByteArray(), buf)) { + qWarning(lcQtGuiTextureIO, "Invalid KTX file %s", logName().constData()); return QTextureFileData(); } - const KTXHeader *header = reinterpret_cast<const KTXHeader *>(buf.data()); - if (!checkHeader(*header)) { - qCDebug(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); + if (buf.size() < qsizetype(qktxh_headerSize)) { + qWarning(lcQtGuiTextureIO, "Invalid KTX header size in %s", logName().constData()); + return QTextureFileData(); + } + + KTXHeader header; + memcpy(&header, buf.data(), qktxh_headerSize); + if (!checkHeader(header)) { + qWarning(lcQtGuiTextureIO, "Unsupported KTX file format in %s", logName().constData()); return QTextureFileData(); } QTextureFileData texData; texData.setData(buf); - texData.setSize(QSize(decode(header->pixelWidth), decode(header->pixelHeight))); - texData.setGLFormat(decode(header->glFormat)); - texData.setGLInternalFormat(decode(header->glInternalFormat)); - texData.setGLBaseInternalFormat(decode(header->glBaseInternalFormat)); + texData.setSize(QSize(decode(header.pixelWidth), decode(header.pixelHeight))); + texData.setGLFormat(decode(header.glFormat)); + texData.setGLInternalFormat(decode(header.glInternalFormat)); + texData.setGLBaseInternalFormat(decode(header.glBaseInternalFormat)); - texData.setNumLevels(decode(header->numberOfMipmapLevels)); - texData.setNumFaces(decode(header->numberOfFaces)); + texData.setNumLevels(decode(header.numberOfMipmapLevels)); + texData.setNumFaces(decode(header.numberOfFaces)); + + const quint32 bytesOfKeyValueData = decode(header.bytesOfKeyValueData); + quint32 headerKeyValueSize; + if (qAddOverflow(qktxh_headerSize, bytesOfKeyValueData, &headerKeyValueSize)) { + qWarning(lcQtGuiTextureIO, "Overflow in size of key value data in header of KTX file %s", + logName().constData()); + return QTextureFileData(); + } - const quint32 bytesOfKeyValueData = decode(header->bytesOfKeyValueData); - if (headerSize + bytesOfKeyValueData < quint64(buf.length())) // oob check - texData.setKeyValueMetadata( - decodeKeyValues(QByteArrayView(buf.data() + headerSize, bytesOfKeyValueData))); - quint32 offset = headerSize + bytesOfKeyValueData; + if (headerKeyValueSize >= quint32(buf.size())) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + // File contains key/values + if (bytesOfKeyValueData > 0) { + auto keyValueDataView = safeView(buf, qktxh_headerSize, bytesOfKeyValueData); + if (keyValueDataView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + auto keyValues = decodeKeyValues(keyValueDataView); + if (!keyValues) { + qWarning(lcQtGuiTextureIO, "Could not parse key values in KTX file %s", + logName().constData()); + return QTextureFileData(); + } + + texData.setKeyValueMetadata(*keyValues); + } + + // Technically, any number of levels is allowed but if the value is bigger than + // what is possible in KTX V2 (and what makes sense) we return an error. + // maxLevels = log2(max(width, height, depth)) + const int maxLevels = (sizeof(quint32) * 8) + - qCountLeadingZeroBits(std::max( + { header.pixelWidth, header.pixelHeight, header.pixelDepth })); + + if (texData.numLevels() > maxLevels) { + qWarning(lcQtGuiTextureIO, "Too many levels in KTX file %s", logName().constData()); + return QTextureFileData(); + } - constexpr int MAX_ITERATIONS = 32; // cap iterations in case of corrupt data + if (texData.numFaces() != 1 && texData.numFaces() != 6) { + qWarning(lcQtGuiTextureIO, "Invalid number of faces in KTX file %s", logName().constData()); + return QTextureFileData(); + } - for (int level = 0; level < qMin(texData.numLevels(), MAX_ITERATIONS); level++) { - if (offset + sizeof(quint32) > dataSize) // Corrupt file; avoid oob read - break; + quint32 offset = headerKeyValueSize; + for (int level = 0; level < texData.numLevels(); level++) { + const auto imageSizeView = safeView(buf, offset, sizeof(quint32)); + if (imageSizeView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } - const quint32 imageSize = decode(qFromUnaligned<quint32>(buf.data() + offset)); - offset += sizeof(quint32); + const quint32 imageSize = decode(qFromUnaligned<quint32>(imageSizeView.data())); + offset += sizeof(quint32); // overflow checked indirectly above - for (int face = 0; face < qMin(texData.numFaces(), MAX_ITERATIONS); face++) { + for (int face = 0; face < texData.numFaces(); face++) { texData.setDataOffset(offset, level, face); texData.setDataLength(imageSize, level, face); // Add image data and padding to offset - offset += withPadding(imageSize, 4); + const auto padded = nearestMultipleOf4(imageSize); + if (!padded) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + quint32 offsetNext; + if (qAddOverflow(offset, *padded, &offsetNext)) { + qWarning(lcQtGuiTextureIO, "OOB request in KTX file %s", logName().constData()); + return QTextureFileData(); + } + + offset = offsetNext; } } if (!texData.isValid()) { - qCDebug(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", logName().constData()); + qWarning(lcQtGuiTextureIO, "Invalid values in header of KTX file %s", + logName().constData()); return QTextureFileData(); } @@ -187,33 +266,83 @@ bool QKtxHandler::checkHeader(const KTXHeader &header) return is2D && (isCubeMap || isCompressedImage); } -QMap<QByteArray, QByteArray> QKtxHandler::decodeKeyValues(QByteArrayView view) const +std::optional<QMap<QByteArray, QByteArray>> QKtxHandler::decodeKeyValues(QByteArrayView view) const { QMap<QByteArray, QByteArray> output; quint32 offset = 0; - while (offset < view.size() + sizeof(quint32)) { + while (offset < quint32(view.size())) { + const auto keyAndValueByteSizeView = safeView(view, offset, sizeof(quint32)); + if (keyAndValueByteSizeView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } + const quint32 keyAndValueByteSize = - decode(qFromUnaligned<quint32>(view.constData() + offset)); - offset += sizeof(quint32); + decode(qFromUnaligned<quint32>(keyAndValueByteSizeView.data())); - if (offset + keyAndValueByteSize > quint64(view.size())) - break; // oob read + quint32 offsetKeyAndValueStart; + if (qAddOverflow(offset, quint32(sizeof(quint32)), &offsetKeyAndValueStart)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + quint32 offsetKeyAndValueEnd; + if (qAddOverflow(offsetKeyAndValueStart, keyAndValueByteSize, &offsetKeyAndValueEnd)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + const auto keyValueView = safeView(view, offsetKeyAndValueStart, keyAndValueByteSize); + if (keyValueView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } // 'key' is a UTF-8 string ending with a null terminator, 'value' is the rest. // To separate the key and value we convert the complete data to utf-8 and find the first // null terminator from the left, here we split the data into two. - const auto str = QString::fromUtf8(view.constData() + offset, keyAndValueByteSize); - const int idx = str.indexOf('\0'_L1); - if (idx == -1) - continue; - - const QByteArray key = str.left(idx).toUtf8(); - const size_t keySize = key.size() + 1; // Actual data size - const QByteArray value = QByteArray::fromRawData(view.constData() + offset + keySize, - keyAndValueByteSize - keySize); - - offset = withPadding(offset + keyAndValueByteSize, 4); - output.insert(key, value); + + const int idx = keyValueView.indexOf('\0'); + if (idx == -1) { + qWarning(lcQtGuiTextureIO, "Invalid key in KTX key-value"); + return std::nullopt; + } + + const QByteArrayView keyView = safeView(view, offsetKeyAndValueStart, idx); + if (keyView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + const quint32 keySize = idx + 1; // Actual data size + + quint32 offsetValueStart; + if (qAddOverflow(offsetKeyAndValueStart, keySize, &offsetValueStart)) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + quint32 valueSize; + if (qSubOverflow(keyAndValueByteSize, keySize, &valueSize)) { + qWarning(lcQtGuiTextureIO, "Underflow in KTX key-value"); + return std::nullopt; + } + + const QByteArrayView valueView = safeView(view, offsetValueStart, valueSize); + if (valueView.isEmpty()) { + qWarning(lcQtGuiTextureIO, "Invalid view in KTX key-value"); + return std::nullopt; + } + + output.insert(keyView.toByteArray(), valueView.toByteArray()); + + const auto offsetNext = nearestMultipleOf4(offsetKeyAndValueEnd); + if (!offsetNext) { + qWarning(lcQtGuiTextureIO, "Overflow in KTX key-value"); + return std::nullopt; + } + + offset = *offsetNext; } return output; diff --git a/src/gui/util/qktxhandler_p.h b/src/gui/util/qktxhandler_p.h index 7d54b20922..3a0b8fcf7e 100644 --- a/src/gui/util/qktxhandler_p.h +++ b/src/gui/util/qktxhandler_p.h @@ -17,6 +17,8 @@ #include "qtexturefilehandler_p.h" +#include <optional> + QT_BEGIN_NAMESPACE struct KTXHeader; @@ -33,7 +35,7 @@ public: private: bool checkHeader(const KTXHeader &header); - QMap<QByteArray, QByteArray> decodeKeyValues(QByteArrayView view) const; + std::optional<QMap<QByteArray, QByteArray>> decodeKeyValues(QByteArrayView view) const; quint32 decode(quint32 val) const; bool inverseEndian = false; diff --git a/src/gui/util/qpkmhandler.cpp b/src/gui/util/qpkmhandler.cpp index 7b0fb020df..d84ce2ce7f 100644 --- a/src/gui/util/qpkmhandler.cpp +++ b/src/gui/util/qpkmhandler.cpp @@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE -static const int headerSize = 16; +static const int qpkmh_headerSize = 16; struct PkmType { @@ -46,7 +46,7 @@ QTextureFileData QPkmHandler::read() return texData; QByteArray fileData = device()->readAll(); - if (fileData.size() < headerSize || !canRead(QByteArray(), fileData)) { + if (fileData.size() < qpkmh_headerSize || !canRead(QByteArray(), fileData)) { qCDebug(lcQtGuiTextureIO, "Invalid PKM file %s", logName().constData()); return QTextureFileData(); } @@ -75,7 +75,7 @@ QTextureFileData QPkmHandler::read() QSize texSize(qFromBigEndian<quint16>(rawData + 12), qFromBigEndian<quint16>(rawData + 14)); texData.setSize(texSize); - texData.setDataOffset(headerSize); + texData.setDataOffset(qpkmh_headerSize); if (!texData.isValid()) { qCDebug(lcQtGuiTextureIO, "Invalid values in header of PKM file %s", logName().constData()); diff --git a/src/gui/util/qtexturefiledata.cpp b/src/gui/util/qtexturefiledata.cpp index 4b9bcb0a33..e1fa900b84 100644 --- a/src/gui/util/qtexturefiledata.cpp +++ b/src/gui/util/qtexturefiledata.cpp @@ -1,6 +1,7 @@ // 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 +#include "QtGui/qimage.h" #include "qtexturefiledata_p.h" #include <QtCore/qsize.h> #include <QtCore/qvarlengtharray.h> @@ -21,14 +22,17 @@ public: QTextureFileDataPrivate(const QTextureFileDataPrivate &other) : QSharedData(other), + mode(other.mode), logName(other.logName), data(other.data), offsets(other.offsets), lengths(other.lengths), + images(other.images), size(other.size), format(other.format), numFaces(other.numFaces), - numLevels(other.numLevels) + numLevels(other.numLevels), + keyValues(other.keyValues) { } @@ -40,13 +44,18 @@ public: { numLevels = force ? levels : qMax(numLevels, levels); numFaces = force ? faces : qMax(numFaces, faces); - - offsets.resize(numFaces); - lengths.resize(numFaces); - - for (auto faceList : { &offsets, &lengths }) - for (auto &levelList : *faceList) + if (mode == QTextureFileData::ByteArrayMode) { + offsets.resize(numFaces); + lengths.resize(numFaces); + + for (auto faceList : { &offsets, &lengths }) + for (auto &levelList : *faceList) + levelList.resize(numLevels); + } else { + images.resize(numFaces); + for (auto &levelList : images) levelList.resize(numLevels); + } } bool isValid(int level, int face) const { return level < numLevels && face < numFaces; } @@ -56,10 +65,12 @@ public: int getLength(int level, int face) const { return lengths[face][level]; } void setLength(int value, int level, int face) { lengths[face][level] = value; } + QTextureFileData::Mode mode = QTextureFileData::ByteArrayMode; QByteArray logName; QByteArray data; QVarLengthArray<QList<int>, MAX_FACES> offsets; // [Face][Level] = offset QVarLengthArray<QList<int>, MAX_FACES> lengths; // [Face][Level] = length + QVarLengthArray<QList<QImage>, MAX_FACES> images; // [Face][Level] = length QSize size; quint32 format = 0; quint32 internalFormat = 0; @@ -69,8 +80,10 @@ public: QMap<QByteArray, QByteArray> keyValues; }; -QTextureFileData::QTextureFileData() +QTextureFileData::QTextureFileData(Mode mode) { + d = new QTextureFileDataPrivate; + d->mode = mode; } QTextureFileData::QTextureFileData(const QTextureFileData &other) @@ -98,11 +111,14 @@ bool QTextureFileData::isValid() const if (!d) return false; + if (d->mode == ImageMode) + return true; // Manually populated: the caller needs to do verification at that time. + if (d->data.isEmpty() || d->size.isEmpty() || (!d->format && !d->internalFormat)) return false; - const int numFacesOffset = d->offsets.length(); - const int numFacesLength = d->lengths.length(); + const int numFacesOffset = d->offsets.size(); + const int numFacesLength = d->lengths.size(); if (numFacesOffset == 0 || numFacesLength == 0 || d->numFaces != numFacesOffset || d->numFaces != numFacesLength) return false; @@ -139,19 +155,26 @@ QByteArray QTextureFileData::data() const void QTextureFileData::setData(const QByteArray &data) { - if (!d.constData()) //### uh think about this design, this is the only way to create; should be constructor instead at least - d = new QTextureFileDataPrivate; - + Q_ASSERT(d->mode == ByteArrayMode); d->data = data; } +void QTextureFileData::setData(const QImage &image, int level, int face) +{ + Q_ASSERT(d->mode == ImageMode); + d->ensureSize(level + 1, face + 1); + d->images[face][level] = image; +} + int QTextureFileData::dataOffset(int level, int face) const { + Q_ASSERT(d->mode == ByteArrayMode); return (d && d->isValid(level, face)) ? d->getOffset(level, face) : 0; } void QTextureFileData::setDataOffset(int offset, int level, int face) { + Q_ASSERT(d->mode == ByteArrayMode); if (d.constData() && level >= 0) { d->ensureSize(level + 1, face + 1); d->setOffset(offset, level, face); @@ -160,22 +183,31 @@ void QTextureFileData::setDataOffset(int offset, int level, int face) int QTextureFileData::dataLength(int level, int face) const { + Q_ASSERT(d->mode == ByteArrayMode); return (d && d->isValid(level, face)) ? d->getLength(level, face) : 0; } QByteArrayView QTextureFileData::getDataView(int level, int face) const { - const int dataLength = this->dataLength(level, face); - const int dataOffset = this->dataOffset(level, face); + if (d->mode == ByteArrayMode) { + const int dataLength = this->dataLength(level, face); + const int dataOffset = this->dataOffset(level, face); - if (d == nullptr || dataLength == 0) - return QByteArrayView(); + if (d == nullptr || dataLength == 0) + return QByteArrayView(); - return QByteArrayView(d->data.constData() + dataOffset, dataLength); + return QByteArrayView(d->data.constData() + dataOffset, dataLength); + } else { + if (!d->isValid(level, face)) + return QByteArrayView(); + const QImage &img = d->images[face][level]; + return img.isNull() ? QByteArrayView() : QByteArrayView(img.constBits(), img.sizeInBytes()); + } } void QTextureFileData::setDataLength(int length, int level, int face) { + Q_ASSERT(d->mode == ByteArrayMode); if (d.constData() && level >= 0) { d->ensureSize(level + 1, face + 1); d->setLength(length, level, face); @@ -286,9 +318,11 @@ QDebug operator<<(QDebug dbg, const QTextureFileData &d) dbg << "glInternalFormat:" << glFormatName(d.glInternalFormat()); dbg << "glBaseInternalFormat:" << glFormatName(d.glBaseInternalFormat()); dbg.nospace() << "Levels: " << d.numLevels(); + dbg.nospace() << "Faces: " << d.numFaces(); if (!d.isValid()) dbg << " {Invalid}"; dbg << ")"; + dbg << (d.d->mode ? "[bytearray-based]" : "[image-based]"); } else { dbg << "null)"; } diff --git a/src/gui/util/qtexturefiledata_p.h b/src/gui/util/qtexturefiledata_p.h index 189a590979..cd0dbe171c 100644 --- a/src/gui/util/qtexturefiledata_p.h +++ b/src/gui/util/qtexturefiledata_p.h @@ -30,7 +30,9 @@ class QTextureFileDataPrivate; class Q_GUI_EXPORT QTextureFileData { public: - QTextureFileData(); + enum Mode { ByteArrayMode, ImageMode }; + + QTextureFileData(Mode mode = ByteArrayMode); QTextureFileData(const QTextureFileData &other); QTextureFileData &operator=(const QTextureFileData &other); ~QTextureFileData(); @@ -42,6 +44,7 @@ public: QByteArray data() const; void setData(const QByteArray &data); + void setData(const QImage &image, int level = 0, int face = 0); int dataOffset(int level = 0, int face = 0) const; void setDataOffset(int offset, int level = 0, int face = 0); @@ -77,6 +80,7 @@ public: private: QSharedDataPointer<QTextureFileDataPrivate> d; + friend Q_GUI_EXPORT QDebug operator<<(QDebug dbg, const QTextureFileData &d); }; Q_DECLARE_TYPEINFO(QTextureFileData, Q_RELOCATABLE_TYPE); diff --git a/src/gui/util/qundostack.cpp b/src/gui/util/qundostack.cpp index 94d97216c4..403833d421 100644 --- a/src/gui/util/qundostack.cpp +++ b/src/gui/util/qundostack.cpp @@ -286,7 +286,7 @@ void QUndoCommand::setText(const QString &text) int QUndoCommand::childCount() const { - return d->child_list.count(); + return d->child_list.size(); } /*! @@ -299,7 +299,7 @@ int QUndoCommand::childCount() const const QUndoCommand *QUndoCommand::child(int index) const { - if (index < 0 || index >= d->child_list.count()) + if (index < 0 || index >= d->child_list.size()) return nullptr; return d->child_list.at(index); } @@ -444,10 +444,10 @@ void QUndoStackPrivate::setIndex(int idx, bool clean) bool QUndoStackPrivate::checkUndoLimit() { - if (undo_limit <= 0 || !macro_stack.isEmpty() || undo_limit >= command_list.count()) + if (undo_limit <= 0 || !macro_stack.isEmpty() || undo_limit >= command_list.size()) return false; - int del_count = command_list.count() - undo_limit; + int del_count = command_list.size() - undo_limit; for (int i = 0; i < del_count; ++i) delete command_list.takeFirst(); @@ -1142,7 +1142,7 @@ void QUndoStack::beginMacro(const QString &text) } d->macro_stack.append(cmd); - if (d->macro_stack.count() == 1) { + if (d->macro_stack.size() == 1) { emit canUndoChanged(false); emit undoTextChanged(QString()); emit canRedoChanged(false); @@ -1191,7 +1191,7 @@ const QUndoCommand *QUndoStack::command(int index) const { Q_D(const QUndoStack); - if (index < 0 || index >= d->command_list.count()) + if (index < 0 || index >= d->command_list.size()) return nullptr; return d->command_list.at(index); } diff --git a/src/gui/util/qvalidator.cpp b/src/gui/util/qvalidator.cpp index f71a66c98c..2a81006657 100644 --- a/src/gui/util/qvalidator.cpp +++ b/src/gui/util/qvalidator.cpp @@ -331,12 +331,12 @@ QIntValidator::~QIntValidator() or is a prefix of an integer in the valid range, returns \l Intermediate. Otherwise, returns \l Invalid. - If the valid range consists of just positive integers (e.g., 32 to 100) - and \a input is a negative integer, then Invalid is returned. (On the other - hand, if the range consists of negative integers (e.g., -100 to -32) and - \a input is a positive integer, then Intermediate is returned, because - the user might be just about to type the minus (especially for right-to-left - languages). + If the valid range consists of just positive integers (e.g., 32 to 100) and + \a input is a negative integer, then Invalid is returned. (On the other + hand, if the range consists of negative integers (e.g., -100 to -32) and \a + input is a positive integer without leading plus sign, then Intermediate is + returned, because the user might be just about to type the minus (especially + for right-to-left languages). Similarly, if the valid range is between 46 and 53, then 41 and 59 will be evaluated as \l Intermediate, as otherwise the user wouldn't be able to @@ -362,34 +362,45 @@ static qlonglong pow10(int exp) return result; } -QValidator::State QIntValidator::validate(QString & input, int&) const +template <typename T> static inline +std::optional<QValidator::State> initialResultCheck(T min, T max, const ParsingResult &result) { - QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1, - locale().numberOptions())) { - return Invalid; - } + if (result.state == ParsingResult::Invalid) + return QValidator::Invalid; + const CharBuff &buff = result.buff; if (buff.isEmpty()) - return Intermediate; + return QValidator::Intermediate; - const bool startsWithMinus(buff[0] == '-'); - if (b >= 0 && startsWithMinus) - return Invalid; + char ch = buff[0]; + const bool signConflicts = (min >= 0 && ch == '-') || (max < 0 && ch == '+'); + if (signConflicts) + return QValidator::Invalid; - const bool startsWithPlus(buff[0] == '+'); - if (t < 0 && startsWithPlus) - return Invalid; + if (result.state == ParsingResult::Intermediate) + return QValidator::Intermediate; - if (buff.size() == 1 && (startsWithPlus || startsWithMinus)) - return Intermediate; + return std::nullopt; +} - bool ok; - qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok); - if (!ok) +QValidator::State QIntValidator::validate(QString & input, int&) const +{ + ParsingResult result = + locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1, + locale().numberOptions()); + + std::optional<State> opt = initialResultCheck(b, t, result); + if (opt) + return *opt; + + const CharBuff &buff = result.buff; + QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10); + if (!r.ok()) return Invalid; + qint64 entered = r.result; if (entered >= b && entered <= t) { + bool ok = false; locale().toInt(input, &ok); return ok ? Acceptable : Intermediate; } @@ -401,7 +412,7 @@ QValidator::State QIntValidator::validate(QString & input, int&) const // of a number of digits equal to or less than the max value as intermediate. int buffLength = buff.size(); - if (startsWithPlus) + if (buff[0] == '+') buffLength--; const int tLength = t != 0 ? static_cast<int>(std::log10(qAbs(t))) + 1 : 1; @@ -414,15 +425,15 @@ QValidator::State QIntValidator::validate(QString & input, int&) const /*! \reimp */ void QIntValidator::fixup(QString &input) const { - QByteArray buff; - if (!locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, &buff, -1, - locale().numberOptions())) { + auto [parseState, buff] = + locale().d->m_data->validateChars(input, QLocaleData::IntegerMode, -1, + locale().numberOptions()); + if (parseState == ParsingResult::Invalid) return; - } - bool ok; - qlonglong entered = QLocaleData::bytearrayToLongLong(buff, 10, &ok); - if (ok) - input = locale().toString(entered); + + QSimpleParsedNumber r = QLocaleData::bytearrayToLongLong(buff, 10); + if (r.ok()) + input = locale().toString(r.result); } /*! @@ -531,10 +542,11 @@ public: in the German locale, "1,234" will be accepted as the fractional number 1.234. In Arabic locales, QDoubleValidator will accept Arabic digits. - \note The QLocale::NumberOptions set on the locale() also affect the - way the number is interpreted. For example, since QLocale::RejectGroupSeparator - is not set by default, the validator will accept group separators. It is thus - recommended to use QLocale::toDouble() to obtain the numeric value. + \note The QLocale::NumberOptions set on the locale() also affect the way the + number is interpreted. For example, since QLocale::RejectGroupSeparator is + not set by default (except on the \c "C" locale), the validator will accept + group separators. If the string passes validation, pass it to + locale().toDouble() to obtain its numeric value. \sa QIntValidator, QRegularExpressionValidator, QLocale::toDouble(), {Line Edits Example} */ @@ -544,10 +556,23 @@ public: \since 4.3 This enum defines the allowed notations for entering a double. - \value StandardNotation The string is written as a standard number - (i.e. 0.015). - \value ScientificNotation The string is written in scientific - form. It may have an exponent part(i.e. 1.5E-2). + \value StandardNotation The string is written in the standard format, a + whole number part optionally followed by a separator + and fractional part, for example \c{"0.015"}. + + \value ScientificNotation The string is written in scientific form, which + optionally appends an exponent part to the + standard format, for example \c{"1.5E-2"}. + + The whole number part may, as usual, include a sign. This, along with the + separators for fractional part, exponent and any digit-grouping, depend on + locale. QDoubleValidator doesn't check the placement (which would also + depend on locale) of any digit-grouping separators it finds, but it will + reject input that contains them if \l QLocale::RejectGroupSeparator is set + in \c locale().numberOptions(). + + \sa QLocale::numberOptions(), QLocale::decimalPoint(), + QLocale::exponential(), QLocale::negativeSign() */ /*! @@ -589,14 +614,14 @@ QDoubleValidator::~QDoubleValidator() /*! \fn QValidator::State QDoubleValidator::validate(QString &input, int &pos) const - Returns \l Acceptable if the string \a input contains a double - that is within the valid range and is in the correct format. + Returns \l Acceptable if the string \a input is in the correct format and + contains a double within the valid range. - Returns \l Intermediate if \a input contains a double that is - outside the range or is in the wrong format; e.g. is empty. + Returns \l Intermediate if \a input is in the wrong format or contains a + double outside the range. - Returns \l Invalid if the \a input is not a double or with too many - digits after the decimal point. + Returns \l Invalid if the \a input doesn't represent a double or has too + many digits after the decimal point. Note: If the valid range consists of just positive doubles (e.g. 0.0 to 100.0) and \a input is a negative double then \l Invalid is returned. If notation() @@ -632,19 +657,12 @@ QValidator::State QDoubleValidator::validate(QString & input, int &) const QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QLocaleData::NumberMode numMode, const QLocale &locale) const { Q_Q(const QDoubleValidator); - QByteArray buff; - if (!locale.d->m_data->validateChars(input, numMode, &buff, q->dec, locale.numberOptions())) { - return QValidator::Invalid; - } - - if (buff.isEmpty()) - return QValidator::Intermediate; - - if (q->b >= 0 && buff.startsWith('-')) - return QValidator::Invalid; + ParsingResult result = + locale.d->m_data->validateChars(input, numMode, q->dec, locale.numberOptions()); - if (q->t < 0 && buff.startsWith('+')) - return QValidator::Invalid; + std::optional<QValidator::State> opt = initialResultCheck(q->b, q->t, result); + if (opt) + return *opt; bool ok = false; double i = locale.toDouble(input, &ok); // returns 0.0 if !ok @@ -658,7 +676,11 @@ QValidator::State QDoubleValidatorPrivate::validateWithLocale(QString &input, QL if (notation == QDoubleValidator::StandardNotation) { double max = qMax(qAbs(q->b), qAbs(q->t)); qlonglong v; - if (convertDoubleTo(max, &v)) { + // Need a whole number to pass to convertDoubleTo() or it fails. Use + // floor, as max is positive so this has the same number of digits + // before the decimal point, where qCeil() might take us up to a power + // of ten, adding a digit. + if (convertDoubleTo(qFloor(max), &v)) { qlonglong n = pow10(numDigits(v)); // In order to get the highest possible number in the intermediate // range we need to get 10 to the power of the number of digits @@ -719,15 +741,16 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe const QLocale &locale) const { Q_Q(const QDoubleValidator); - QByteArray buff; // Passing -1 as the number of decimals, because fixup() exists to improve // an Intermediate value, if it can. - if (!locale.d->m_data->validateChars(input, numMode, &buff, -1, locale.numberOptions())) + auto [parseState, buff] = + locale.d->m_data->validateChars(input, numMode, -1, locale.numberOptions()); + if (parseState == ParsingResult::Invalid) return; - // buff now contains data in C locale. + // buff contains data in C locale. bool ok = false; - const double entered = buff.toDouble(&ok); + const double entered = QByteArrayView(buff).toDouble(&ok); if (ok) { // Here we need to adjust the output format accordingly char mode; @@ -751,7 +774,7 @@ void QDoubleValidatorPrivate::fixupWithLocale(QString &input, QLocaleData::Numbe if (eIndex < 0) eIndex = buff.size(); precision = eIndex - (buff.contains('.') ? 1 : 0) - - (buff.startsWith('-') || buff.startsWith('+') ? 1 : 0); + - (buff[0] == '-' || buff[0] == '+' ? 1 : 0); } // Use q->dec to limit the number of decimals, because we want the // fixup() result to pass validate(). |