diff options
author | Marco Bubke <marco.bubke@qt.io> | 2020-09-16 13:44:43 +0200 |
---|---|---|
committer | Tim Jenssen <tim.jenssen@qt.io> | 2020-10-16 10:01:21 +0000 |
commit | d1b0c12d6b6c4698492851716b3931bc9cae5fd3 (patch) | |
tree | 9aa7d8fddbafced065e392b84939c59d6ae8726e | |
parent | 58e612c85fb8320fd99a28d573372220cbfe309a (diff) |
QmlDesigner: Add image cache
The image cache is saving images and icon of this images in a sqlite
database. If there are no images they are generated in the backgound.
The icons are fetched by item library.
Task-number: QDS-2782
Task-number: QDS-2783
Task-number: QDS-2858
Change-Id: I5a32cccfef7f8fd8eb78902605a09f5da18ce88e
Reviewed-by: Tim Jenssen <tim.jenssen@qt.io>
111 files changed, 4403 insertions, 237 deletions
diff --git a/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h index 6c3715dd5f..0d3bbba7b7 100644 --- a/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h +++ b/share/qtcreator/qml/qmlpuppet/commands/captureddatacommand.h @@ -151,8 +151,19 @@ public: qint32 nodeId = -1; }; + CapturedDataCommand() = default; + + CapturedDataCommand(QVector<StateData> &&stateData) + : stateData{std::move(stateData)} + {} + + CapturedDataCommand(QImage &&image) + : image{std::move(image)} + {} + friend QDataStream &operator<<(QDataStream &out, const CapturedDataCommand &command) { + out << command.image; out << command.stateData; return out; @@ -160,12 +171,14 @@ public: friend QDataStream &operator>>(QDataStream &in, CapturedDataCommand &command) { + in >> command.image; in >> command.stateData; return in; } public: + QImage image; QVector<StateData> stateData; }; diff --git a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h index 841cd8561e..b4ffb74903 100644 --- a/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h +++ b/share/qtcreator/qml/qmlpuppet/interfaces/nodeinstanceserverinterface.h @@ -86,6 +86,7 @@ public: virtual void requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command) = 0; virtual void changeLanguage(const ChangeLanguageCommand &command) = 0; virtual void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) = 0; + virtual void dispatchCommand(const QVariant &) {} virtual void benchmark(const QString &) {} diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri index 85de00c5ff..448a67c913 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/instances.pri @@ -9,6 +9,7 @@ HEADERS += $$PWD/qt5nodeinstanceserver.h \ $$PWD/capturenodeinstanceserverdispatcher.h \ $$PWD/capturescenecreatedcommand.h \ $$PWD/nodeinstanceserverdispatcher.h \ + $$PWD/qt5captureimagenodeinstanceserver.h \ $$PWD/qt5capturepreviewnodeinstanceserver.h \ $$PWD/qt5testnodeinstanceserver.h \ $$PWD/qt5informationnodeinstanceserver.h \ @@ -33,11 +34,13 @@ HEADERS += $$PWD/qt5nodeinstanceserver.h \ $$PWD/layoutnodeinstance.h \ $$PWD/qt3dpresentationnodeinstance.h \ $$PWD/quick3dnodeinstance.h \ - $$PWD/quick3dtexturenodeinstance.h + $$PWD/quick3dtexturenodeinstance.h \ + SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ $$PWD/capturenodeinstanceserverdispatcher.cpp \ $$PWD/nodeinstanceserverdispatcher.cpp \ + $$PWD/qt5captureimagenodeinstanceserver.cpp \ $$PWD/qt5capturepreviewnodeinstanceserver.cpp \ $$PWD/qt5testnodeinstanceserver.cpp \ $$PWD/qt5informationnodeinstanceserver.cpp \ diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp index dd9c42c5bd..eb5aae6a7e 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp @@ -25,6 +25,7 @@ #include "nodeinstanceserverdispatcher.h" +#include "qt5captureimagenodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #include "qt5rendernodeinstanceserver.h" @@ -183,6 +184,8 @@ std::unique_ptr<NodeInstanceServer> createNodeInstanceServer( { if (serverName == "capturemode") return std::make_unique<Qt5CapturePreviewNodeInstanceServer>(nodeInstanceClient); + else if (serverName == "captureiconmode") + return std::make_unique<Qt5CaptureImageNodeInstanceServer>(nodeInstanceClient); else if (serverName == "rendermode") return std::make_unique<Qt5RenderNodeInstanceServer>(nodeInstanceClient); else if (serverName == "editormode") diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.cpp new file mode 100644 index 0000000000..d24c4e5552 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "qt5captureimagenodeinstanceserver.h" +#include "servernodeinstance.h" + +#include <captureddatacommand.h> +#include <createscenecommand.h> +#include <nodeinstanceclientinterface.h> + +#include <QImage> +#include <QQuickItem> +#include <QQuickView> + +namespace QmlDesigner { + +namespace { + +QImage renderImage(ServerNodeInstance rootNodeInstance) +{ + rootNodeInstance.updateDirtyNodeRecursive(); + + QSize previewImageSize = rootNodeInstance.boundingRect().size().toSize(); + if (previewImageSize.isEmpty()) + previewImageSize = {640, 480}; + + if (previewImageSize.width() > 800 || previewImageSize.height() > 800) + previewImageSize.scale({800, 800}, Qt::KeepAspectRatio); + + QImage previewImage = rootNodeInstance.renderPreviewImage(previewImageSize); + + return previewImage; +} +} // namespace + +void Qt5CaptureImageNodeInstanceServer::collectItemChangesAndSendChangeCommands() +{ + static bool inFunction = false; + + if (!rootNodeInstance().holdsGraphical()) { + nodeInstanceClient()->capturedData(CapturedDataCommand{}); + return; + } + + if (!inFunction) { + inFunction = true; + + auto rooNodeInstance = rootNodeInstance(); + rooNodeInstance.rootQuickItem()->setClip(true); + + DesignerSupport::polishItems(quickView()); + + QImage image = renderImage(rooNodeInstance); + + nodeInstanceClient()->capturedData(CapturedDataCommand{std::move(image)}); + + slowDownRenderTimer(); + inFunction = false; + } +} + +} // namespace diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.h new file mode 100644 index 0000000000..7c26e47a87 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5captureimagenodeinstanceserver.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <qt5previewnodeinstanceserver.h> + +namespace QmlDesigner { + +class Qt5CaptureImageNodeInstanceServer : public Qt5PreviewNodeInstanceServer +{ +public: + explicit Qt5CaptureImageNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) + : Qt5PreviewNodeInstanceServer(nodeInstanceClient) + {} + +protected: + void collectItemChangesAndSendChangeCommands() override; + +private: +}; + +} // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturepreviewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturepreviewnodeinstanceserver.cpp index c70ef76afe..7fb87defb0 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturepreviewnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5capturepreviewnodeinstanceserver.cpp @@ -100,7 +100,7 @@ void Qt5CapturePreviewNodeInstanceServer::collectItemChangesAndSendChangeCommand stateInstance.deactivateState(); } - nodeInstanceClient()->capturedData(CapturedDataCommand{stateDatas}); + nodeInstanceClient()->capturedData(CapturedDataCommand{std::move(stateDatas)}); slowDownRenderTimer(); inFunction = false; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index 64b6eb409c..93e6e786cd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -28,6 +28,7 @@ #include <QCoreApplication> #include "capturenodeinstanceserverdispatcher.h" +#include "qt5captureimagenodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #include "qt5previewnodeinstanceserver.h" @@ -92,6 +93,9 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : } else if (QCoreApplication::arguments().at(2) == QLatin1String("capturemode")) { setNodeInstanceServer(std::make_unique<Qt5CapturePreviewNodeInstanceServer>(this)); initializeSocket(); + } else if (QCoreApplication::arguments().at(2) == QLatin1String("captureiconmode")) { + setNodeInstanceServer(std::make_unique<Qt5CaptureImageNodeInstanceServer>(this)); + initializeSocket(); } } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp index bfaff0eea8..1f54bffdbd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp @@ -566,7 +566,7 @@ QRectF QuickItemNodeInstance::boundingRectWithStepChilds(QQuickItem *parentItem) boundingRect = boundingRect.united(QRectF(QPointF(0, 0), size())); - foreach (QQuickItem *childItem, parentItem->childItems()) { + for (QQuickItem *childItem : parentItem->childItems()) { if (!nodeInstanceServer()->hasInstanceForObject(childItem)) { QRectF transformedRect = childItem->mapRectToItem(parentItem, boundingRectWithStepChilds(childItem)); if (isRectangleSane(transformedRect)) @@ -574,6 +574,9 @@ QRectF QuickItemNodeInstance::boundingRectWithStepChilds(QQuickItem *parentItem) } } + if (boundingRect.isEmpty()) + QRectF{0, 0, 640, 480}; + return boundingRect; } diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp index 98df3de295..1f94bc3e59 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppetmain.cpp @@ -42,9 +42,11 @@ #endif #ifdef Q_OS_WIN -#include <windows.h> +#include <Windows.h> #endif +namespace { + int internalMain(QGuiApplication *application) { QCoreApplication::setOrganizationName("QtProject"); @@ -138,6 +140,7 @@ int internalMain(QGuiApplication *application) return application->exec(); } +} // namespace int main(int argc, char *argv[]) { diff --git a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml index 1cbec5d802..b7bc9b0b58 100644 --- a/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml +++ b/share/qtcreator/qmldesigner/itemLibraryQmlSources/ItemDelegate.qml @@ -46,8 +46,6 @@ Item { width: itemLibraryIconWidth // to be set in Qml context height: itemLibraryIconHeight // to be set in Qml context source: itemLibraryIconPath // to be set by model - - cache: false // Allow thumbnail to be dynamically updated } Text { @@ -71,10 +69,11 @@ Item { renderType: Text.NativeRendering } - ToolTipArea { + ImagePreviewTooltipArea { id: mouseRegion + anchors.fill: parent - tooltip: itemName + onPressed: { rootView.startDragAndDrop(mouseRegion, itemLibraryEntry) } diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml new file mode 100644 index 0000000000..51194cc3cb --- /dev/null +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ImagePreviewTooltipArea.qml @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +import QtQuick 2.1 +import HelperWidgets 2.0 +import QtQuick.Layouts 1.0 + +MouseArea { + id: mouseArea + + onExited: tooltipBackend.hideTooltip() + onCanceled: tooltipBackend.hideTooltip() + onClicked: forceActiveFocus() + + hoverEnabled: true + + Timer { + interval: 1000 + running: mouseArea.containsMouse + onTriggered: { + tooltipBackend.componentName = itemName + tooltipBackend.componentPath = componentPath + tooltipBackend.showTooltip() + } + } +} diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir index 59573217c5..0da6489551 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/qmldir @@ -47,3 +47,4 @@ ExpressionTextField 2.0 ExpressionTextField.qml MarginSection 2.0 MarginSection.qml HorizontalScrollBar 2.0 HorizontalScrollBar.qml VerticalScrollBar 2.0 VerticalScrollBar.qml +ImagePreviewTooltipArea 2.0 ImagePreviewTooltipArea.qml diff --git a/src/libs/sqlite/CMakeLists.txt b/src/libs/sqlite/CMakeLists.txt index 30e5edf12f..f684fa0849 100644 --- a/src/libs/sqlite/CMakeLists.txt +++ b/src/libs/sqlite/CMakeLists.txt @@ -44,4 +44,6 @@ add_qtc_library(Sqlite tableconstraints.h utf8string.cpp utf8string.h utf8stringvector.cpp utf8stringvector.h + sqliteblob.h + sqlitetimestamp.h ) diff --git a/src/libs/sqlite/sqlite-lib.pri b/src/libs/sqlite/sqlite-lib.pri index 8ded8ca106..fd02c8801f 100644 --- a/src/libs/sqlite/sqlite-lib.pri +++ b/src/libs/sqlite/sqlite-lib.pri @@ -27,6 +27,8 @@ SOURCES += \ $$PWD/sqlitebasestatement.cpp HEADERS += \ $$PWD/constraints.h \ + $$PWD/sqliteblob.h \ + $$PWD/sqlitetimestamp.h \ $$PWD/tableconstraints.h \ $$PWD/createtablesqlstatementbuilder.h \ $$PWD/lastchangedrowid.h \ diff --git a/src/libs/sqlite/sqlitebasestatement.cpp b/src/libs/sqlite/sqlitebasestatement.cpp index dc4dd465e7..1bd7ef62a2 100644 --- a/src/libs/sqlite/sqlitebasestatement.cpp +++ b/src/libs/sqlite/sqlitebasestatement.cpp @@ -191,12 +191,12 @@ void BaseStatement::bind(int index, Utils::SmallStringView text) checkForBindingError(resultCode); } -void BaseStatement::bind(int index, Utils::span<const byte> bytes) +void BaseStatement::bind(int index, BlobView blobView) { int resultCode = sqlite3_bind_blob64(m_compiledStatement.get(), index, - bytes.data(), - static_cast<long long>(bytes.size()), + blobView.data(), + blobView.size(), SQLITE_STATIC); if (resultCode != SQLITE_OK) checkForBindingError(resultCode); @@ -498,7 +498,7 @@ StringType textForColumn(sqlite3_stmt *sqlStatment, int column) return StringType(text, size); } -Utils::span<const byte> blobForColumn(sqlite3_stmt *sqlStatment, int column) +BlobView blobForColumn(sqlite3_stmt *sqlStatment, int column) { const byte *blob = reinterpret_cast<const byte *>(sqlite3_column_blob(sqlStatment, column)); std::size_t size = std::size_t(sqlite3_column_bytes(sqlStatment, column)); @@ -506,7 +506,7 @@ Utils::span<const byte> blobForColumn(sqlite3_stmt *sqlStatment, int column) return {blob, size}; } -Utils::span<const byte> convertToBlobForColumn(sqlite3_stmt *sqlStatment, int column) +BlobView convertToBlobForColumn(sqlite3_stmt *sqlStatment, int column) { int dataType = sqlite3_column_type(sqlStatment, column); if (dataType == SQLITE_BLOB) @@ -571,7 +571,7 @@ double BaseStatement::fetchDoubleValue(int column) const return sqlite3_column_double(m_compiledStatement.get(), column); } -Utils::span<const byte> BaseStatement::fetchBlobValue(int column) const +BlobView BaseStatement::fetchBlobValue(int column) const { return convertToBlobForColumn(m_compiledStatement.get(), column); } diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h index bae41665d5..0b990f7d93 100644 --- a/src/libs/sqlite/sqlitebasestatement.h +++ b/src/libs/sqlite/sqlitebasestatement.h @@ -27,6 +27,7 @@ #include "sqliteglobal.h" +#include "sqliteblob.h" #include "sqliteexception.h" #include "sqlitevalue.h" @@ -70,7 +71,7 @@ public: double fetchDoubleValue(int column) const; Utils::SmallStringView fetchSmallStringViewValue(int column) const; ValueView fetchValueView(int column) const; - Utils::span<const byte> fetchBlobValue(int column) const; + BlobView fetchBlobValue(int column) const; template<typename Type> Type fetchValue(int column) const; int columnCount() const; @@ -82,7 +83,7 @@ public: void bind(int index, void *pointer); void bind(int index, Utils::SmallStringView fetchValue); void bind(int index, const Value &fetchValue); - void bind(int index, Utils::span<const byte> bytes); + void bind(int index, BlobView blobView); void bind(int index, uint value) { bind(index, static_cast<long long>(value)); } @@ -358,7 +359,7 @@ private: operator long long() { return statement.fetchLongLongValue(column); } operator double() { return statement.fetchDoubleValue(column); } operator Utils::SmallStringView() { return statement.fetchSmallStringViewValue(column); } - operator Utils::span<const Sqlite::byte>() { return statement.fetchBlobValue(column); } + operator BlobView() { return statement.fetchBlobValue(column); } operator ValueView() { return statement.fetchValueView(column); } StatementImplementation &statement; diff --git a/src/libs/sqlite/sqliteblob.h b/src/libs/sqlite/sqliteblob.h new file mode 100644 index 0000000000..17d9426ad0 --- /dev/null +++ b/src/libs/sqlite/sqliteblob.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "sqliteglobal.h" + +#include <utils/span.h> + +#include <QByteArray> + +#include <algorithm> +#include <cstring> +#include <vector> + +namespace Sqlite { + +class BlobView +{ +public: + BlobView() = default; + + BlobView(const byte *data, std::size_t size) + : m_data(data) + , m_size(size) + {} + + BlobView(const QByteArray &byteArray) + : m_data(reinterpret_cast<const byte *>(byteArray.constData())) + , m_size(static_cast<std::size_t>(byteArray.size())) + {} + + BlobView(const std::vector<Sqlite::byte> &bytes) + : m_data(bytes.data()) + , m_size(static_cast<std::size_t>(bytes.size())) + {} + + const byte *data() const { return m_data; } + const char *cdata() const { return reinterpret_cast<const char *>(m_data); } + std::size_t size() const { return m_size; } + int sisize() const { return static_cast<int>(m_size); } + long long ssize() const { return static_cast<long long>(m_size); } + bool empty() const { return !m_size; } + + friend bool operator==(Sqlite::BlobView first, Sqlite::BlobView second) + { + return first.size() == second.size() + && std::memcmp(first.data(), second.data(), first.size()) == 0; + } + +private: + const byte *m_data{}; + std::size_t m_size{}; +}; + +class Blob +{ +public: + Blob(BlobView blobView) + { + bytes.reserve(blobView.size()); + std::copy_n(blobView.data(), blobView.size(), std::back_inserter(bytes)); + } + + std::vector<Sqlite::byte> bytes; +}; + +class ByteArrayBlob +{ +public: + ByteArrayBlob(BlobView blobView) + : byteArray{blobView.cdata(), blobView.sisize()} + {} + + QByteArray byteArray; +}; + +} // namespace Sqlite diff --git a/src/libs/sqlite/sqlitesessionchangeset.cpp b/src/libs/sqlite/sqlitesessionchangeset.cpp index 3aa43c625f..430cbae222 100644 --- a/src/libs/sqlite/sqlitesessionchangeset.cpp +++ b/src/libs/sqlite/sqlitesessionchangeset.cpp @@ -46,7 +46,7 @@ void checkResultCode(int resultCode) } // namespace -SessionChangeSet::SessionChangeSet(Utils::span<const byte> blob) +SessionChangeSet::SessionChangeSet(BlobView blob) : data(sqlite3_malloc64(blob.size())) , size(int(blob.size())) { @@ -64,7 +64,7 @@ SessionChangeSet::~SessionChangeSet() sqlite3_free(data); } -Utils::span<const byte> SessionChangeSet::asSpan() const +BlobView SessionChangeSet::asBlobView() const { return {static_cast<const byte *>(data), static_cast<std::size_t>(size)}; } diff --git a/src/libs/sqlite/sqlitesessionchangeset.h b/src/libs/sqlite/sqlitesessionchangeset.h index 65396622a9..8ea2dba008 100644 --- a/src/libs/sqlite/sqlitesessionchangeset.h +++ b/src/libs/sqlite/sqlitesessionchangeset.h @@ -25,10 +25,9 @@ #pragma once +#include "sqliteblob.h" #include "sqliteglobal.h" -#include <utils/span.h> - #include <memory> #include <vector> @@ -41,7 +40,7 @@ class Sessions; class SessionChangeSet { public: - SessionChangeSet(Utils::span<const byte> blob); + SessionChangeSet(BlobView blob); SessionChangeSet(Sessions &session); ~SessionChangeSet(); SessionChangeSet(const SessionChangeSet &) = delete; @@ -54,7 +53,7 @@ public: } void operator=(SessionChangeSet &); - Utils::span<const byte> asSpan() const; + BlobView asBlobView() const; friend void swap(SessionChangeSet &first, SessionChangeSet &second) noexcept { diff --git a/src/libs/sqlite/sqlitesessions.cpp b/src/libs/sqlite/sqlitesessions.cpp index e377ef9b72..69c0e1fe4b 100644 --- a/src/libs/sqlite/sqlitesessions.cpp +++ b/src/libs/sqlite/sqlitesessions.cpp @@ -103,7 +103,7 @@ void Sessions::commit() if (session && !sqlite3session_isempty(session.get())) { SessionChangeSet changeSet{*this}; - insertSession.write(changeSet.asSpan()); + insertSession.write(changeSet.asBlobView()); } session.reset(); diff --git a/src/libs/sqlite/sqlitetimestamp.h b/src/libs/sqlite/sqlitetimestamp.h new file mode 100644 index 0000000000..f30d71fb29 --- /dev/null +++ b/src/libs/sqlite/sqlitetimestamp.h @@ -0,0 +1,47 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +namespace Sqlite { + +class TimeStamp +{ +public: + TimeStamp() = default; + TimeStamp(long long value) + : value(value) + {} + + friend bool operator==(TimeStamp first, TimeStamp second) + { + return first.value == second.value; + } + +public: + long long value = -1; +}; + +} // namespace Sqlite diff --git a/src/libs/sqlite/sqlitevalue.h b/src/libs/sqlite/sqlitevalue.h index 934db37642..8ace8a7bf1 100644 --- a/src/libs/sqlite/sqlitevalue.h +++ b/src/libs/sqlite/sqlitevalue.h @@ -23,6 +23,8 @@ ** ****************************************************************************/ +#pragma once + #include "sqliteexception.h" #include <utils/smallstring.h> @@ -32,9 +34,6 @@ #include <cstddef> - -#pragma once - namespace Sqlite { enum class ValueType : unsigned char { Null, Integer, Float, String }; diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h index 8de1d9168d..ce8440fa38 100644 --- a/src/plugins/projectexplorer/target.h +++ b/src/plugins/projectexplorer/target.h @@ -53,9 +53,11 @@ class PROJECTEXPLORER_EXPORT Target : public QObject friend class SessionManager; // for setActiveBuild and setActiveDeployConfiguration Q_OBJECT - struct _constructor_tag { explicit _constructor_tag() = default; }; - public: + struct _constructor_tag + { + explicit _constructor_tag() = default; + }; Target(Project *parent, Kit *k, _constructor_tag); ~Target() override; diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 291948a7e7..ed9340dcfb 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -6,7 +6,7 @@ endif() add_qtc_plugin(QmlDesigner DEPENDS QmlJS LanguageUtils QmlEditorWidgets AdvancedDockingSystem - Qt5::QuickWidgets Qt5::CorePrivate + Qt5::QuickWidgets Qt5::CorePrivate Sqlite DEFINES DESIGNER_CORE_LIBRARY IDE_LIBRARY_BASENAME=\"${IDE_LIBRARY_BASE_PATH}\" @@ -317,6 +317,7 @@ extend_qtc_plugin(QmlDesigner itemlibraryassetimportdialog.cpp itemlibraryassetimportdialog.h itemlibraryassetimportdialog.ui itemlibraryassetimporter.cpp itemlibraryassetimporter.h + itemlibraryiconimageprovider.cpp itemlibraryiconimageprovider.h ) find_package(Qt5 COMPONENTS Quick3DAssetImport QUIET) @@ -502,6 +503,7 @@ extend_qtc_plugin(QmlDesigner include/textmodifier.h include/variantproperty.h include/viewmanager.h + include/imagecache.h ) extend_qtc_plugin(QmlDesigner @@ -586,6 +588,21 @@ extend_qtc_plugin(QmlDesigner pluginmanager/widgetpluginmanager.cpp pluginmanager/widgetpluginmanager.h pluginmanager/widgetpluginpath.cpp pluginmanager/widgetpluginpath.h rewritertransaction.cpp rewritertransaction.h + + imagecache/imagecachecollector.h + imagecache/imagecachecollector.cpp + imagecache/imagecache.cpp + imagecache/imagecachecollectorinterface.h + imagecache/imagecacheconnectionmanager.cpp + imagecache/imagecacheconnectionmanager.h + imagecache/imagecachegenerator.cpp + imagecache/imagecachegenerator.h + imagecache/imagecachestorage.h + imagecache/imagecachegeneratorinterface.h + imagecache/imagecachestorageinterface.h + imagecache/timestampproviderinterface.h + imagecache/timestampprovider.h + imagecache/timestampprovider.cpp ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri index 0e6219dd9a..8eaa9ce983 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri @@ -7,6 +7,7 @@ qtHaveModule(quick3dassetimport) { # Input HEADERS += itemlibraryview.h \ + $$PWD/itemlibraryiconimageprovider.h \ itemlibrarywidget.h \ itemlibrarymodel.h \ itemlibraryresourceview.h \ @@ -19,6 +20,7 @@ HEADERS += itemlibraryview.h \ customfilesystemmodel.h SOURCES += itemlibraryview.cpp \ + $$PWD/itemlibraryiconimageprovider.cpp \ itemlibrarywidget.cpp \ itemlibrarymodel.cpp \ itemlibraryresourceview.cpp \ diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 1fc817cd01..ca00b66f46 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -502,7 +502,6 @@ bool ItemLibraryAssetImporter::generateComponentIcon(int size, const QString &ic QProcessUniquePointer process = puppetCreator.createPuppetProcess( "custom", {}, - this, std::function<void()>(), [&](int exitCode, QProcess::ExitStatus exitStatus) { processFinished(exitCode, exitStatus); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp new file mode 100644 index 0000000000..e0254111a9 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.cpp @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "itemlibraryiconimageprovider.h" + +#include <projectexplorer/target.h> +#include <utils/stylehelper.h> + +#include <QMetaObject> +#include <QQuickImageResponse> + +namespace QmlDesigner { + +class ImageRespose : public QQuickImageResponse +{ +public: + QQuickTextureFactory *textureFactory() const override + { + return QQuickTextureFactory::textureFactoryForImage(m_image); + } + + void setImage(const QImage &image) + { + m_image = image; + + emit finished(); + } + + void abort() + { + m_image = QImage{ + Utils::StyleHelper::dpiSpecificImageFile(":/ItemLibrary/images/item-default-icon.png")}; + + emit finished(); + } + +private: + QImage m_image; +}; + + +QQuickImageResponse *ItemLibraryIconImageProvider::requestImageResponse(const QString &id, + const QSize &) +{ + auto response = std::make_unique<ImageRespose>(); + + m_cache.requestIcon( + id, + [response = QPointer<ImageRespose>(response.get())](const QImage &image) { + QMetaObject::invokeMethod( + response, + [response, image] { + if (response) + response->setImage(image); + }, + Qt::QueuedConnection); + }, + [response = QPointer<ImageRespose>(response.get())] { + QMetaObject::invokeMethod( + response, + [response] { + if (response) + response->abort(); + }, + Qt::QueuedConnection); + }); + + return response.release(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h new file mode 100644 index 0000000000..9e60246d4d --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryiconimageprovider.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <nodeinstanceview.h> +#include <rewriterview.h> + +#include <coreplugin/icore.h> +#include <imagecache.h> +#include <imagecache/imagecachecollector.h> +#include <imagecache/imagecacheconnectionmanager.h> +#include <imagecache/imagecachegenerator.h> +#include <imagecache/imagecachestorage.h> +#include <imagecache/timestampprovider.h> + +#include <sqlitedatabase.h> + +#include <QQuickAsyncImageProvider> + +namespace QmlDesigner { + +class ItemLibraryIconImageProvider : public QQuickAsyncImageProvider +{ +public: + ItemLibraryIconImageProvider(ImageCache &imageCache) + : m_cache{imageCache} + {} + + QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; + +private: + ImageCache &m_cache; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp index d67631531c..0d46140f12 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.cpp @@ -47,8 +47,18 @@ QString ItemLibraryItem::typeName() const QString ItemLibraryItem::itemLibraryIconPath() const { - //Prepend image provider prefix - return QStringLiteral("image://qmldesigner_itemlibrary/") + m_itemLibraryEntry.libraryEntryIconPath(); + if (m_itemLibraryEntry.customComponentSource().isEmpty()) { + return QStringLiteral("image://qmldesigner_itemlibrary/") + + m_itemLibraryEntry.libraryEntryIconPath(); + } else { + return QStringLiteral("image://itemlibrary_preview/") + + m_itemLibraryEntry.customComponentSource(); + } +} + +QString ItemLibraryItem::componentPath() const +{ + return m_itemLibraryEntry.customComponentSource(); } bool ItemLibraryItem::setVisible(bool isVisible) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h index fa1fa92257..859cbe51dd 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryitem.h @@ -42,6 +42,7 @@ class ItemLibraryItem: public QObject { Q_PROPERTY(QString itemName READ itemName FINAL) Q_PROPERTY(QString itemLibraryIconPath READ itemLibraryIconPath FINAL) Q_PROPERTY(bool itemVisible READ isVisible NOTIFY visibilityChanged FINAL) + Q_PROPERTY(QString componentPath READ componentPath FINAL) public: ItemLibraryItem(QObject *parent); @@ -50,6 +51,7 @@ public: QString itemName() const; QString typeName() const; QString itemLibraryIconPath() const; + QString componentPath() const; bool setVisible(bool isVisible); bool isVisible() const; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp index a01ce38fc7..19506042bb 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp @@ -29,6 +29,8 @@ #include "itemlibraryitem.h" #include "itemlibrarysection.h" +#include <components/previewtooltip/previewtooltipbackend.h> + #include <model.h> #include <nodehints.h> #include <nodemetainfo.h> diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp index 5ffece61cd..79d559c0a0 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp @@ -25,24 +25,47 @@ #include "itemlibraryview.h" #include "itemlibrarywidget.h" +#include "metainfo.h" +#include <bindingproperty.h> +#include <coreplugin/icore.h> +#include <imagecache.h> +#include <imagecache/imagecachecollector.h> +#include <imagecache/imagecacheconnectionmanager.h> +#include <imagecache/imagecachegenerator.h> +#include <imagecache/imagecachestorage.h> +#include <imagecache/timestampprovider.h> #include <import.h> #include <importmanagerview.h> -#include <qmlitemnode.h> -#include <rewriterview.h> -#include <bindingproperty.h> #include <nodelistproperty.h> +#include <projectexplorer/kit.h> +#include <projectexplorer/target.h> +#include <rewriterview.h> +#include <sqlitedatabase.h> #include <utils/algorithm.h> #include <qmldesignerplugin.h> -#include "metainfo.h" +#include <qmlitemnode.h> namespace QmlDesigner { +class ImageCacheData +{ +public: + Sqlite::Database database{ + Utils::PathString{Core::ICore::cacheResourcePath() + "/imagecache-v1.db"}}; + ImageCacheStorage<Sqlite::Database> storage{database}; + ImageCacheConnectionManager connectionManager; + ImageCacheCollector collector{connectionManager}; + ImageCacheGenerator generator{collector, storage}; + TimeStampProvider timeStampProvider; + ImageCache cache{storage, generator, timeStampProvider}; +}; + ItemLibraryView::ItemLibraryView(QObject* parent) : AbstractView(parent), m_importManagerView(new ImportManagerView(this)) { - + m_imageCacheData = std::make_unique<ImageCacheData>(); } ItemLibraryView::~ItemLibraryView() = default; @@ -55,7 +78,7 @@ bool ItemLibraryView::hasWidget() const WidgetInfo ItemLibraryView::widgetInfo() { if (m_widget.isNull()) { - m_widget = new ItemLibraryWidget; + m_widget = new ItemLibraryWidget{m_imageCacheData->cache}; m_widget->setImportsWidget(m_importManagerView->widgetInfo().widget); } @@ -70,6 +93,16 @@ WidgetInfo ItemLibraryView::widgetInfo() void ItemLibraryView::modelAttached(Model *model) { AbstractView::modelAttached(model); + auto target = QmlDesignerPlugin::instance()->currentDesignDocument()->currentTarget(); + m_imageCacheData->cache.clean(); + + if (target) { + auto clonedTarget = std::make_unique<ProjectExplorer::Target>( + target->project(), target->kit()->clone(), ProjectExplorer::Target::_constructor_tag{}); + + m_imageCacheData->collector.setTarget(std::move(clonedTarget)); + } + m_widget->clearSearchFilter(); m_widget->setModel(model); updateImports(); @@ -83,6 +116,8 @@ void ItemLibraryView::modelAboutToBeDetached(Model *model) { model->detachView(m_importManagerView); + m_imageCacheData->collector.setTarget({}); + AbstractView::modelAboutToBeDetached(model); m_widget->setModel(nullptr); @@ -124,7 +159,7 @@ void ItemLibraryView::importsChanged(const QList<Import> &addedImports, const QL void ItemLibraryView::setResourcePath(const QString &resourcePath) { if (m_widget.isNull()) - m_widget = new ItemLibraryWidget; + m_widget = new ItemLibraryWidget{m_imageCacheData->cache}; m_widget->setResourcePath(resourcePath); } @@ -142,4 +177,4 @@ void ItemLibraryView::updateImports() m_widget->delayedUpdateModel(); } -} //QmlDesigner +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h index 09535c3230..3d4af5d21c 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.h @@ -34,6 +34,7 @@ namespace QmlDesigner { class ItemLibraryWidget; class ImportManagerView; +class ImageCacheData; class ItemLibraryView : public AbstractView { @@ -58,6 +59,7 @@ protected: void updateImports(); private: + std::unique_ptr<ImageCacheData> m_imageCacheData; QPointer<ItemLibraryWidget> m_widget; ImportManagerView *m_importManagerView; bool m_hasErrors = false; diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 45e5601135..10a253f5b6 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -27,19 +27,21 @@ #include "customfilesystemmodel.h" #include "itemlibraryassetimportdialog.h" +#include "itemlibraryiconimageprovider.h" #include <theme.h> -#include <itemlibrarymodel.h> +#include <designeractionmanager.h> +#include <designermcumanager.h> #include <itemlibraryimageprovider.h> #include <itemlibraryinfo.h> +#include <itemlibrarymodel.h> #include <metainfo.h> #include <model.h> +#include <previewtooltip/previewtooltipbackend.h> #include <rewritingexception.h> -#include <qmldesignerplugin.h> #include <qmldesignerconstants.h> -#include <designeractionmanager.h> -#include <designermcumanager.h> +#include <qmldesignerplugin.h> #include <utils/algorithm.h> #include <utils/flowlayout.h> @@ -81,14 +83,14 @@ static QString propertyEditorResourcesPath() { return Core::ICore::resourcePath() + QStringLiteral("/qmldesigner/propertyEditorQmlSources"); } -ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : - QFrame(parent), - m_itemIconSize(24, 24), - m_itemViewQuickWidget(new QQuickWidget(this)), - m_resourcesView(new ItemLibraryResourceView(this)), - m_importTagsWidget(new QWidget(this)), - m_addResourcesWidget(new QWidget(this)), - m_filterFlag(QtBasic) +ItemLibraryWidget::ItemLibraryWidget(ImageCache &imageCache) + : m_itemIconSize(24, 24) + , m_itemViewQuickWidget(new QQuickWidget(this)) + , m_resourcesView(new ItemLibraryResourceView(this)) + , m_importTagsWidget(new QWidget(this)) + , m_addResourcesWidget(new QWidget(this)) + , m_imageCache{imageCache} + , m_filterFlag(QtBasic) { m_compressionTimer.setInterval(200); m_compressionTimer.setSingleShot(true); @@ -102,16 +104,20 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : m_itemViewQuickWidget->engine()->addImportPath(propertyEditorResourcesPath() + "/imports"); m_itemLibraryModel = new ItemLibraryModel(this); - m_itemViewQuickWidget->rootContext()->setContextProperties( - QVector<QQmlContext::PropertyPair>{ - {{"itemLibraryModel"}, QVariant::fromValue(m_itemLibraryModel.data())}, - {{"itemLibraryIconWidth"}, m_itemIconSize.width()}, - {{"itemLibraryIconHeight"}, m_itemIconSize.height()}, - {{"rootView"}, QVariant::fromValue(this)}, - {{"highlightColor"}, Utils::StyleHelper::notTooBrightHighlightColor()} - } - ); - m_itemViewQuickWidget->setClearColor(Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); + m_itemViewQuickWidget->rootContext()->setContextProperties(QVector<QQmlContext::PropertyPair>{ + {{"itemLibraryModel"}, QVariant::fromValue(m_itemLibraryModel.data())}, + {{"itemLibraryIconWidth"}, m_itemIconSize.width()}, + {{"itemLibraryIconHeight"}, m_itemIconSize.height()}, + {{"rootView"}, QVariant::fromValue(this)}, + {{"highlightColor"}, Utils::StyleHelper::notTooBrightHighlightColor()}, + }); + + m_previewTooltipBackend = std::make_unique<PreviewTooltipBackend>(m_imageCache); + m_itemViewQuickWidget->rootContext()->setContextProperty("tooltipBackend", + m_previewTooltipBackend.get()); + + m_itemViewQuickWidget->setClearColor( + Theme::getColor(Theme::Color::QmlDesigner_BackgroundColorDarkAlternate)); /* create Resources view and its model */ m_resourcesFileSystemModel = new CustomFileSystemModel(this); @@ -119,6 +125,7 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : /* create image provider for loading item icons */ m_itemViewQuickWidget->engine()->addImageProvider(QStringLiteral("qmldesigner_itemlibrary"), new Internal::ItemLibraryImageProvider); + Theme::setupTheme(m_itemViewQuickWidget->engine()); /* other widgets */ @@ -243,6 +250,8 @@ ItemLibraryWidget::ItemLibraryWidget(QWidget *parent) : reloadQmlSource(); } +ItemLibraryWidget::~ItemLibraryWidget() = default; + void ItemLibraryWidget::setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo) { if (m_itemLibraryInfo.data() == itemLibraryInfo) @@ -306,9 +315,14 @@ void ItemLibraryWidget::delayedUpdateModel() void ItemLibraryWidget::setModel(Model *model) { + m_itemViewQuickWidget->engine()->removeImageProvider("itemlibrary_preview"); m_model = model; if (!model) return; + + m_itemViewQuickWidget->engine()->addImageProvider("itemlibrary_preview", + new ItemLibraryIconImageProvider{m_imageCache}); + setItemLibraryInfo(model->metaInfo().itemLibraryInfo()); } @@ -318,7 +332,8 @@ void ItemLibraryWidget::setCurrentIndexOfStackedWidget(int index) m_filterLineEdit->setVisible(false); m_importTagsWidget->setVisible(true); m_addResourcesWidget->setVisible(false); - } if (index == 1) { + } + if (index == 1) { m_filterLineEdit->setVisible(true); m_importTagsWidget->setVisible(false); m_addResourcesWidget->setVisible(true); @@ -564,5 +579,4 @@ void ItemLibraryWidget::addResources() } } } - -} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h index 11dea7d0c1..bfe9106a23 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.h @@ -37,6 +37,8 @@ #include <QQmlPropertyMap> #include <QTimer> +#include <memory> + QT_BEGIN_NAMESPACE class QStackedWidget; class QShortcut; @@ -52,6 +54,9 @@ class CustomFileSystemModel; class ItemLibraryModel; class ItemLibraryResourceView; +class PreviewTooltipBackend; +class ImageCache; +class ImageCacheCollector; class ItemLibraryWidget : public QFrame { @@ -63,7 +68,8 @@ class ItemLibraryWidget : public QFrame }; public: - ItemLibraryWidget(QWidget *parent = nullptr); + ItemLibraryWidget(ImageCache &imageCache); + ~ItemLibraryWidget(); void setItemLibraryInfo(ItemLibraryInfo *itemLibraryInfo); QList<QToolButton *> createToolBarWidgets(); @@ -115,9 +121,10 @@ private: QScopedPointer<ItemLibraryResourceView> m_resourcesView; QScopedPointer<QWidget> m_importTagsWidget; QScopedPointer<QWidget> m_addResourcesWidget; + std::unique_ptr<PreviewTooltipBackend> m_previewTooltipBackend; QShortcut *m_qmlSourceUpdateShortcut; - + ImageCache &m_imageCache; QPointer<Model> m_model; FilterChangeFlag m_filterFlag; ItemLibraryEntry m_currentitemLibraryEntry; diff --git a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui index ccfcb0a6c4..65e7cb01ce 100644 --- a/src/plugins/qmldesigner/components/navigator/previewtooltip.ui +++ b/src/plugins/qmldesigner/components/navigator/previewtooltip.ui @@ -82,12 +82,6 @@ </property> <item> <widget class="QLabel" name="imageLabel"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> <property name="minimumSize"> <size> <width>150</width> @@ -100,9 +94,6 @@ <property name="frameShadow"> <enum>QFrame::Plain</enum> </property> - <property name="text"> - <string notr="true"><image></string> - </property> <property name="alignment"> <set>Qt::AlignCenter</set> </property> diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp new file mode 100644 index 0000000000..d3c972c217 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.cpp @@ -0,0 +1,63 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "previewimagetooltip.h" +#include "ui_previewimagetooltip.h" + +#include <utils/theme/theme.h> + +#include <QtGui/qpixmap.h> + +namespace QmlDesigner { + +PreviewImageTooltip::PreviewImageTooltip(QWidget *parent) + : QWidget(parent) + , m_ui(std::make_unique<Ui::PreviewImageTooltip>()) +{ + // setAttribute(Qt::WA_TransparentForMouseEvents); + setWindowFlags(Qt::ToolTip); + m_ui->setupUi(this); + setStyleSheet(QString("QWidget { background-color: %1 }").arg(Utils::creatorTheme()->color(Utils::Theme::BackgroundColorNormal).name())); +} + +PreviewImageTooltip::~PreviewImageTooltip() = default; + +void PreviewImageTooltip::setComponentPath(const QString &path) +{ + m_ui->componentPathLabel->setText(path); +} + +void PreviewImageTooltip::setComponentName(const QString &name) +{ + m_ui->componentNameLabel->setText(name); +} + +void PreviewImageTooltip::setImage(const QImage &image) +{ + resize(image.width() + 20 + m_ui->componentNameLabel->width(), + std::max(image.height() + 20, height())); + m_ui->imageLabel->setPixmap(QPixmap::fromImage({image})); +} +} diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h new file mode 100644 index 0000000000..e05b8a0727 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QtWidgets/qwidget.h> +#include <QtGui/qpixmap.h> + +#include <memory> + +namespace QmlDesigner { +namespace Ui { +class PreviewImageTooltip; +} + +class PreviewImageTooltip : public QWidget +{ + Q_OBJECT + +public: + explicit PreviewImageTooltip(QWidget *parent = {}); + ~PreviewImageTooltip(); + + void setComponentPath(const QString &path); + void setComponentName(const QString &name); + void setImage(const QImage &pixmap); + +private: + std::unique_ptr<Ui::PreviewImageTooltip> m_ui; +}; +} diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui new file mode 100644 index 0000000000..16f34fae07 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewimagetooltip.ui @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>QmlDesigner::PreviewImageTooltip</class> + <widget class="QWidget" name="QmlDesigner::PreviewImageTooltip"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>300</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>200</width> + <height>200</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>1000</width> + <height>1000</height> + </size> + </property> + <property name="windowTitle"> + <string/> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="sizeGripEnabled" stdset="0"> + <bool>false</bool> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>1</number> + </property> + <property name="topMargin"> + <number>1</number> + </property> + <property name="rightMargin"> + <number>0</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="1"> + <widget class="QLabel" name="componentPathLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + <item row="0" column="0" rowspan="2"> + <widget class="QLabel" name="imageLabel"> + <property name="frameShape"> + <enum>QFrame::Box</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Plain</enum> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="Utils::ElidingLabel" name="componentNameLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>1</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="font"> + <font> + <pointsize>12</pointsize> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string notr="true"/> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + <property name="textInteractionFlags"> + <set>Qt::NoTextInteraction</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>Utils::ElidingLabel</class> + <extends>QLabel</extends> + <header location="global">utils/elidinglabel.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp new file mode 100644 index 0000000000..559cd5c17b --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.cpp @@ -0,0 +1,110 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "previewtooltipbackend.h" + +#include "previewimagetooltip.h" + +#include <coreplugin/icore.h> +#include <imagecache.h> + +#include <QApplication> +#include <QDesktopWidget> +#include <QMetaObject> + +namespace QmlDesigner { + +PreviewTooltipBackend::PreviewTooltipBackend(ImageCache &cache) + : m_cache{cache} +{} + +PreviewTooltipBackend::~PreviewTooltipBackend() +{ + hideTooltip(); +} + +void PreviewTooltipBackend::showTooltip() +{ + if (m_componentPath.isEmpty()) + return; + + m_tooltip = std::make_unique<PreviewImageTooltip>(); + + m_tooltip->setComponentName(m_componentName); + m_tooltip->setComponentPath(m_componentPath); + + m_cache.requestImage( + m_componentPath, + [tooltip = QPointer<PreviewImageTooltip>(m_tooltip.get())](const QImage &image) { + QMetaObject::invokeMethod(tooltip, [tooltip, image] { + if (tooltip) + tooltip->setImage(image); + }); + }, + [] {}); + + auto desktopWidget = QApplication::desktop(); + auto mousePosition = desktopWidget->cursor().pos(); + + mousePosition += {20, 20}; + m_tooltip->move(mousePosition); + m_tooltip->show(); +} + +void PreviewTooltipBackend::hideTooltip() +{ + if (m_tooltip) + m_tooltip->hide(); + + m_tooltip.reset(); +} + +QString QmlDesigner::PreviewTooltipBackend::componentPath() const +{ + return m_componentPath; +} + +void QmlDesigner::PreviewTooltipBackend::setComponentPath(const QString &path) +{ + m_componentPath = path; + + if (m_componentPath != path) + emit componentPathChanged(); +} + +QString QmlDesigner::PreviewTooltipBackend::componentName() const +{ + return m_componentName; +} + +void QmlDesigner::PreviewTooltipBackend::setComponentName(const QString &name) +{ + m_componentName = name; + + if (m_componentName != name) + emit componentNameChanged(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h new file mode 100644 index 0000000000..b5f777662b --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.h @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QObject> +#include <QQmlEngine> + +#include <memory> + +namespace QmlDesigner { + +class PreviewImageTooltip; +class ImageCache; + +class PreviewTooltipBackend : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString componentPath READ componentPath WRITE setComponentPath NOTIFY componentPathChanged) + Q_PROPERTY(QString componentName READ componentName WRITE setComponentName NOTIFY componentNameChanged) + +public: + PreviewTooltipBackend(ImageCache &cache); + ~PreviewTooltipBackend(); + + Q_INVOKABLE void showTooltip(); + Q_INVOKABLE void hideTooltip(); + + QString componentPath() const; + void setComponentPath(const QString &path); + + QString componentName() const; + void setComponentName(const QString &path); + +signals: + void componentPathChanged(); + void componentNameChanged(); + +private: + QString m_componentPath; + QString m_componentName; + std::unique_ptr<PreviewImageTooltip> m_tooltip; + ImageCache &m_cache; +}; + +} + +QML_DECLARE_TYPE(QmlDesigner::PreviewTooltipBackend) diff --git a/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri new file mode 100644 index 0000000000..a33c5d9853 --- /dev/null +++ b/src/plugins/qmldesigner/components/previewtooltip/previewtooltipbackend.pri @@ -0,0 +1,11 @@ +HEADERS += \ + $$PWD/previewtooltipbackend.h \ + $$PWD/previewimagetooltip.h + +SOURCES += \ + $$PWD/previewtooltipbackend.cpp \ + $$PWD/previewimagetooltip.cpp + +FORMS += $$PWD/previewimagetooltip.ui + + diff --git a/src/plugins/qmldesigner/designercore/designercore-lib.pri b/src/plugins/qmldesigner/designercore/designercore-lib.pri index 08842527ee..981025d1fb 100644 --- a/src/plugins/qmldesigner/designercore/designercore-lib.pri +++ b/src/plugins/qmldesigner/designercore/designercore-lib.pri @@ -14,6 +14,7 @@ include (../../../../share/qtcreator/qml/qmlpuppet/container/container.pri) include (../../../../share/qtcreator/qml/qmlpuppet/types/types.pri) SOURCES += $$PWD/model/abstractview.cpp \ + $$PWD/imagecache/imagecachecollector.cpp \ $$PWD/model/rewriterview.cpp \ $$PWD/model/documentmessage.cpp \ $$PWD/metainfo/metainfo.cpp \ @@ -84,9 +85,15 @@ SOURCES += $$PWD/model/abstractview.cpp \ $$PWD/model/qmltimeline.cpp \ $$PWD/model/qmltimelinekeyframegroup.cpp \ $$PWD/model/annotation.cpp \ - $$PWD/model/stylesheetmerger.cpp + $$PWD/model/stylesheetmerger.cpp \ + $$PWD/imagecache/imagecache.cpp \ + $$PWD/imagecache/imagecacheconnectionmanager.cpp \ + $$PWD/imagecache/imagecachegenerator.cpp \ + $$PWD/imagecache/timestampprovider.cpp + HEADERS += $$PWD/include/qmldesignercorelib_global.h \ + $$PWD/imagecache/imagecachecollector.h \ $$PWD/include/abstractview.h \ $$PWD/include/nodeinstanceview.h \ $$PWD/include/rewriterview.h \ @@ -162,7 +169,17 @@ HEADERS += $$PWD/include/qmldesignercorelib_global.h \ $$PWD/include/qmltimeline.h \ $$PWD/include/qmltimelinekeyframegroup.h \ $$PWD/include/annotation.h \ - $$PWD/include/stylesheetmerger.h + $$PWD/include/stylesheetmerger.h \ + $$PWD/include/imagecache.h \ + $$PWD/imagecache/imagecachecollectorinterface.h \ + $$PWD/imagecache/imagecacheconnectionmanager.h \ + $$PWD/imagecache/imagecachegeneratorinterface.h \ + $$PWD/imagecache/imagecachestorageinterface.h \ + $$PWD/imagecache/imagecachegenerator.h \ + $$PWD/imagecache/imagecachestorage.h \ + $$PWD/imagecache/timestampprovider.h \ + $$PWD/imagecache/timestampproviderinterface.h + FORMS += \ $$PWD/instances/puppetdialog.ui diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp new file mode 100644 index 0000000000..20409eb7fa --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecache.cpp @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecache.h" + +#include "imagecachegenerator.h" +#include "imagecachestorage.h" +#include "timestampprovider.h" + +#include <thread> + +namespace QmlDesigner { + +ImageCache::ImageCache(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) + : m_storage(storage) + , m_generator(generator) + , m_timeStampProvider(timeStampProvider) +{ + m_backgroundThread = std::thread{[this] { + while (isRunning()) { + if (auto [hasEntry, entry] = getEntry(); hasEntry) { + request(entry.name, + entry.requestType, + std::move(entry.captureCallback), + std::move(entry.abortCallback), + m_storage, + m_generator, + m_timeStampProvider); + } + + waitForEntries(); + } + }}; +} + +ImageCache::~ImageCache() +{ + clean(); + stopThread(); + m_condition.notify_all(); + if (m_backgroundThread.joinable()) + m_backgroundThread.join(); +} + +void ImageCache::request(Utils::SmallStringView name, + ImageCache::RequestType requestType, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider) +{ + const auto timeStamp = timeStampProvider.timeStamp(name); + const auto entry = requestType == RequestType::Image ? storage.fetchImage(name, timeStamp) + : storage.fetchIcon(name, timeStamp); + + if (entry.hasEntry) { + if (entry.image.isNull()) + abortCallback(); + else + captureCallback(entry.image); + } else { + generator.generateImage(name, timeStamp, std::move(captureCallback), std::move(abortCallback)); + } +} + +void ImageCache::requestImage(Utils::PathString name, + ImageCache::CaptureCallback captureCallback, + AbortCallback abortCallback) +{ + addEntry(std::move(name), std::move(captureCallback), std::move(abortCallback), RequestType::Image); + m_condition.notify_all(); +} + +void ImageCache::requestIcon(Utils::PathString name, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback) +{ + addEntry(std::move(name), std::move(captureCallback), std::move(abortCallback), RequestType::Icon); + m_condition.notify_all(); +} + +void ImageCache::clean() +{ + clearEntries(); + m_generator.clean(); +} + +std::tuple<bool, ImageCache::Entry> ImageCache::getEntry() +{ + std::unique_lock lock{m_mutex}; + + if (m_entries.empty()) + return {false, Entry{}}; + + Entry entry = m_entries.back(); + m_entries.pop_back(); + + return {true, entry}; +} + +void ImageCache::addEntry(Utils::PathString &&name, + ImageCache::CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType) +{ + std::unique_lock lock{m_mutex}; + + m_entries.emplace_back(std::move(name), + std::move(captureCallback), + std::move(abortCallback), + requestType); +} + +void ImageCache::clearEntries() +{ + std::unique_lock lock{m_mutex}; + for (Entry &entry : m_entries) + entry.abortCallback(); + m_entries.clear(); +} + +void ImageCache::waitForEntries() +{ + std::unique_lock lock{m_mutex}; + if (m_entries.empty()) + m_condition.wait(lock, [&] { return m_entries.size() || m_finishing; }); +} + +void ImageCache::stopThread() +{ + std::unique_lock lock{m_mutex}; + m_finishing = true; +} + +bool ImageCache::isRunning() +{ + std::unique_lock lock{m_mutex}; + return !m_finishing; +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp new file mode 100644 index 0000000000..1bb7262d17 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.cpp @@ -0,0 +1,117 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachecollector.h" +#include "imagecacheconnectionmanager.h" + +#include <metainfo.h> +#include <model.h> +#include <nodeinstanceview.h> +#include <plaintexteditmodifier.h> +#include <rewriterview.h> + +#include <projectexplorer/project.h> +#include <projectexplorer/target.h> +#include <utils/fileutils.h> + +#include <QPlainTextEdit> + +namespace QmlDesigner { + +namespace { + +QByteArray fileToByteArray(QString const &filename) +{ + QFile file(filename); + QFileInfo fleInfo(file); + + if (fleInfo.exists() && file.open(QFile::ReadOnly)) + return file.readAll(); + + return {}; +} + +QString fileToString(const QString &filename) +{ + return QString::fromUtf8(fileToByteArray(filename)); +} + +} // namespace + +ImageCacheCollector::ImageCacheCollector(ImageCacheConnectionManager &connectionManager) + : m_connectionManager{connectionManager} +{} + +ImageCacheCollector::~ImageCacheCollector() = default; + +void ImageCacheCollector::start(Utils::SmallStringView name, + CaptureCallback captureCallback, + AbortCallback abortCallback) +{ + RewriterView rewriterView{RewriterView::Amend, nullptr}; + NodeInstanceView nodeInstanceView{m_connectionManager}; + + const QString filePath{name}; + std::unique_ptr<Model> model{QmlDesigner::Model::create("QtQuick/Item", 2, 1)}; + model->setFileUrl(QUrl::fromLocalFile(filePath)); + + auto textDocument = std::make_unique<QTextDocument>(fileToString(filePath)); + + auto modifier = std::make_unique<NotIndentingTextEditModifier>(textDocument.get(), + QTextCursor{textDocument.get()}); + + rewriterView.setTextModifier(modifier.get()); + + model->setRewriterView(&rewriterView); + + if (rewriterView.inErrorState() || !rewriterView.rootModelNode().metaInfo().isGraphicalItem()) { + abortCallback(); + return; + } + + m_connectionManager.setCallback(std::move(captureCallback)); + + nodeInstanceView.setTarget(m_target.get()); + nodeInstanceView.setCrashCallback(abortCallback); + model->setNodeInstanceView(&nodeInstanceView); + + bool capturedDataArrived = m_connectionManager.waitForCapturedData(); + + m_connectionManager.setCallback({}); + m_connectionManager.setCrashCallback({}); + + model->setNodeInstanceView({}); + model->setRewriterView({}); + + if (!capturedDataArrived) + abortCallback(); +} + +void ImageCacheCollector::setTarget(std::unique_ptr<ProjectExplorer::Target> target) +{ + m_target = std::move(target); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h new file mode 100644 index 0000000000..e39f95f573 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollector.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "imagecachecollectorinterface.h" + +#include <memory> + +QT_BEGIN_NAMESPACE +class QTextDocument; +QT_END_NAMESPACE + +namespace ProjectExplorer { +class Target; +} + +namespace QmlDesigner { + +class Model; +class NotIndentingTextEditModifier; +class ImageCacheConnectionManager; +class RewriterView; +class NodeInstanceView; + +class ImageCacheCollector final : public ImageCacheCollectorInterface +{ +public: + ImageCacheCollector(ImageCacheConnectionManager &connectionManager); + + ~ImageCacheCollector(); + + void start(Utils::SmallStringView filePath, + CaptureCallback captureCallback, + AbortCallback abortCallback) override; + + void setTarget(std::unique_ptr<ProjectExplorer::Target> target); + +private: + ImageCacheConnectionManager &m_connectionManager; + std::unique_ptr<ProjectExplorer::Target> m_target; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h new file mode 100644 index 0000000000..e6528f2ec3 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachecollectorinterface.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class ImageCacheCollectorInterface +{ +public: + using CaptureCallback = std::function<void(QImage &&image)>; + using AbortCallback = std::function<void()>; + + virtual void start(Utils::SmallStringView filePath, + CaptureCallback captureCallback, + AbortCallback abortCallback) + = 0; + +protected: + ~ImageCacheCollectorInterface() = default; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp new file mode 100644 index 0000000000..2c7982bd1f --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +****************************************************************************/ + +#include "imagecacheconnectionmanager.h" + +#include <captureddatacommand.h> + +#include <QLocalSocket> + +namespace QmlDesigner { + +ImageCacheConnectionManager::ImageCacheConnectionManager() +{ + connections().emplace_back("Capture icon", "captureiconmode"); +} + +void ImageCacheConnectionManager::setCallback(ImageCacheConnectionManager::Callback callback) +{ + m_captureCallback = std::move(callback); +} + +bool ImageCacheConnectionManager::waitForCapturedData() +{ + if (connections().empty()) + return false; + + disconnect(connections().front().socket.get(), &QIODevice::readyRead, nullptr, nullptr); + + while (!m_capturedDataArrived) { + bool dataArrived = connections().front().socket->waitForReadyRead(600000); + + if (!dataArrived) + return false; + + readDataStream(connections().front()); + } + + m_capturedDataArrived = false; + + return true; +} + +void ImageCacheConnectionManager::dispatchCommand(const QVariant &command, + ConnectionManagerInterface::Connection &) +{ + static const int capturedDataCommandType = QMetaType::type("CapturedDataCommand"); + + if (command.userType() == capturedDataCommandType) { + m_captureCallback(command.value<CapturedDataCommand>().image); + m_capturedDataArrived = true; + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h new file mode 100644 index 0000000000..5788f6f31d --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecacheconnectionmanager.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +****************************************************************************/ + +#pragma once + +#include <designercore/instances/connectionmanager.h> + +namespace QmlDesigner { + +class CapturedDataCommand; + +class ImageCacheConnectionManager : public ConnectionManager +{ +public: + using Callback = std::function<void(QImage &&)>; + + ImageCacheConnectionManager(); + + void setCallback(Callback captureCallback); + + bool waitForCapturedData(); + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + Callback m_captureCallback; + bool m_capturedDataArrived = false; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp new file mode 100644 index 0000000000..a6783fbf48 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachegenerator.h" + +#include "imagecachecollectorinterface.h" +#include "imagecachestorage.h" + +#include <QThread> + +namespace QmlDesigner { + +ImageCacheGenerator::~ImageCacheGenerator() +{ + std::lock_guard threadLock{*m_threadMutex.get()}; + + if (m_backgroundThread) + m_backgroundThread->wait(); + + clean(); +} + +void ImageCacheGenerator::generateImage(Utils::SmallStringView name, + Sqlite::TimeStamp timeStamp, + ImageCacheGeneratorInterface::CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) +{ + { + std::lock_guard lock{m_dataMutex}; + m_tasks.emplace_back(name, timeStamp, std::move(captureCallback), std::move(abortCallback)); + } + + startGenerationAsynchronously(); +} + +void ImageCacheGenerator::clean() +{ + std::lock_guard dataLock{m_dataMutex}; + m_tasks.clear(); +} + +class ReleaseProcessing +{ +public: + ReleaseProcessing(std::atomic_flag &processing) + : m_processing(processing) + { + m_processing.test_and_set(std::memory_order_acquire); + } + + ~ReleaseProcessing() { m_processing.clear(std::memory_order_release); } + +private: + std::atomic_flag &m_processing; +}; + +void ImageCacheGenerator::startGeneration(std::shared_ptr<std::mutex> threadMutex) +{ + ReleaseProcessing guard(m_processing); + + while (true) { + Task task; + + { + std::unique_lock threadLock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!threadLock.try_lock()) + return; + + std::lock_guard dataLock{m_dataMutex}; + + if (m_tasks.empty()) { + m_storage.walCheckpointFull(); + return; + } + + task = std::move(m_tasks.back()); + + m_tasks.pop_back(); + } + + m_collector.start( + task.filePath, + [this, threadMutex, task](QImage &&image) { + std::unique_lock lock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (threadMutex.use_count() == 1) + return; + + if (image.isNull()) + task.abortCallback(); + else + task.captureCallback(image); + + m_storage.storeImage(std::move(task.filePath), task.timeStamp, image); + }, + [this, threadMutex, task] { + std::unique_lock lock{*threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (threadMutex.use_count() == 1) + return; + + task.abortCallback(); + m_storage.storeImage(std::move(task.filePath), task.timeStamp, {}); + }); + } +} + +void ImageCacheGenerator::startGenerationAsynchronously() +{ + if (m_processing.test_and_set(std::memory_order_acquire)) + return; + + std::unique_lock lock{*m_threadMutex.get(), std::defer_lock_t{}}; + + if (!lock.try_lock()) + return; + + if (m_backgroundThread) + m_backgroundThread->wait(); + + m_backgroundThread.reset(QThread::create( + [this](std::shared_ptr<std::mutex> threadMutex) { startGeneration(threadMutex); }, + m_threadMutex)); + m_backgroundThread->start(); + // m_backgroundThread = std::thread( + // [this](std::shared_ptr<std::mutex> threadMutex) { startGeneration(threadMutex); }, + // m_threadMutex); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h new file mode 100644 index 0000000000..207622714b --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegenerator.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "imagecachegeneratorinterface.h" + +#include <utils/smallstring.h> + +#include <QThread> + +#include <memory> +#include <mutex> + +QT_BEGIN_NAMESPACE +class QPlainTextEdit; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ImageCacheCollectorInterface; +class ImageCacheStorageInterface; + +class ImageCacheGenerator final : public ImageCacheGeneratorInterface +{ +public: + ImageCacheGenerator(ImageCacheCollectorInterface &collector, ImageCacheStorageInterface &storage) + : m_collector{collector} + , m_storage(storage) + {} + + ~ImageCacheGenerator(); + + void generateImage(Utils::SmallStringView filePath, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) override; + void clean() override; + +private: + struct Task + { + Task() = default; + Task(Utils::SmallStringView filePath, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) + : filePath(filePath) + , captureCallback(std::move(captureCallback)) + , abortCallback(std::move(abortCallback)) + , timeStamp(timeStamp) + {} + + Utils::PathString filePath; + CaptureCallback captureCallback; + AbortCallback abortCallback; + Sqlite::TimeStamp timeStamp; + }; + + void startGeneration(std::shared_ptr<std::mutex> threadMutex); + void startGenerationAsynchronously(); + +private: + std::unique_ptr<QThread> m_backgroundThread; + std::mutex m_dataMutex; + std::shared_ptr<std::mutex> m_threadMutex{std::make_shared<std::mutex>()}; + std::vector<Task> m_tasks; + ImageCacheCollectorInterface &m_collector; + ImageCacheStorageInterface &m_storage; + std::atomic_flag m_processing = ATOMIC_FLAG_INIT; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h new file mode 100644 index 0000000000..26b9621995 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachegeneratorinterface.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class ImageCacheGeneratorInterface +{ +public: + using CaptureCallback = std::function<void(const QImage &image)>; + using AbortCallback = std::function<void()>; + + virtual void generateImage(Utils::SmallStringView name, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback) + = 0; + + virtual void clean() = 0; + +protected: + ~ImageCacheGeneratorInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h new file mode 100644 index 0000000000..90900cf19e --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorage.h @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "imagecachestorageinterface.h" + +#include <createtablesqlstatementbuilder.h> + +#include <sqliteblob.h> +#include <sqlitereadstatement.h> +#include <sqlitetable.h> +#include <sqlitetransaction.h> +#include <sqlitewritestatement.h> + +#include <QBuffer> +#include <QImageReader> +#include <QImageWriter> + +namespace QmlDesigner { + +template<typename DatabaseType> +class ImageCacheStorage : public ImageCacheStorageInterface +{ +public: + using ReadStatement = typename DatabaseType::ReadStatement; + using WriteStatement = typename DatabaseType::WriteStatement; + + ImageCacheStorage(DatabaseType &database) + : database(database) + { + transaction.commit(); + } + + Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override + { + try { + Sqlite::DeferredTransaction transaction{database}; + + auto optionalBlob = selectImageStatement.template value<Sqlite::ByteArrayBlob>( + name, minimumTimeStamp.value); + + transaction.commit(); + + if (optionalBlob) { + QBuffer buffer{&optionalBlob->byteArray}; + QImageReader reader{&buffer, "PNG"}; + + return Entry{reader.read(), true}; + } + + return {}; + + } catch (const Sqlite::StatementIsBusy &) { + return fetchImage(name, minimumTimeStamp); + } + } + + Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const override + { + try { + Sqlite::DeferredTransaction transaction{database}; + + auto optionalBlob = selectIconStatement.template value<Sqlite::ByteArrayBlob>( + name, minimumTimeStamp.value); + + transaction.commit(); + + if (optionalBlob) { + QBuffer buffer{&optionalBlob->byteArray}; + QImageReader reader{&buffer, "PNG"}; + + return Entry{reader.read(), true}; + } + + return {}; + + } catch (const Sqlite::StatementIsBusy &) { + return fetchIcon(name, minimumTimeStamp); + } + } + + void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) override + { + try { + Sqlite::ImmediateTransaction transaction{database}; + + if (image.isNull()) { + upsertImageStatement.write(name, + newTimeStamp.value, + Sqlite::NullValue{}, + Sqlite::NullValue{}); + } else { + QSize iconSize = image.size().scaled(QSize{96, 96}.boundedTo(image.size()), + Qt::KeepAspectRatio); + QImage icon = image.scaled(iconSize); + upsertImageStatement.write(name, + newTimeStamp.value, + Sqlite::BlobView{createImageBuffer(image)->data()}, + Sqlite::BlobView{createImageBuffer(icon)->data()}); + } + transaction.commit(); + + } catch (const Sqlite::StatementIsBusy &) { + return storeImage(name, newTimeStamp, image); + } + } + + void walCheckpointFull() + { + try { + database.walCheckpointFull(); + } catch (const Sqlite::StatementIsBusy &) { + return walCheckpointFull(); + } + } + +private: + class Initializer + { + public: + Initializer(DatabaseType &database) + { + if (!database.isInitialized()) { + Sqlite::ExclusiveTransaction transaction{database}; + + createImagesTable(database); + + transaction.commit(); + + database.setIsInitialized(true); + + database.walCheckpointFull(); + } + } + + void createImagesTable(DatabaseType &database) + { + Sqlite::Table table; + table.setUseIfNotExists(true); + table.setName("images"); + table.addColumn("id", Sqlite::ColumnType::Integer, {Sqlite::PrimaryKey{}}); + table.addColumn("name", Sqlite::ColumnType::Text, {Sqlite::NotNull{}, Sqlite::Unique{}}); + table.addColumn("mtime", Sqlite::ColumnType::Integer); + table.addColumn("image", Sqlite::ColumnType::Blob); + table.addColumn("icon", Sqlite::ColumnType::Blob); + + table.initialize(database); + } + }; + + std::unique_ptr<QBuffer> createImageBuffer(const QImage &image) + { + auto buffer = std::make_unique<QBuffer>(); + buffer->open(QIODevice::WriteOnly); + QImageWriter writer{buffer.get(), "PNG"}; + writer.write(image); + + return buffer; + } + +public: + DatabaseType &database; + Initializer initializer{database}; + Sqlite::ImmediateNonThrowingDestructorTransaction transaction{database}; + mutable ReadStatement selectImageStatement{ + "SELECT image FROM images WHERE name=?1 AND mtime >= ?2", database}; + mutable ReadStatement selectIconStatement{ + "SELECT icon FROM images WHERE name=?1 AND mtime >= ?2", database}; + WriteStatement upsertImageStatement{ + "INSERT INTO images(name, mtime, image, icon) VALUES (?1, ?2, ?3, ?4) ON " + "CONFLICT(name) DO UPDATE SET mtime=excluded.mtime, image=excluded.image, " + "icon=excluded.icon", + database}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h new file mode 100644 index 0000000000..97ace6efe6 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/imagecachestorageinterface.h @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <QImage> + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +namespace QmlDesigner { +namespace Internal { +class ImageCacheStorageEntry +{ + public: + QImage image; + bool hasEntry = false; +}; + +} // namespace Internal + +class ImageCacheStorageInterface +{ +public: + using Entry = Internal::ImageCacheStorageEntry; + + virtual Entry fetchImage(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0; + virtual Entry fetchIcon(Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp) const = 0; + virtual void storeImage(Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image) = 0; + virtual void walCheckpointFull() = 0; + +protected: + ~ImageCacheStorageInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp new file mode 100644 index 0000000000..99573f175f --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.cpp @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "timestampprovider.h" + +#include <QDateTime> +#include <QFileInfo> + +namespace QmlDesigner { + +Sqlite::TimeStamp TimeStampProvider::timeStamp(Utils::SmallStringView name) const +{ + return QFileInfo{QString{name}}.lastModified().toSecsSinceEpoch(); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h new file mode 100644 index 0000000000..8acc5fcb58 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampprovider.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "timestampproviderinterface.h" + +namespace QmlDesigner { + +class TimeStampProvider : public TimeStampProviderInterface +{ +public: + Sqlite::TimeStamp timeStamp(Utils::SmallStringView name) const override; +}; + +} // namespace QmlDesigner + diff --git a/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h b/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h new file mode 100644 index 0000000000..33cffc9b49 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/imagecache/timestampproviderinterface.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <sqlitetimestamp.h> +#include <utils/smallstringview.h> + +#include <QImage> + +namespace QmlDesigner { + +class TimeStampProviderInterface +{ +public: + virtual Sqlite::TimeStamp timeStamp(Utils::SmallStringView name) const = 0; + +protected: + ~TimeStampProviderInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h b/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h index e20566724b..640ff367bc 100644 --- a/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h +++ b/src/plugins/qmldesigner/designercore/include/basetexteditmodifier.h @@ -49,6 +49,9 @@ public: bool renameId(const QString &oldId, const QString &newId) override; bool moveToComponent(int nodeOffset) override; QStringList autoComplete(QTextDocument *textDocument, int position, bool explicitComplete) override; + +private: + TextEditor::TextEditorWidget *m_textEdit; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h b/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h new file mode 100644 index 0000000000..c0ba644fdd --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/filesystemfacadeinterface.h @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +namespace QmlDesigner { + +class FileSystemFacadeInterface +{ +public: +protected: + ~FileSystemFacadeInterface() = default; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/imagecache.h b/src/plugins/qmldesigner/designercore/include/imagecache.h new file mode 100644 index 0000000000..4ac360c2a5 --- /dev/null +++ b/src/plugins/qmldesigner/designercore/include/imagecache.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <utils/smallstring.h> + +#include <condition_variable> +#include <functional> +#include <mutex> +#include <thread> + +#include <QImage> + +namespace QmlDesigner { + +class TimeStampProviderInterface; +class ImageCacheStorageInterface; +class ImageCacheGeneratorInterface; + +class ImageCache +{ +public: + using CaptureCallback = std::function<void(const QImage &)>; + using AbortCallback = std::function<void()>; + + ~ImageCache(); + + ImageCache(ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + + void requestImage(Utils::PathString name, + CaptureCallback captureCallback, + AbortCallback abortCallback); + void requestIcon(Utils::PathString name, + CaptureCallback captureCallback, + AbortCallback abortCallback); + + void clean(); + +private: + enum class RequestType { Image, Icon }; + struct Entry + { + Entry() = default; + Entry(Utils::PathString name, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType) + : name{std::move(name)} + , captureCallback{std::move(captureCallback)} + , abortCallback{std::move(abortCallback)} + , requestType{requestType} + {} + + Utils::PathString name; + CaptureCallback captureCallback; + AbortCallback abortCallback; + RequestType requestType = RequestType::Image; + }; + + std::tuple<bool, Entry> getEntry(); + void addEntry(Utils::PathString &&name, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback, + RequestType requestType); + void clearEntries(); + void waitForEntries(); + void stopThread(); + bool isRunning(); + static void request(Utils::SmallStringView name, + ImageCache::RequestType requestType, + ImageCache::CaptureCallback captureCallback, + ImageCache::AbortCallback abortCallback, + ImageCacheStorageInterface &storage, + ImageCacheGeneratorInterface &generator, + TimeStampProviderInterface &timeStampProvider); + +private: + std::vector<Entry> m_entries; + mutable std::mutex m_mutex; + std::condition_variable m_condition; + std::thread m_backgroundThread; + ImageCacheStorageInterface &m_storage; + ImageCacheGeneratorInterface &m_generator; + TimeStampProviderInterface &m_timeStampProvider; + bool m_finishing{false}; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index a570a0e690..0e86ad11ee 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -146,6 +146,11 @@ public: QVariant previewImageDataForGenericNode(const ModelNode &modelNode, const ModelNode &renderNode); QVariant previewImageDataForImageNode(const ModelNode &modelNode); + void setCrashCallback(std::function<void()> crashCallback) + { + m_crashCallback = std::move(crashCallback); + } + protected: void timerEvent(QTimerEvent *event) override; @@ -231,6 +236,7 @@ private: // key: fileUrl value: (key: instance qml id, value: related tool states) QHash<QUrl, QHash<QString, QVariantMap>> m_edit3DToolStates; + std::function<void()> m_crashCallback{[this] { handleCrash(); }}; }; } // namespace ProxyNodeInstanceView diff --git a/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h b/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h index 500bffd6fd..c18b4d8cfb 100644 --- a/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h +++ b/src/plugins/qmldesigner/designercore/include/plaintexteditmodifier.h @@ -47,6 +47,7 @@ private: public: PlainTextEditModifier(QPlainTextEdit *textEdit); + PlainTextEditModifier(QTextDocument *document, const QTextCursor &textCursor); ~PlainTextEditModifier() override; QTextDocument *textDocument() const override; @@ -76,20 +77,17 @@ public: bool moveToComponent(int /* nodeOffset */) override { return false; } -protected: - QPlainTextEdit *plainTextEdit() const - { return m_textEdit; } - private: void textEditChanged(); void runRewriting(Utils::ChangeSet *writer); private: - Utils::ChangeSet *m_changeSet; - QPlainTextEdit *m_textEdit; - bool m_changeSignalsEnabled; - bool m_pendingChangeSignal; - bool m_ongoingTextChange; + Utils::ChangeSet *m_changeSet = nullptr; + QTextDocument *m_textDocument; + QTextCursor m_textCursor; + bool m_changeSignalsEnabled{true}; + bool m_pendingChangeSignal{false}; + bool m_ongoingTextChange{false}; }; class QMLDESIGNERCORE_EXPORT NotIndentingTextEditModifier: public PlainTextEditModifier @@ -99,6 +97,10 @@ public: : PlainTextEditModifier(textEdit) {} + NotIndentingTextEditModifier(QTextDocument *document, const QTextCursor &textCursor) + : PlainTextEditModifier{document, textCursor} + {} + void indent(int /*offset*/, int /*length*/) override {} void indentLines(int /*offset*/, int /*length*/) override diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp index b7a2cc282e..76d641fe30 100644 --- a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.cpp @@ -33,11 +33,12 @@ namespace QmlDesigner { -void BaseConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void BaseConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &, - ProjectExplorer::Target *) + ProjectExplorer::Target *, + AbstractView *view) { - m_nodeInstanceServerProxy = nodeInstanceServerProxy; + m_nodeInstanceServer = nodeInstanceServer; m_isActive = true; } @@ -47,7 +48,14 @@ void BaseConnectionManager::shutDown() writeCommand(QVariant::fromValue(EndPuppetCommand())); - m_nodeInstanceServerProxy = nullptr; + m_nodeInstanceServer = nullptr; +} + +void BaseConnectionManager::setCrashCallback(std::function<void()> callback) +{ + std::lock_guard<std::mutex> lock{m_callbackMutex}; + + m_crashCallback = std::move(callback); } bool BaseConnectionManager::isActive() const @@ -85,7 +93,7 @@ void BaseConnectionManager::dispatchCommand(const QVariant &command, Connection if (!isActive()) return; - m_nodeInstanceServerProxy->dispatchCommand(command); + m_nodeInstanceServer->dispatchCommand(command); } void BaseConnectionManager::readDataStream(Connection &connection) @@ -123,5 +131,12 @@ void BaseConnectionManager::readDataStream(Connection &connection) for (const QVariant &command : commandList) dispatchCommand(command, connection); } + +void BaseConnectionManager::callCrashCallback() +{ + std::lock_guard<std::mutex> lock{m_callbackMutex}; + + m_crashCallback(); +} } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h index 83a41a2bd8..fca035682f 100644 --- a/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/baseconnectionmanager.h @@ -29,6 +29,8 @@ #include <QProcess> +#include <mutex> + QT_BEGIN_NAMESPACE class QLocalSocket; QT_END_NAMESPACE @@ -40,7 +42,6 @@ class Target; namespace QmlDesigner { class AbstractView; -class NodeInstanceServerProxy; class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public ConnectionManagerInterface { @@ -49,11 +50,14 @@ class QMLDESIGNERCORE_EXPORT BaseConnectionManager : public QObject, public Conn public: BaseConnectionManager() = default; - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void shutDown() override; + void setCrashCallback(std::function<void()> callback) override; + bool isActive() const; protected: @@ -61,15 +65,19 @@ protected: virtual void showCannotConnectToPuppetWarningAndSwitchToEditMode(); using ConnectionManagerInterface::processFinished; void processFinished(); - void writeCommandToIODevice(const QVariant &command, - QIODevice *ioDevice, - unsigned int commandCounter); + static void writeCommandToIODevice(const QVariant &command, + QIODevice *ioDevice, + unsigned int commandCounter); void readDataStream(Connection &connection); - NodeInstanceServerProxy *nodeInstanceServerProxy() const { return m_nodeInstanceServerProxy; } + NodeInstanceServerInterface *nodeInstanceServer() const { return m_nodeInstanceServer; } + + void callCrashCallback(); private: - NodeInstanceServerProxy *m_nodeInstanceServerProxy{}; + std::mutex m_callbackMutex; + std::function<void()> m_crashCallback; + NodeInstanceServerInterface *m_nodeInstanceServer{}; bool m_isActive = false; }; diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp index adf395d874..90f4226217 100644 --- a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.cpp @@ -34,11 +34,12 @@ namespace QmlDesigner { -void CapturingConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void CapturingConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - InteractiveConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + InteractiveConnectionManager::setUp(nodeInstanceServer, qrcMappingString, target, view); int indexOfCapturePuppetStream = QCoreApplication::arguments().indexOf( "-capture-puppet-stream"); @@ -72,7 +73,7 @@ void CapturingConnectionManager::writeCommand(const QVariant &command) if (m_captureFileForTest.isWritable()) { qDebug() << "command name: " << QMetaType::typeName(command.userType()); - writeCommandToIODevice(command, &m_captureFileForTest, m_writeCommandCounter); + writeCommandToIODevice(command, &m_captureFileForTest, writeCommandCounter()); qDebug() << "\tcatpure file offset: " << m_captureFileForTest.pos(); } } diff --git a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h index 1bedef440b..e13d2e254a 100644 --- a/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/capturingconnectionmanager.h @@ -32,9 +32,10 @@ namespace QmlDesigner { class QMLDESIGNERCORE_EXPORT CapturingConnectionManager : public InteractiveConnectionManager { public: - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp index fa8528579d..77ea8706bf 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.cpp @@ -46,19 +46,19 @@ ConnectionManager::ConnectionManager() = default; ConnectionManager::~ConnectionManager() = default; -void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void ConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServerProxy, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + BaseConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target, view); m_localServer = std::make_unique<QLocalServer>(); QString socketToken(QUuid::createUuid().toString()); m_localServer->listen(socketToken); m_localServer->setMaxPendingConnections(3); - NodeInstanceView *nodeInstanceView = nodeInstanceServerProxy->nodeInstanceView(); - PuppetCreator puppetCreator(target, nodeInstanceView->model()); + PuppetCreator puppetCreator(target, view->model()); puppetCreator.setQrcMappingString(qrcMappingString); puppetCreator.createQml2PuppetExecutableIfMissing(); @@ -67,7 +67,6 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, connection.qmlPuppetProcess = puppetCreator.createPuppetProcess( connection.mode, socketToken, - nodeInstanceView, [&] { printProcessOutput(connection.qmlPuppetProcess.get(), connection.name); }, [&](int exitCode, QProcess::ExitStatus exitStatus) { processFinished(exitCode, exitStatus); @@ -90,7 +89,7 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, if (connectedToPuppet) { connection.socket.reset(m_localServer->nextPendingConnection()); - QObject::connect(connection.socket.get(), &QIODevice::readyRead, [&] { + QObject::connect(connection.socket.get(), &QIODevice::readyRead, this, [&] { readDataStream(connection); }); } else { @@ -101,11 +100,6 @@ void ConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, } m_localServer->close(); - - connect(this, - &ConnectionManager::processCrashed, - nodeInstanceServerProxy, - &NodeInstanceServerProxy::processCrashed); } void ConnectionManager::shutDown() @@ -143,7 +137,7 @@ void ConnectionManager::processFinished(int exitCode, QProcess::ExitStatus exitS closeSocketsAndKillProcesses(); if (exitStatus == QProcess::CrashExit) - emit processCrashed(); + callCrashCallback(); } void ConnectionManager::closeSocketsAndKillProcesses() diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h index 3e8ef26744..c3c2c34afb 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanager.h @@ -48,19 +48,20 @@ public: ~ConnectionManager() override; enum PuppetStreamType { FirstPuppetStream, SecondPuppetStream, ThirdPuppetStream }; - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServerProxy, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; void shutDown() override; void writeCommand(const QVariant &command) override; -signals: - void processCrashed(); - protected: using BaseConnectionManager::processFinished; void processFinished(int exitCode, QProcess::ExitStatus exitStatus) override; + std::vector<Connection> &connections() { return m_connections; } + + quint32 &writeCommandCounter() { return m_writeCommandCounter; } private: void printProcessOutput(QProcess *process, const QString &connectionName); @@ -69,7 +70,6 @@ private: private: std::unique_ptr<QLocalServer> m_localServer; -protected: std::vector<Connection> m_connections; quint32 m_writeCommandCounter = 0; }; diff --git a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h index 92d7449bc0..2fc75c61c2 100644 --- a/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h +++ b/src/plugins/qmldesigner/designercore/instances/connectionmanagerinterface.h @@ -38,7 +38,8 @@ class Target; namespace QmlDesigner { -class NodeInstanceServerProxy; +class NodeInstanceServerInterface; +class AbstractView; class QMLDESIGNERCORE_EXPORT ConnectionManagerInterface { @@ -65,12 +66,15 @@ public: virtual ~ConnectionManagerInterface(); - virtual void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + virtual void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) = 0; virtual void shutDown() = 0; + virtual void setCrashCallback(std::function<void()> callback) = 0; + virtual void writeCommand(const QVariant &command) = 0; protected: diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp index 6da44603df..cd9b5fc5cf 100644 --- a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.cpp @@ -38,20 +38,21 @@ namespace QmlDesigner { InteractiveConnectionManager::InteractiveConnectionManager() { - m_connections.emplace_back("Editor", "editormode"); - m_connections.emplace_back("Render", "rendermode"); - m_connections.emplace_back("Preview", "previewmode"); + connections().emplace_back("Editor", "editormode"); + connections().emplace_back("Render", "rendermode"); + connections().emplace_back("Preview", "previewmode"); } -void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, +void InteractiveConnectionManager::setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) + ProjectExplorer::Target *target, + AbstractView *view) { - ConnectionManager::setUp(nodeInstanceServerProxy, qrcMappingString, target); + ConnectionManager::setUp(nodeInstanceServer, qrcMappingString, target, view); DesignerSettings settings = QmlDesignerPlugin::instance()->settings(); int timeOutTime = settings.value(DesignerSettingsKey::PUPPET_KILL_TIMEOUT).toInt(); - for (Connection &connection : m_connections) + for (Connection &connection : connections()) connection.timer->setInterval(timeOutTime); if (QmlDesignerPlugin::instance() @@ -59,7 +60,7 @@ void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceSe .value(DesignerSettingsKey::DEBUG_PUPPET) .toString() .isEmpty()) { - for (Connection &connection : m_connections) { + for (Connection &connection : connections()) { QObject::connect(connection.timer.get(), &QTimer::timeout, [&]() { puppetTimeout(connection); }); @@ -67,6 +68,12 @@ void InteractiveConnectionManager::setUp(NodeInstanceServerProxy *nodeInstanceSe } } +void InteractiveConnectionManager::shutDown() +{ + m_view = {}; + ConnectionManager::shutDown(); +} + void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEditMode() { Core::AsynchronousMessageBox::warning( @@ -75,8 +82,8 @@ void InteractiveConnectionManager::showCannotConnectToPuppetWarningAndSwitchToEd "Switching to another kit might help.")); QmlDesignerPlugin::instance()->switchToTextModeDeferred(); - nodeInstanceServerProxy()->nodeInstanceView()->emitDocumentMessage( - tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); + if (m_view) + m_view->emitDocumentMessage(tr("Cannot Connect to QML Emulation Layer (QML Puppet)")); } void InteractiveConnectionManager::dispatchCommand(const QVariant &command, Connection &connection) diff --git a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h index 1946620a43..03be103ad6 100644 --- a/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h +++ b/src/plugins/qmldesigner/designercore/instances/interactiveconnectionmanager.h @@ -34,9 +34,12 @@ class InteractiveConnectionManager : public ConnectionManager public: InteractiveConnectionManager(); - void setUp(NodeInstanceServerProxy *nodeInstanceServerProxy, + void setUp(NodeInstanceServerInterface *nodeInstanceServer, const QString &qrcMappingString, - ProjectExplorer::Target *target) override; + ProjectExplorer::Target *target, + AbstractView *view) override; + + void shutDown() override; void showCannotConnectToPuppetWarningAndSwitchToEditMode() override; @@ -46,6 +49,9 @@ protected: private: void puppetTimeout(Connection &connection); void puppetAlive(Connection &connection); + +private: + AbstractView *m_view{}; }; } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp index 173adc1b00..e04d725b38 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.cpp @@ -100,7 +100,7 @@ NodeInstanceServerProxy::NodeInstanceServerProxy(NodeInstanceView *nodeInstanceV if (instanceViewBenchmark().isInfoEnabled()) m_benchmarkTimer.start(); - m_connectionManager.setUp(this, qrcMappingString(), target); + m_connectionManager.setUp(this, qrcMappingString(), target, nodeInstanceView); qCInfo(instanceViewBenchmark) << "puppets setup:" << m_benchmarkTimer.elapsed(); } diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h index 0177bd6a14..e4edeb6725 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceserverproxy.h @@ -84,6 +84,7 @@ public: void requestModelNodePreviewImage(const RequestModelNodePreviewImageCommand &command) override; void changeLanguage(const ChangeLanguageCommand &command) override; void changePreviewImageSize(const ChangePreviewImageSizeCommand &command) override; + void dispatchCommand(const QVariant &command) override; NodeInstanceView *nodeInstanceView() const { return m_nodeInstanceView; } @@ -91,12 +92,8 @@ public: protected: void writeCommand(const QVariant &command); - void dispatchCommand(const QVariant &command); NodeInstanceClientInterface *nodeInstanceClient() const; -signals: - void processCrashed(); - private: NodeInstanceView *m_nodeInstanceView{}; QElapsedTimer m_benchmarkTimer; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index e2ac5f6356..3f0b1f8a5f 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -196,10 +196,7 @@ void NodeInstanceView::modelAttached(Model *model) AbstractView::modelAttached(model); m_nodeInstanceServer = createNodeInstanceServerProxy(); m_lastCrashTime.start(); - connect(m_nodeInstanceServer.get(), - &NodeInstanceServerProxy::processCrashed, - this, - &NodeInstanceView::handleCrash); + m_connectionManager.setCrashCallback(m_crashCallback); if (!isSkippedRootNode(rootModelNode())) { m_nodeInstanceServer->createScene(createCreateSceneCommand()); @@ -215,6 +212,8 @@ void NodeInstanceView::modelAttached(Model *model) void NodeInstanceView::modelAboutToBeDetached(Model * model) { + m_connectionManager.setCrashCallback({}); + removeAllInstanceNodeRelationships(); if (m_nodeInstanceServer) { m_nodeInstanceServer->clearScene(createClearSceneCommand()); @@ -281,11 +280,6 @@ void NodeInstanceView::restartProcess() m_nodeInstanceServer.reset(); m_nodeInstanceServer = createNodeInstanceServerProxy(); - connect(m_nodeInstanceServer.get(), - &NodeInstanceServerProxy::processCrashed, - this, - &NodeInstanceView::handleCrash); - if (!isSkippedRootNode(rootModelNode())) { m_nodeInstanceServer->createScene(createCreateSceneCommand()); m_nodeInstanceServer->changeSelection( diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp index 5f691bc5a0..df609f4cb0 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp @@ -181,7 +181,6 @@ PuppetCreator::PuppetCreator(ProjectExplorer::Target *target, const Model *model QProcessUniquePointer PuppetCreator::createPuppetProcess( const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const @@ -190,7 +189,6 @@ QProcessUniquePointer PuppetCreator::createPuppetProcess( qmlPuppetDirectory(m_availablePuppetType), puppetMode, socketToken, - handlerObject, processOutputCallback, processFinishCallback, customOptions); @@ -201,7 +199,6 @@ QProcessUniquePointer PuppetCreator::puppetProcess( const QString &workingDirectory, const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const @@ -216,7 +213,6 @@ QProcessUniquePointer PuppetCreator::puppetProcess( &QProcess::kill); QObject::connect(puppetProcess.get(), static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), - handlerObject, processFinishCallback); #ifndef QMLDESIGNER_TEST @@ -227,7 +223,7 @@ QProcessUniquePointer PuppetCreator::puppetProcess( #endif if (forwardOutput == puppetMode || forwardOutput == "all") { puppetProcess->setProcessChannelMode(QProcess::MergedChannels); - QObject::connect(puppetProcess.get(), &QProcess::readyRead, handlerObject, processOutputCallback); + QObject::connect(puppetProcess.get(), &QProcess::readyRead, processOutputCallback); } puppetProcess->setWorkingDirectory(workingDirectory); diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h index 69c66688fe..e001b9d0c1 100644 --- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h +++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h @@ -58,7 +58,6 @@ public: QProcessUniquePointer createPuppetProcess( const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions = {}) const; @@ -89,7 +88,6 @@ protected: const QString &workingDirectory, const QString &puppetMode, const QString &socketToken, - QObject *handlerObject, std::function<void()> processOutputCallback, std::function<void(int, QProcess::ExitStatus)> processFinishCallback, const QStringList &customOptions) const; diff --git a/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp b/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp index 03239faedc..8113e62846 100644 --- a/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/basetexteditmodifier.cpp @@ -38,8 +38,9 @@ using namespace QmlDesigner; -BaseTextEditModifier::BaseTextEditModifier(TextEditor::TextEditorWidget *textEdit): - PlainTextEditModifier(textEdit) +BaseTextEditModifier::BaseTextEditModifier(TextEditor::TextEditorWidget *textEdit) + : PlainTextEditModifier(textEdit) + , m_textEdit{textEdit} { } @@ -47,21 +48,20 @@ void BaseTextEditModifier::indentLines(int startLine, int endLine) { if (startLine < 0) return; - auto baseTextEditorWidget = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit()); - if (!baseTextEditorWidget) + + if (!m_textEdit) return; - QTextDocument *textDocument = plainTextEdit()->document(); - TextEditor::TextDocument *baseTextEditorDocument = baseTextEditorWidget->textDocument(); + TextEditor::TextDocument *baseTextEditorDocument = m_textEdit->textDocument(); TextEditor::TabSettings tabSettings = baseTextEditorDocument->tabSettings(); - QTextCursor tc(textDocument); + QTextCursor tc(textDocument()); tc.beginEditBlock(); for (int i = startLine; i <= endLine; i++) { - QTextBlock start = textDocument->findBlockByNumber(i); + QTextBlock start = textDocument()->findBlockByNumber(i); if (start.isValid()) { - QmlJSEditor::Internal::Indenter indenter(textDocument); + QmlJSEditor::Internal::Indenter indenter(textDocument()); indenter.indentBlock(start, QChar::Null, tabSettings); } } @@ -82,22 +82,23 @@ void BaseTextEditModifier::indent(int offset, int length) int BaseTextEditModifier::indentDepth() const { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) - return bte->textDocument()->tabSettings().m_indentSize; + if (m_textEdit) + return m_textEdit->textDocument()->tabSettings().m_indentSize; else return 0; } bool BaseTextEditModifier::renameId(const QString &oldId, const QString &newId) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) { - if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) { + if (m_textEdit) { + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) { Utils::ChangeSet changeSet; foreach (const QmlJS::SourceLocation &loc, document->semanticInfo().idLocations.value(oldId)) { changeSet.replace(loc.begin(), loc.end(), newId); } - QTextCursor tc = bte->textCursor(); + QTextCursor tc = textCursor(); changeSet.apply(&tc); return true; } @@ -120,10 +121,9 @@ static QmlJS::AST::UiObjectDefinition *getObjectDefinition(const QList<QmlJS::AS bool BaseTextEditModifier::moveToComponent(int nodeOffset) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) { - if (auto document - = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) { - + if (m_textEdit) { + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) { auto qualifiedId = QmlJS::AST::cast<QmlJS::AST::UiQualifiedId *>(document->semanticInfo().astNodeAt(nodeOffset)); QList<QmlJS::AST::Node *> path = document->semanticInfo().rangePath(nodeOffset); QmlJS::AST::UiObjectDefinition *object = getObjectDefinition(path, qualifiedId); @@ -140,9 +140,9 @@ bool BaseTextEditModifier::moveToComponent(int nodeOffset) QStringList BaseTextEditModifier::autoComplete(QTextDocument *textDocument, int position, bool explicitComplete) { - if (auto bte = qobject_cast<TextEditor::TextEditorWidget*>(plainTextEdit())) - if (auto document - = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>(bte->textDocument())) + if (m_textEdit) + if (auto document = qobject_cast<QmlJSEditor::QmlJSEditorDocument *>( + m_textEdit->textDocument())) return QmlJSEditor::qmlJSAutoComplete(textDocument, position, document->filePath(), diff --git a/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp b/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp index f87dc73a2c..782ff3794b 100644 --- a/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp +++ b/src/plugins/qmldesigner/designercore/model/plaintexteditmodifier.cpp @@ -35,19 +35,17 @@ using namespace Utils; using namespace QmlDesigner; -PlainTextEditModifier::PlainTextEditModifier(QPlainTextEdit *textEdit): - m_changeSet(nullptr), - m_textEdit(textEdit), - m_changeSignalsEnabled(true), - m_pendingChangeSignal(false), - m_ongoingTextChange(false) +PlainTextEditModifier::PlainTextEditModifier(QPlainTextEdit *textEdit) + : PlainTextEditModifier(textEdit->document(), textEdit->textCursor()) { - Q_ASSERT(textEdit); - - connect(m_textEdit, &QPlainTextEdit::textChanged, - this, &PlainTextEditModifier::textEditChanged); + connect(textEdit, &QPlainTextEdit::textChanged, this, &PlainTextEditModifier::textEditChanged); } +PlainTextEditModifier::PlainTextEditModifier(QTextDocument *document, const QTextCursor &textCursor) + : m_textDocument{document} + , m_textCursor{textCursor} +{} + PlainTextEditModifier::~PlainTextEditModifier() = default; void PlainTextEditModifier::replace(int offset, int length, const QString &replacement) @@ -158,17 +156,17 @@ void PlainTextEditModifier::runRewriting(ChangeSet *changeSet) QTextDocument *PlainTextEditModifier::textDocument() const { - return m_textEdit->document(); + return m_textDocument; } QString PlainTextEditModifier::text() const { - return m_textEdit->toPlainText(); + return m_textDocument->toPlainText(); } QTextCursor PlainTextEditModifier::textCursor() const { - return m_textEdit->textCursor(); + return m_textCursor; } void PlainTextEditModifier::deactivateChangeSignals() diff --git a/src/plugins/qmldesigner/qmldesigner_dependencies.pri b/src/plugins/qmldesigner/qmldesigner_dependencies.pri index 321d2c2b07..473692aa5d 100644 --- a/src/plugins/qmldesigner/qmldesigner_dependencies.pri +++ b/src/plugins/qmldesigner/qmldesigner_dependencies.pri @@ -3,7 +3,8 @@ QTC_LIB_DEPENDS += \ utils \ qmljs \ qmleditorwidgets \ - advanceddockingsystem + advanceddockingsystem \ + sqlite QTC_PLUGIN_DEPENDS += \ coreplugin \ texteditor \ diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index 6590dcb046..8f34f42d0a 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -35,6 +35,7 @@ include(components/annotationeditor/annotationeditor.pri) include(components/richtexteditor/richtexteditor.pri) include(components/transitioneditor/transitioneditor.pri) include(components/listmodeleditor/listmodeleditor.pri) +include(components/previewtooltip/previewtooltipbackend.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) !isEmpty(BUILD_PUPPET_IN_CREATOR_BINPATH) { diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index a4e22d7520..3d73f1c05e 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -27,6 +27,7 @@ Project { Depends { name: "LanguageUtils" } Depends { name: "QtSupport" } Depends { name: "app_version_header" } + Depends { name: "Sqlite" } cpp.defines: base.concat([ "DESIGNER_CORE_LIBRARY", @@ -411,6 +412,21 @@ Project { "pluginmanager/widgetpluginmanager.h", "pluginmanager/widgetpluginpath.cpp", "pluginmanager/widgetpluginpath.h", + "include/imagecache.h", + "imagecache/imagecachecollector.cpp", + "imagecache/imagecachecollector.h", + "imagecache/imagecache.cpp", + "imagecache/imagecachecollectorinterface.h", + "imagecache/imagecacheconnectionmanager.cpp", + "imagecache/imagecacheconnectionmanager.h", + "imagecache/imagecachegeneratorinterface.h", + "imagecache/imagecachegenerator.cpp", + "imagecache/imagecachegenerator.h", + "imagecache/imagecachestorageinterface.h", + "imagecache/imagecachestorage.h", + "imagecache/timestampproviderinterface.h", + "imagecache/timestampprovider.h", + "imagecache/timestampprovider.cpp", ] } @@ -602,6 +618,8 @@ Project { "itemlibrary/itemlibrarywidget.h", "itemlibrary/customfilesystemmodel.cpp", "itemlibrary/customfilesystemmodel.h", + "itemlibrary/itemlibraryiconimageprovider.cpp", + "itemlibrary/itemlibraryiconimageprovider.h", "navigator/iconcheckboxitemdelegate.cpp", "navigator/iconcheckboxitemdelegate.h", "navigator/nameitemdelegate.cpp", diff --git a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri index cd4d52e8d3..b5ae93f2f1 100644 --- a/src/plugins/qmldesigner/qmldesignerunittestfiles.pri +++ b/src/plugins/qmldesigner/qmldesignerunittestfiles.pri @@ -1,5 +1,6 @@ INCLUDEPATH += $$PWD INCLUDEPATH += $$PWD/designercore/include +INCLUDEPATH += $$PWD/designercore/imagecache INCLUDEPATH += $$PWD/designercore INCLUDEPATH += $$PWD/../../../share/qtcreator/qml/qmlpuppet/interfaces INCLUDEPATH += $$PWD/../../../share/qtcreator/qml/qmlpuppet/types @@ -30,9 +31,18 @@ SOURCES += \ $$PWD/designercore/model/variantproperty.cpp\ $$PWD/designercore/model/annotation.cpp \ $$PWD/designercore/rewritertransaction.cpp \ - $$PWD/components/listmodeleditor/listmodeleditormodel.cpp + $$PWD/components/listmodeleditor/listmodeleditormodel.cpp \ + $$PWD/designercore/imagecache/imagecache.cpp \ + $$PWD/designercore/imagecache/imagecachegenerator.cpp HEADERS += \ + $$PWD/designercore/imagecache/imagecachecollectorinterface.h \ + $$PWD/designercore/imagecache/imagecachestorage.h \ + $$PWD/designercore/imagecache/imagecachegenerator.h \ + $$PWD/designercore/imagecache/imagecachestorageinterface.h \ + $$PWD/designercore/imagecache/imagecachegeneratorinterface.h \ + $$PWD/designercore/imagecache/timestampproviderinterface.h \ + $$PWD/designercore/include/imagecache.h \ $$PWD/designercore/include/modelnode.h \ $$PWD/designercore/include/model.h \ $$PWD/../../../share/qtcreator/qml/qmlpuppet/interfaces/commondefines.h \ diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 3d2e53a48c..85809020d8 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -174,6 +174,7 @@ extend_qtc_executable(qml2puppet qt5capturepreviewnodeinstanceserver.cpp qt5capturepreviewnodeinstanceserver.h nodeinstanceserverdispatcher.cpp nodeinstanceserverdispatcher.h capturenodeinstanceserverdispatcher.cpp capturenodeinstanceserverdispatcher.h + qt5captureimagenodeinstanceserver.cpp qt5captureimagenodeinstanceserver.h ) extend_qtc_executable(qml2puppet diff --git a/src/tools/qml2puppet/qml2puppet.qbs b/src/tools/qml2puppet/qml2puppet.qbs index f38a6c8215..742c20406e 100644 --- a/src/tools/qml2puppet/qml2puppet.qbs +++ b/src/tools/qml2puppet/qml2puppet.qbs @@ -224,6 +224,8 @@ QtcTool { "instances/servernodeinstance.h", "instances/qt5capturepreviewnodeinstanceserver.cpp", "instances/qt5capturepreviewnodeinstanceserver.h", + "instances/qt5captureimagenodeinstanceserver.cpp", + "instances/qt5captureimagenodeinstanceserver.h", "instances/nodeinstanceserverdispatcher.cpp", "instances/nodeinstanceserverdispatcher.h", "instances/capturenodeinstanceserverdispatcher.cpp", diff --git a/tests/unit/mockup/qmldesigner/designercore/include/nodeinstanceview.h b/tests/unit/mockup/qmldesigner/designercore/include/nodeinstanceview.h index 50d0e22a2d..50dcf529b6 100644 --- a/tests/unit/mockup/qmldesigner/designercore/include/nodeinstanceview.h +++ b/tests/unit/mockup/qmldesigner/designercore/include/nodeinstanceview.h @@ -28,6 +28,10 @@ #include "qmldesignercorelib_global.h" #include "abstractview.h" +namespace ProjectExplorer { +class Target; +} + namespace QmlDesigner { class NodeInstanceView : public AbstractView @@ -88,6 +92,8 @@ public: void requestModelNodePreviewImage(const ModelNode &node) {} void sendToken(const QString &token, int number, const QVector<ModelNode> &nodeVector) {} + void setTarget(ProjectExplorer::Target *newTarget) {} + void setCrashCallback(std::function<void()>) {} }; } // namespace QmlDesigner diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt index ff0c1abaad..13741ae478 100644 --- a/tests/unit/unittest/CMakeLists.txt +++ b/tests/unit/unittest/CMakeLists.txt @@ -174,6 +174,19 @@ add_qtc_test(unittest GTEST sqlstatementbuilder-test.cpp createtablesqlstatementbuilder-test.cpp sqlitevalue-test.cpp + imagecache-test.cpp + imagecachegenerator-test.cpp + imagecachestorage-test.cpp + sqlitedatabasemock.h + sqlitereadstatementmock.cpp sqlitereadstatementmock.h + sqlitestatementmock.h + sqlitetransactionbackendmock.h + sqlitewritestatementmock.cpp sqlitewritestatementmock.h + notification.h + mocktimestampprovider.h + imagecachecollectormock.h + mockimagecachegenerator.h + mockimagecachestorage.h ) function(extend_qtc_test_with_target_sources target) @@ -335,6 +348,7 @@ extend_qtc_test(unittest "${QmlDesignerDir}" "${QmlDesignerDir}/designercore" "${QmlDesignerDir}/designercore/include" + "${QmlDesignerDir}/designercore/imagecache" "${QmlDesignerDir}/../../../share/qtcreator/qml/qmlpuppet/interfaces" "${QmlDesignerDir}/../../../share/qtcreator/qml/qmlpuppet/types" DEFINES @@ -382,6 +396,13 @@ extend_qtc_test(unittest model/signalhandlerproperty.cpp include/signalhandlerproperty.h model/variantproperty.cpp include/variantproperty.h rewritertransaction.cpp rewritertransaction.h + imagecache/imagecache.cpp include/imagecache.h + imagecache/imagecachecollectorinterface.h + imagecache/imagecachegenerator.cpp imagecache/imagecachegenerator.h + imagecache/imagecachegeneratorinterface.h + imagecache/imagecachestorage.h + imagecache/imagecachestorageinterface.h + imagecache/timestampproviderinterface.h include/qmldesignercorelib_global.h diff --git a/tests/unit/unittest/gmock_dependency.pri b/tests/unit/unittest/gmock_dependency.pri index 1af7b95f09..614c6379d3 100644 --- a/tests/unit/unittest/gmock_dependency.pri +++ b/tests/unit/unittest/gmock_dependency.pri @@ -14,10 +14,8 @@ defineTest(setGoogleTestDirectories) { } isEmpty(GOOGLETEST_DIR) { - exists($$PWD/../../../../googletest) { - setGoogleTestDirectories($$PWD/../../../../googletest) - } else: exists($$PWD/../../../../../googletest) { - setGoogleTestDirectories($$PWD/../../../../../googletest) + exists($$PWD/3rdparty/googletest) { + setGoogleTestDirectories($$PWD/3rdparty/googletest) } else: linux { GTEST_INCLUDE_DIR = /usr/include/gtest GMOCK_INCLUDE_DIR = /usr/include/gmock diff --git a/tests/unit/unittest/google-using-declarations.h b/tests/unit/unittest/google-using-declarations.h index 99514fe9b4..4419c8236d 100644 --- a/tests/unit/unittest/google-using-declarations.h +++ b/tests/unit/unittest/google-using-declarations.h @@ -35,12 +35,16 @@ using testing::An; using testing::AnyNumber; using testing::AnyOf; using testing::Assign; +using testing::AtLeast; +using testing::AtMost; +using testing::Between; using testing::ByMove; using testing::ByRef; using testing::ContainerEq; using testing::Contains; using testing::ElementsAre; using testing::Eq; +using testing::Exactly; using testing::Field; using testing::Ge; using testing::Gt; diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index 8d84ad4d2e..c9c871475d 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -70,6 +70,7 @@ #include <usedmacro.h> #include <utils/link.h> #include <variantproperty.h> +#include <qmldesigner/designercore/imagecache/imagecachestorageinterface.h> #include <sqlite3ext.h> @@ -1468,6 +1469,15 @@ std::ostream &operator<<(std::ostream &out, const VariantProperty &property) return out << "(" << property.parentModelNode() << ", " << property.name() << ", " << property.value() << ")"; } + +namespace Internal { +std::ostream &operator<<(std::ostream &out, const ImageCacheStorageEntry &entry) +{ + return out << "(" << entry.image << ", " << entry.hasEntry << ")"; +} + +} // namespace Internal + } // namespace QmlDesigner void setFilePathCache(ClangBackEnd::FilePathCaching *cache) diff --git a/tests/unit/unittest/gtest-creator-printing.h b/tests/unit/unittest/gtest-creator-printing.h index 565479be03..cdef782578 100644 --- a/tests/unit/unittest/gtest-creator-printing.h +++ b/tests/unit/unittest/gtest-creator-printing.h @@ -356,6 +356,13 @@ class VariantProperty; std::ostream &operator<<(std::ostream &out, const ModelNode &node); std::ostream &operator<<(std::ostream &out, const VariantProperty &property); + +namespace Internal { +class ImageCacheStorageEntry; + +std::ostream &operator<<(std::ostream &out, const ImageCacheStorageEntry &entry); + +} // namespace Internal } // namespace QmlDesigner void setFilePathCache(ClangBackEnd::FilePathCaching *filePathCache); diff --git a/tests/unit/unittest/gtest-qt-printing.cpp b/tests/unit/unittest/gtest-qt-printing.cpp index c097fd0b4c..445ebf7f51 100644 --- a/tests/unit/unittest/gtest-qt-printing.cpp +++ b/tests/unit/unittest/gtest-qt-printing.cpp @@ -85,6 +85,11 @@ std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format) return out; } +std::ostream &operator<<(std::ostream &out, const QImage &image) +{ + return out << "(" << image.width() << ", " << image.height() << ", " << image.format() << ")"; +} + void PrintTo(const QString &text, std::ostream *os) { *os << text; diff --git a/tests/unit/unittest/gtest-qt-printing.h b/tests/unit/unittest/gtest-qt-printing.h index ebaeb2c785..54db4ee105 100644 --- a/tests/unit/unittest/gtest-qt-printing.h +++ b/tests/unit/unittest/gtest-qt-printing.h @@ -34,11 +34,13 @@ QT_BEGIN_NAMESPACE class QVariant; class QString; class QTextCharFormat; +class QImage; std::ostream &operator<<(std::ostream &out, const QVariant &QVariant); std::ostream &operator<<(std::ostream &out, const QString &text); std::ostream &operator<<(std::ostream &out, const QByteArray &byteArray); std::ostream &operator<<(std::ostream &out, const QTextCharFormat &format); +std::ostream &operator<<(std::ostream &out, const QImage &image); void PrintTo(const QString &text, std::ostream *os); void PrintTo(const QVariant &variant, std::ostream *os); diff --git a/tests/unit/unittest/imagecache-test.cpp b/tests/unit/unittest/imagecache-test.cpp new file mode 100644 index 0000000000..4219335774 --- /dev/null +++ b/tests/unit/unittest/imagecache-test.cpp @@ -0,0 +1,326 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include "mockimagecachegenerator.h" +#include "mockimagecachestorage.h" +#include "mocktimestampprovider.h" +#include "notification.h" + +#include <imagecache.h> + +namespace { + +class ImageCache : public testing::Test +{ +protected: + Notification notification; + Notification waitInThread; + NiceMock<MockImageCacheStorage> mockStorage; + NiceMock<MockImageCacheGenerator> mockGenerator; + NiceMock<MockTimeStampProvider> mockTimeStampProvider; + QmlDesigner::ImageCache cache{mockStorage, mockGenerator, mockTimeStampProvider}; + NiceMock<MockFunction<void()>> mockAbortCallback; + NiceMock<MockFunction<void(const QImage &image)>> mockCaptureCallback; + QImage image1{10, 10, QImage::Format_ARGB32}; +}; + +TEST_F(ImageCache, RequestImageFetchesImageFromStorage) +{ + EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _)) + .WillRepeatedly([&](Utils::SmallStringView, auto) { + notification.notify(); + return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false}; + }); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageFetchesImageFromStorageWithTimeStamp) +{ + EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillRepeatedly(Return(Sqlite::TimeStamp{123})); + EXPECT_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123}))) + .WillRepeatedly([&](Utils::SmallStringView, auto) { + notification.notify(); + return QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, false}; + }); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageCallsCaptureCallbackWithImageFromStorage) +{ + ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _)) + .WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{image1, true})); + + EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))).WillRepeatedly([&](const QImage &) { + notification.notify(); + }); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageCallsAbortCallbackWithoutImage) +{ + ON_CALL(mockStorage, fetchImage(Eq("/path/to/Component.qml"), _)) + .WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, true})); + + EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); }); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageRequestImageFromGenerator) +{ + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123}), _, _)) + .WillRepeatedly([&](auto &&, auto, auto &&callback, auto) { notification.notify(); }); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageCallsCaptureCallbackWithImageFromGenerator) +{ + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _)) + .WillByDefault([&](auto &&, auto, auto &&callback, auto) { + callback(QImage{image1}); + notification.notify(); + }); + + EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestImageCallsAbortCallbackFromGenerator) +{ + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _)) + .WillByDefault([&](auto &&, auto, auto &&, auto &&abortCallback) { + abortCallback(); + notification.notify(); + }); + + EXPECT_CALL(mockAbortCallback, Call()); + + cache.requestImage("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconFetchesIconFromStorage) +{ + EXPECT_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _)) + .WillRepeatedly([&](Utils::SmallStringView, auto) { + notification.notify(); + return QmlDesigner::ImageCacheStorageInterface::Entry{{}, false}; + }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconFetchesIconFromStorageWithTimeStamp) +{ + EXPECT_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillRepeatedly(Return(Sqlite::TimeStamp{123})); + EXPECT_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123}))) + .WillRepeatedly([&](Utils::SmallStringView, auto) { + notification.notify(); + return QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, false}; + }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconCallsCaptureCallbackWithImageFromStorage) +{ + ON_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _)) + .WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{image1, true})); + + EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))).WillRepeatedly([&](const QImage &) { + notification.notify(); + }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconCallsAbortCallbackWithoutIcon) +{ + ON_CALL(mockStorage, fetchIcon(Eq("/path/to/Component.qml"), _)) + .WillByDefault(Return(QmlDesigner::ImageCacheStorageInterface::Entry{QImage{}, true})); + + EXPECT_CALL(mockAbortCallback, Call()).WillRepeatedly([&] { notification.notify(); }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconRequestImageFromGenerator) +{ + ON_CALL(mockTimeStampProvider, timeStamp(Eq("/path/to/Component.qml"))) + .WillByDefault(Return(Sqlite::TimeStamp{123})); + + EXPECT_CALL(mockGenerator, + generateImage(Eq("/path/to/Component.qml"), Eq(Sqlite::TimeStamp{123}), _, _)) + .WillRepeatedly([&](auto &&, auto, auto &&callback, auto) { notification.notify(); }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconCallsCaptureCallbackWithImageFromGenerator) +{ + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _)) + .WillByDefault([&](auto &&, auto, auto &&callback, auto) { + callback(QImage{image1}); + notification.notify(); + }); + + EXPECT_CALL(mockCaptureCallback, Call(Eq(image1))); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, RequestIconCallsAbortCallbackFromGenerator) +{ + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _)) + .WillByDefault([&](auto &&, auto, auto &&, auto &&abortCallback) { + abortCallback(); + notification.notify(); + }); + + EXPECT_CALL(mockAbortCallback, Call()); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCache, CleanRemovesEntries) +{ + EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component1.qml"), _, _, _)) + .WillRepeatedly([&](auto &&, auto, auto &&mockCaptureCallback, auto &&) { + mockCaptureCallback(QImage{}); + waitInThread.wait(); + }); + EXPECT_CALL(mockGenerator, generateImage(_, _, _, _)) + .WillRepeatedly([&](auto &&, auto, auto &&mockCaptureCallback, auto &&) { + mockCaptureCallback(QImage{}); + }); + cache.requestIcon("/path/to/Component1.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + + EXPECT_CALL(mockCaptureCallback, Call(_)).Times(AtMost(1)); + + cache.requestIcon("/path/to/Component3.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + cache.clean(); + waitInThread.notify(); +} + +TEST_F(ImageCache, CleanCallsAbort) +{ + ON_CALL(mockGenerator, generateImage(Eq("/path/to/Component1.qml"), _, _, _)) + .WillByDefault( + [&](auto &&, auto, auto &&mockCaptureCallback, auto &&) { waitInThread.wait(); }); + cache.requestIcon("/path/to/Component1.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + cache.requestIcon("/path/to/Component2.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + + EXPECT_CALL(mockAbortCallback, Call()).Times(AtLeast(2)); + + cache.requestIcon("/path/to/Component3.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + cache.clean(); + waitInThread.notify(); +} + +TEST_F(ImageCache, CleanCallsGeneratorClean) +{ + EXPECT_CALL(mockGenerator, clean()).Times(AtLeast(1)); + + cache.clean(); +} + +TEST_F(ImageCache, AfterCleanNewJobsWorks) +{ + cache.clean(); + + EXPECT_CALL(mockGenerator, generateImage(Eq("/path/to/Component.qml"), _, _, _)) + .WillRepeatedly([&](auto &&, auto, auto &&, auto &&) { notification.notify(); }); + + cache.requestIcon("/path/to/Component.qml", + mockCaptureCallback.AsStdFunction(), + mockAbortCallback.AsStdFunction()); + notification.wait(); +} + +} // namespace diff --git a/tests/unit/unittest/imagecachecollectormock.h b/tests/unit/unittest/imagecachecollectormock.h new file mode 100644 index 0000000000..93520c418d --- /dev/null +++ b/tests/unit/unittest/imagecachecollectormock.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <imagecachecollectorinterface.h> + +class ImageCacheCollectorMock : public QmlDesigner::ImageCacheCollectorInterface +{ +public: + MOCK_METHOD(void, + start, + (Utils::SmallStringView filePath, + ImageCacheCollectorInterface::CaptureCallback captureCallback, + ImageCacheCollectorInterface::AbortCallback abortCallback), + (override)); +}; diff --git a/tests/unit/unittest/imagecachegenerator-test.cpp b/tests/unit/unittest/imagecachegenerator-test.cpp new file mode 100644 index 0000000000..f152bd83ad --- /dev/null +++ b/tests/unit/unittest/imagecachegenerator-test.cpp @@ -0,0 +1,240 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include "imagecachecollectormock.h" +#include "mockimagecachestorage.h" +#include "notification.h" + +#include <imagecachegenerator.h> + +#include <mutex> + +namespace { + +class ImageCacheGenerator : public testing::Test +{ +protected: + template<typename Callable, typename... Arguments> + static void executeAsync(Callable &&call, Arguments... arguments) + { + std::thread thread( + [](Callable &&call, Arguments... arguments) { + call(std::forward<Arguments>(arguments)...); + }, + std::forward<Callable>(call), + std::forward<Arguments>(arguments)...); + thread.detach(); + } + +protected: + Notification waitInThread; + Notification notification; + QImage image1{10, 10, QImage::Format_ARGB32}; + NiceMock<MockFunction<void(const QImage &)>> imageCallbackMock; + NiceMock<MockFunction<void()>> abortCallbackMock; + NiceMock<ImageCacheCollectorMock> collectorMock; + NiceMock<MockImageCacheStorage> storageMock; + QmlDesigner::ImageCacheGenerator generator{collectorMock, storageMock}; +}; + +TEST_F(ImageCacheGenerator, CallsCollectorWithCaptureCallback) +{ + EXPECT_CALL(collectorMock, start(Eq("name"), _, _)) + .WillRepeatedly([&](auto, auto captureCallback, auto) { captureCallback(QImage{image1}); }); + EXPECT_CALL(imageCallbackMock, Call(_)).WillRepeatedly([&](const QImage &) { + notification.notify(); + }); + + generator.generateImage("name", {}, imageCallbackMock.AsStdFunction(), {}); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, CallsCollectorOnlyIfNotProcessing) +{ + EXPECT_CALL(collectorMock, start(Eq("name"), _, _)).WillRepeatedly([&](auto, auto, auto) { + notification.notify(); + }); + + generator.generateImage("name", {}, imageCallbackMock.AsStdFunction(), {}); + generator.generateImage("name", {}, imageCallbackMock.AsStdFunction(), {}); + notification.wait(2); +} + +TEST_F(ImageCacheGenerator, ProcessTaskAfterFirstFinished) +{ + ON_CALL(imageCallbackMock, Call(_)).WillByDefault([&](const QImage &) { notification.notify(); }); + + EXPECT_CALL(collectorMock, start(Eq("name"), _, _)).WillOnce([&](auto, auto captureCallback, auto) { + captureCallback(QImage{image1}); + }); + EXPECT_CALL(collectorMock, start(Eq("name2"), _, _)).WillOnce([&](auto, auto captureCallback, auto) { + captureCallback(QImage{image1}); + }); + + generator.generateImage("name", {}, imageCallbackMock.AsStdFunction(), {}); + generator.generateImage("name2", {}, imageCallbackMock.AsStdFunction(), {}); + notification.wait(2); +} + +TEST_F(ImageCacheGenerator, DontCrashAtDestructingGenerator) +{ + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback(QImage{image1}); + }); + + generator.generateImage("name", {}, imageCallbackMock.AsStdFunction(), {}); + generator.generateImage("name2", {}, imageCallbackMock.AsStdFunction(), {}); + generator.generateImage("name3", {}, imageCallbackMock.AsStdFunction(), {}); + generator.generateImage("name4", {}, imageCallbackMock.AsStdFunction(), {}); +} + +TEST_F(ImageCacheGenerator, StoreImage) +{ + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback(QImage{image1}); + }); + + EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(image1))) + .WillRepeatedly([&](auto, auto, auto) { notification.notify(); }); + + generator.generateImage("name", {11}, imageCallbackMock.AsStdFunction(), {}); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, StoreNullImage) +{ + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback(QImage{}); + }); + + EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{}))) + .WillRepeatedly([&](auto, auto, auto) { notification.notify(); }); + + generator.generateImage("name", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, AbortCallback) +{ + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback(QImage{image1}); + }); + ON_CALL(collectorMock, start(Eq("name2"), _, _)).WillByDefault([&](auto, auto, auto abortCallback) { + abortCallback(); + }); + + EXPECT_CALL(imageCallbackMock, Call(_)).WillOnce([&](const QImage &) { notification.notify(); }); + EXPECT_CALL(abortCallbackMock, Call()).WillOnce([&]() { notification.notify(); }); + + generator.generateImage("name", + {}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + generator.generateImage("name2", + {}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + notification.wait(2); +} + +TEST_F(ImageCacheGenerator, StoreNullImageForAbortCallback) +{ + ON_CALL(collectorMock, start(_, _, _)).WillByDefault([&](auto, auto, auto abortCallback) { + abortCallback(); + notification.notify(); + }); + + EXPECT_CALL(abortCallbackMock, Call()).WillOnce([&]() { notification.notify(); }); + EXPECT_CALL(storageMock, storeImage(Eq("name"), Eq(Sqlite::TimeStamp{11}), Eq(QImage{}))); + + generator.generateImage("name", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, AbortForEmptyImage) +{ + NiceMock<MockFunction<void()>> abortCallbackMock; + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback(QImage{}); + }); + + EXPECT_CALL(abortCallbackMock, Call()).WillOnce([&]() { notification.notify(); }); + + generator.generateImage("name", + {}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, CallWalCheckpointFullIfQueueIsEmpty) +{ + ON_CALL(collectorMock, start(Eq("name"), _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback({}); + }); + + EXPECT_CALL(storageMock, walCheckpointFull()).WillRepeatedly([&]() { notification.notify(); }); + + generator.generateImage("name", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + generator.generateImage("name2", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + notification.wait(); +} + +TEST_F(ImageCacheGenerator, Clean) +{ + ON_CALL(collectorMock, start(_, _, _)).WillByDefault([&](auto, auto captureCallback, auto) { + captureCallback({}); + waitInThread.wait(); + }); + generator.generateImage("name", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + generator.generateImage("name2", + {11}, + imageCallbackMock.AsStdFunction(), + abortCallbackMock.AsStdFunction()); + + EXPECT_CALL(imageCallbackMock, Call(_)).Times(0); + + generator.clean(); + waitInThread.notify(); +} + +} // namespace diff --git a/tests/unit/unittest/imagecachestorage-test.cpp b/tests/unit/unittest/imagecachestorage-test.cpp new file mode 100644 index 0000000000..aaf30526b6 --- /dev/null +++ b/tests/unit/unittest/imagecachestorage-test.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include "sqlitedatabasemock.h" + +#include <imagecachestorage.h> +#include <sqlitedatabase.h> + +namespace { + +MATCHER_P2(IsEntry, + image, + hasEntry, + std::string(negation ? "is't" : "is") + + PrintToString(QmlDesigner::ImageCacheStorageInterface::Entry{image, hasEntry})) +{ + const QmlDesigner::ImageCacheStorageInterface::Entry &entry = arg; + return entry.image == image && entry.hasEntry == hasEntry; +} + +class ImageCacheStorageTest : public testing::Test +{ +protected: + QImage createImage() + { + QImage image{150, 150, QImage::Format_ARGB32}; + image.fill(QColor{128, 64, 0, 11}); + image.setPixelColor(75, 75, QColor{1, 255, 33, 196}); + + return image; + } + +protected: + using ReadStatement = QmlDesigner::ImageCacheStorage<SqliteDatabaseMock>::ReadStatement; + using WriteStatement = QmlDesigner::ImageCacheStorage<SqliteDatabaseMock>::WriteStatement; + + NiceMock<SqliteDatabaseMock> databaseMock; + QmlDesigner::ImageCacheStorage<SqliteDatabaseMock> storage{databaseMock}; + ReadStatement &selectImageStatement = storage.selectImageStatement; + ReadStatement &selectIconStatement = storage.selectIconStatement; + WriteStatement &upsertImageStatement = storage.upsertImageStatement; + QImage image1{createImage()}; +}; + +TEST_F(ImageCacheStorageTest, Initialize) +{ + InSequence s; + + EXPECT_CALL(databaseMock, exclusiveBegin()); + EXPECT_CALL(databaseMock, + execute(Eq("CREATE TABLE IF NOT EXISTS images(id INTEGER PRIMARY KEY, name TEXT " + "NOT NULL UNIQUE, mtime INTEGER, image BLOB, icon BLOB)"))); + EXPECT_CALL(databaseMock, commit()); + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(databaseMock, prepare(Eq(selectImageStatement.sqlStatement))); + EXPECT_CALL(databaseMock, prepare(Eq(selectIconStatement.sqlStatement))); + EXPECT_CALL(databaseMock, prepare(Eq(upsertImageStatement.sqlStatement))); + EXPECT_CALL(databaseMock, commit()); + + QmlDesigner::ImageCacheStorage<SqliteDatabaseMock> storage{databaseMock}; +} + +TEST_F(ImageCacheStorageTest, FetchImageCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectImageStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchImage("/path/to/component", {123}); +} + +TEST_F(ImageCacheStorageTest, FetchImageCallsIsBusy) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectImageStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))) + .WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); + EXPECT_CALL(databaseMock, rollback()); + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectImageStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchImage("/path/to/component", {123}); +} + +TEST_F(ImageCacheStorageTest, FetchIconCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectIconStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchIcon("/path/to/component", {123}); +} + +TEST_F(ImageCacheStorageTest, FetchIconCallsIsBusy) +{ + InSequence s; + + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectIconStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))) + .WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); + EXPECT_CALL(databaseMock, rollback()); + EXPECT_CALL(databaseMock, deferredBegin()); + EXPECT_CALL(selectIconStatement, + valueReturnBlob(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123))); + EXPECT_CALL(databaseMock, commit()); + + storage.fetchIcon("/path/to/component", {123}); +} + +TEST_F(ImageCacheStorageTest, StoreImageCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(upsertImageStatement, + write(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123), + A<Sqlite::BlobView>(), + A<Sqlite::BlobView>())); + EXPECT_CALL(databaseMock, commit()); + + storage.storeImage("/path/to/component", {123}, image1); +} + +TEST_F(ImageCacheStorageTest, StoreEmptyImageCalls) +{ + InSequence s; + + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(upsertImageStatement, + write(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123), + A<Sqlite::NullValue>(), + A<Sqlite::NullValue>())); + EXPECT_CALL(databaseMock, commit()); + + storage.storeImage("/path/to/component", {123}, QImage{}); +} + +TEST_F(ImageCacheStorageTest, StoreImageCallsIsBusy) +{ + InSequence s; + + EXPECT_CALL(databaseMock, immediateBegin()).WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); + EXPECT_CALL(databaseMock, immediateBegin()); + EXPECT_CALL(upsertImageStatement, + write(TypedEq<Utils::SmallStringView>("/path/to/component"), + TypedEq<long long>(123), + A<Sqlite::NullValue>(), + A<Sqlite::NullValue>())); + EXPECT_CALL(databaseMock, commit()); + + storage.storeImage("/path/to/component", {123}, QImage{}); +} + +TEST_F(ImageCacheStorageTest, CallWalCheckointFull) +{ + EXPECT_CALL(databaseMock, walCheckpointFull()); + + storage.walCheckpointFull(); +} + +TEST_F(ImageCacheStorageTest, CallWalCheckointFullIsBusy) +{ + InSequence s; + + EXPECT_CALL(databaseMock, walCheckpointFull()).WillOnce(Throw(Sqlite::StatementIsBusy("busy"))); + EXPECT_CALL(databaseMock, walCheckpointFull()); + + storage.walCheckpointFull(); +} + +class ImageCacheStorageSlowTest : public testing::Test +{ +protected: + QImage createImage() + { + QImage image{150, 150, QImage::Format_ARGB32}; + image.fill(QColor{128, 64, 0, 11}); + image.setPixelColor(1, 1, QColor{1, 255, 33, 196}); + + return image; + } + +protected: + Sqlite::Database database{":memory:", Sqlite::JournalMode::Memory}; + QmlDesigner::ImageCacheStorage<Sqlite::Database> storage{database}; + QImage image1{createImage()}; + QImage image2{10, 10, QImage::Format_ARGB32}; + QImage icon1{image1.scaled(96, 96)}; +}; + +TEST_F(ImageCacheStorageSlowTest, StoreImage) +{ + storage.storeImage("/path/to/component", {123}, image1); + + ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(image1, true)); +} + +TEST_F(ImageCacheStorageSlowTest, StoreEmptyImageAfterEntry) +{ + storage.storeImage("/path/to/component", {123}, image1); + + storage.storeImage("/path/to/component", {123}, QImage{}); + + ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(QImage{}, true)); +} + +TEST_F(ImageCacheStorageSlowTest, StoreEmptyEntry) +{ + storage.storeImage("/path/to/component", {123}, QImage{}); + + ASSERT_THAT(storage.fetchImage("/path/to/component", {123}), IsEntry(QImage{}, true)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchNonExistingImageIsEmpty) +{ + auto image = storage.fetchImage("/path/to/component", {123}); + + ASSERT_THAT(image, IsEntry(QImage{}, false)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchSameTimeImage) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchImage("/path/to/component", {123}); + + ASSERT_THAT(image, IsEntry(image1, true)); +} + +TEST_F(ImageCacheStorageSlowTest, DoNotFetchOlderImage) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchImage("/path/to/component", {124}); + + ASSERT_THAT(image, IsEntry(QImage{}, false)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchNewerImage) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchImage("/path/to/component", {122}); + + ASSERT_THAT(image, IsEntry(image1, true)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchNonExistingIconIsEmpty) +{ + auto image = storage.fetchIcon("/path/to/component", {123}); + + ASSERT_THAT(image, IsEntry(QImage{}, false)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchSameTimeIcon) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchIcon("/path/to/component", {123}); + + ASSERT_THAT(image, IsEntry(icon1, true)); +} + +TEST_F(ImageCacheStorageSlowTest, DoNotFetchOlderIcon) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchIcon("/path/to/component", {124}); + + ASSERT_THAT(image, IsEntry(QImage{}, false)); +} + +TEST_F(ImageCacheStorageSlowTest, FetchNewerIcon) +{ + storage.storeImage("/path/to/component", {123}, image1); + + auto image = storage.fetchIcon("/path/to/component", {122}); + + ASSERT_THAT(image, IsEntry(icon1, true)); +} + +TEST_F(ImageCacheStorageSlowTest, DontScaleSmallerIcon) +{ + storage.storeImage("/path/to/component", {123}, image2); + + auto image = storage.fetchImage("/path/to/component", {122}); + + ASSERT_THAT(image, IsEntry(image2, true)); +} + +} // namespace diff --git a/tests/unit/unittest/mockimagecachegenerator.h b/tests/unit/unittest/mockimagecachegenerator.h new file mode 100644 index 0000000000..ffe8d9c709 --- /dev/null +++ b/tests/unit/unittest/mockimagecachegenerator.h @@ -0,0 +1,43 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <imagecachegeneratorinterface.h> + +class MockImageCacheGenerator : public QmlDesigner::ImageCacheGeneratorInterface +{ +public: + MOCK_METHOD(void, + generateImage, + (Utils::SmallStringView name, + Sqlite::TimeStamp timeStamp, + CaptureCallback &&captureCallback, + AbortCallback &&abortCallback), + (override)); + MOCK_METHOD(void, clean, (), (override)); +}; diff --git a/tests/unit/unittest/mockimagecachestorage.h b/tests/unit/unittest/mockimagecachestorage.h new file mode 100644 index 0000000000..add2cfaa96 --- /dev/null +++ b/tests/unit/unittest/mockimagecachestorage.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <imagecachestorageinterface.h> + +class MockImageCacheStorage : public QmlDesigner::ImageCacheStorageInterface +{ +public: + MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::Entry, + fetchImage, + (Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp), + (const, override)); + + MOCK_METHOD(QmlDesigner::ImageCacheStorageInterface::Entry, + fetchIcon, + (Utils::SmallStringView name, Sqlite::TimeStamp minimumTimeStamp), + (const, override)); + + MOCK_METHOD(void, + storeImage, + (Utils::SmallStringView name, Sqlite::TimeStamp newTimeStamp, const QImage &image), + (override)); + MOCK_METHOD(void, walCheckpointFull, (), (override)); +}; diff --git a/tests/unit/unittest/mocktimestampprovider.h b/tests/unit/unittest/mocktimestampprovider.h new file mode 100644 index 0000000000..0adad4c030 --- /dev/null +++ b/tests/unit/unittest/mocktimestampprovider.h @@ -0,0 +1,36 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <timestampproviderinterface.h> + +class MockTimeStampProvider : public QmlDesigner::TimeStampProviderInterface +{ +public: + MOCK_METHOD(Sqlite::TimeStamp, timeStamp, (Utils::SmallStringView name), (const, override)); +}; diff --git a/tests/unit/unittest/notification.h b/tests/unit/unittest/notification.h new file mode 100644 index 0000000000..a27d9acb76 --- /dev/null +++ b/tests/unit/unittest/notification.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "googletest.h" + +#include <condition_variable> +#include <mutex> + +class Notification +{ +public: + void wait(int count = 1) + { + std::unique_lock<std::mutex> lock{m_mutex}; + m_waitCount += count; + if (m_waitCount > 0) + m_condition.wait(lock, [&] { return m_waitCount <= 0; }); + } + + void notify() + { + { + std::unique_lock<std::mutex> lock{m_mutex}; + --m_waitCount; + } + + m_condition.notify_all(); + } + +private: + std::mutex m_mutex; + std::condition_variable m_condition; + int m_waitCount = 0; +}; diff --git a/tests/unit/unittest/sqlitedatabasemock.h b/tests/unit/unittest/sqlitedatabasemock.h new file mode 100644 index 0000000000..8c1179e424 --- /dev/null +++ b/tests/unit/unittest/sqlitedatabasemock.h @@ -0,0 +1,72 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include "sqlitereadstatementmock.h" +#include "sqlitetransactionbackendmock.h" +#include "sqlitewritestatementmock.h" + +#include <sqlitedatabaseinterface.h> +#include <sqlitetable.h> +#include <sqlitetransaction.h> + +#include <utils/smallstringview.h> + +class SqliteDatabaseMock : public SqliteTransactionBackendMock, public Sqlite::DatabaseInterface +{ +public: + using ReadStatement = NiceMock<SqliteReadStatementMock>; + using WriteStatement = NiceMock<SqliteWriteStatementMock>; + + MOCK_METHOD(void, prepare, (Utils::SmallStringView sqlStatement), ()); + + MOCK_METHOD(void, execute, (Utils::SmallStringView sqlStatement), ()); + + MOCK_METHOD(int64_t, lastInsertedRowId, (), (const)); + + MOCK_METHOD(void, setLastInsertedRowId, (int64_t), (const)); + + MOCK_METHOD(bool, isInitialized, (), (const)); + + MOCK_METHOD(void, setIsInitialized, (bool), ()); + + MOCK_METHOD(void, walCheckpointFull, (), (override)); + + MOCK_METHOD(void, + setUpdateHook, + (void *object, + void (*)(void *object, int, char const *database, char const *, long long rowId)), + (override)); + + MOCK_METHOD(void, resetUpdateHook, (), (override)); + + MOCK_METHOD(void, applyAndUpdateSessions, (), (override)); + + MOCK_METHOD(void, setAttachedTables, (const Utils::SmallStringVector &tables), (override)); +}; + diff --git a/tests/unit/unittest/sqlitereadstatementmock.cpp b/tests/unit/unittest/sqlitereadstatementmock.cpp new file mode 100644 index 0000000000..e3e22d4e16 --- /dev/null +++ b/tests/unit/unittest/sqlitereadstatementmock.cpp @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "sqlitereadstatementmock.h" + +#include "sqlitedatabasemock.h" + +SqliteReadStatementMock::SqliteReadStatementMock(Utils::SmallStringView sqlStatement, + SqliteDatabaseMock &databaseMock) + : sqlStatement(sqlStatement) +{ + databaseMock.prepare(sqlStatement); +} + +template<> +std::vector<Utils::SmallString> SqliteReadStatementMock::values<Utils::SmallString>(std::size_t reserveSize) +{ + return valuesReturnStringVector(reserveSize); +} + +template<> +std::vector<long long> SqliteReadStatementMock::values<long long>(std::size_t reserveSize) +{ + return valuesReturnRowIds(reserveSize); +} + +template<> +Utils::optional<long long> SqliteReadStatementMock::value<long long>() +{ + return valueReturnLongLong(); +} + +template<> +Utils::optional<Sqlite::ByteArrayBlob> SqliteReadStatementMock::value<Sqlite::ByteArrayBlob>( + const Utils::SmallStringView &name, const long long &blob) +{ + return valueReturnBlob(name, blob); +} diff --git a/tests/unit/unittest/sqlitereadstatementmock.h b/tests/unit/unittest/sqlitereadstatementmock.h new file mode 100644 index 0000000000..f74cce1e8e --- /dev/null +++ b/tests/unit/unittest/sqlitereadstatementmock.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <sqliteblob.h> +#include <utils/optional.h> +#include <utils/smallstring.h> + +#include <QImage> + +#include <cstdint> +#include <tuple> +#include <vector> + +class SqliteDatabaseMock; + +class SqliteReadStatementMock +{ +public: + SqliteReadStatementMock() = default; + SqliteReadStatementMock(Utils::SmallStringView sqlStatement, SqliteDatabaseMock &databaseMock); + + MOCK_METHOD(std::vector<Utils::SmallString>, valuesReturnStringVector, (std::size_t), ()); + + MOCK_METHOD(std::vector<long long>, valuesReturnRowIds, (std::size_t), ()); + MOCK_METHOD(Utils::optional<long long>, valueReturnLongLong, (), ()); + MOCK_METHOD(Utils::optional<Sqlite::ByteArrayBlob>, + valueReturnBlob, + (Utils::SmallStringView, long long), + ()); + + template<typename ResultType, int ResultTypeCount = 1, typename... QueryType> + std::vector<ResultType> values(std::size_t reserveSize, const QueryType &... queryValues); + + template <typename ResultType, + int ResultTypeCount = 1, + typename... QueryType> + std::vector<ResultType> values(std::size_t reserveSize); + + template <typename ResultType, + int ResultTypeCount = 1, + template <typename...> class QueryContainerType, + typename QueryElementType> + std::vector<ResultType> values(std::size_t reserveSize, + const QueryContainerType<QueryElementType> &queryValues); + + template <typename ResultType, + int ResultTypeCount = 1, + typename... QueryTypes> + Utils::optional<ResultType> value(const QueryTypes&... queryValues); + +public: + Utils::SmallString sqlStatement; +}; + +template<> +std::vector<Utils::SmallString> SqliteReadStatementMock::values<Utils::SmallString>( + std::size_t reserveSize); + +template<> +std::vector<long long> SqliteReadStatementMock::values<long long>(std::size_t reserveSize); + +template<> +Utils::optional<long long> SqliteReadStatementMock::value<long long>(); + +template<> +Utils::optional<Sqlite::ByteArrayBlob> SqliteReadStatementMock::value<Sqlite::ByteArrayBlob>( + const Utils::SmallStringView &name, const long long &blob); diff --git a/tests/unit/unittest/sqlitestatement-test.cpp b/tests/unit/unittest/sqlitestatement-test.cpp index 341679a903..19c897f7cb 100644 --- a/tests/unit/unittest/sqlitestatement-test.cpp +++ b/tests/unit/unittest/sqlitestatement-test.cpp @@ -27,6 +27,7 @@ #include "mocksqlitestatement.h" #include "sqliteteststatement.h" +#include <sqliteblob.h> #include <sqlitedatabase.h> #include <sqlitereadstatement.h> #include <sqlitereadwritestatement.h> @@ -38,14 +39,6 @@ #include <vector> -namespace Sqlite { -bool operator==(Utils::span<const byte> first, Utils::span<const byte> second) -{ - return first.size() == second.size() - && std::memcmp(first.data(), second.data(), first.size()) == 0; -} -} // namespace Sqlite - namespace { using Sqlite::Database; @@ -270,7 +263,7 @@ TEST_F(SqliteStatement, BindBlob) SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); const unsigned char chars[] = "aaafdfdlll"; auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars); - Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1}; + Sqlite::BlobView bytes{bytePointer, sizeof(chars) - 1}; statement.bind(1, bytes); statement.next(); @@ -281,7 +274,7 @@ TEST_F(SqliteStatement, BindBlob) TEST_F(SqliteStatement, BindEmptyBlob) { SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); - Utils::span<const Sqlite::byte> bytes; + Sqlite::BlobView bytes; statement.bind(1, bytes); statement.next(); @@ -341,7 +334,7 @@ TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundValu TEST_F(SqliteStatement, BindIndexIsToLargeIsThrowingBindingIndexIsOutOfBoundBlob) { SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); - Utils::span<const Sqlite::byte> bytes; + Sqlite::BlobView bytes; ASSERT_THROW(statement.bind(2, bytes), Sqlite::BindingIndexIsOutOfRange); } @@ -408,34 +401,25 @@ TEST_F(SqliteStatement, WriteEmptyBlobs) { SqliteTestStatement statement("WITH T(blob) AS (VALUES (?)) SELECT blob FROM T", database); - Utils::span<const Sqlite::byte> bytes; + Sqlite::BlobView bytes; statement.write(bytes); ASSERT_THAT(statement.fetchBlobValue(0), IsEmpty()); } -class Blob -{ -public: - Blob(Utils::span<const Sqlite::byte> bytes) - : bytes(bytes.begin(), bytes.end()) - {} - - std::vector<Sqlite::byte> bytes; -}; - TEST_F(SqliteStatement, WriteBlobs) { SqliteTestStatement statement("INSERT INTO test VALUES ('blob', 40, ?)", database); SqliteTestStatement readStatement("SELECT value FROM test WHERE name = 'blob'", database); const unsigned char chars[] = "aaafdfdlll"; auto bytePointer = reinterpret_cast<const Sqlite::byte *>(chars); - Utils::span<const Sqlite::byte> bytes{bytePointer, sizeof(chars) - 1}; + Sqlite::BlobView bytes{bytePointer, sizeof(chars) - 1}; statement.write(bytes); - ASSERT_THAT(readStatement.template value<Blob>(), Optional(Field(&Blob::bytes, Eq(bytes)))); + ASSERT_THAT(readStatement.template value<Sqlite::Blob>(), + Optional(Field(&Sqlite::Blob::bytes, Eq(bytes)))); } TEST_F(SqliteStatement, CannotWriteToClosedDatabase) @@ -624,38 +608,38 @@ TEST_F(SqliteStatement, GetBlobValues) ReadStatement statement("SELECT value FROM test WHERE name='blob'", database); const int value = 0xDDCCBBAA; auto bytePointer = reinterpret_cast<const Sqlite::byte *>(&value); - Utils::span<const Sqlite::byte> bytes{bytePointer, 4}; + Sqlite::BlobView bytes{bytePointer, 4}; - auto values = statement.values<Blob>(1); + auto values = statement.values<Sqlite::Blob>(1); - ASSERT_THAT(values, ElementsAre(Field(&Blob::bytes, Eq(bytes)))); + ASSERT_THAT(values, ElementsAre(Field(&Sqlite::Blob::bytes, Eq(bytes)))); } TEST_F(SqliteStatement, GetEmptyBlobValueForInteger) { ReadStatement statement("SELECT value FROM test WHERE name='poo'", database); - auto value = statement.value<Blob>(); + auto value = statement.value<Sqlite::Blob>(); - ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); + ASSERT_THAT(value, Optional(Field(&Sqlite::Blob::bytes, IsEmpty()))); } TEST_F(SqliteStatement, GetEmptyBlobValueForFloat) { ReadStatement statement("SELECT number FROM test WHERE name='foo'", database); - auto value = statement.value<Blob>(); + auto value = statement.value<Sqlite::Blob>(); - ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); + ASSERT_THAT(value, Optional(Field(&Sqlite::Blob::bytes, IsEmpty()))); } TEST_F(SqliteStatement, GetEmptyBlobValueForText) { ReadStatement statement("SELECT number FROM test WHERE name='bar'", database); - auto value = statement.value<Blob>(); + auto value = statement.value<Sqlite::Blob>(); - ASSERT_THAT(value, Optional(Field(&Blob::bytes, IsEmpty()))); + ASSERT_THAT(value, Optional(Field(&Sqlite::Blob::bytes, IsEmpty()))); } TEST_F(SqliteStatement, GetOptionalSingleValueAndMultipleQueryValue) diff --git a/tests/unit/unittest/sqlitestatementmock.h b/tests/unit/unittest/sqlitestatementmock.h new file mode 100644 index 0000000000..ad8a73e0a5 --- /dev/null +++ b/tests/unit/unittest/sqlitestatementmock.h @@ -0,0 +1,105 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include <googletest.h> + +#include <sqlitebasestatement.h> + +class BaseSqliteStatementMock +{ +public: + MOCK_METHOD(bool, next, ()); + MOCK_METHOD(void, step, ()); + MOCK_METHOD(void, reset, ()); + + MOCK_METHOD(int, fetchIntValue, (int), (const)); + MOCK_METHOD(long, fetchLongValue, (int), (const)); + MOCK_METHOD(long long, fetchLongLongValue, (int), (const)); + MOCK_METHOD(double, fetchDoubleValue, (int), (const)); + MOCK_METHOD(Utils::SmallString, fetchSmallStringValue, (int), (const)); + MOCK_METHOD(Utils::PathString, fetchPathStringValue, (int), (const)); + + template<typename Type> + Type fetchValue(int column) const; + + MOCK_METHOD(void, bind, (int, int), ()); + MOCK_METHOD(void, bind, (int, long long), ()); + MOCK_METHOD(void, bind, (int, double), ()); + MOCK_METHOD(void, bind, (int, Utils::SmallStringView), ()); + MOCK_METHOD(void, bind, (int, long) ); + MOCK_METHOD(int, bindingIndexForName, (Utils::SmallStringView name), (const)); + + MOCK_METHOD(void, prepare, (Utils::SmallStringView sqlStatement)); +}; + +template<> +int BaseSqliteStatementMock::fetchValue<int>(int column) const +{ + return fetchIntValue(column); +} + +template<> +long BaseSqliteStatementMock::fetchValue<long>(int column) const +{ + return fetchLongValue(column); +} + +template<> +long long BaseSqliteStatementMock::fetchValue<long long>(int column) const +{ + return fetchLongLongValue(column); +} + +template<> +double BaseSqliteStatementMock::fetchValue<double>(int column) const +{ + return fetchDoubleValue(column); +} + +template<> +Utils::SmallString BaseSqliteStatementMock::fetchValue<Utils::SmallString>(int column) const +{ + return fetchSmallStringValue(column); +} + +template<> +Utils::PathString BaseSqliteStatementMock::fetchValue<Utils::PathString>(int column) const +{ + return fetchPathStringValue(column); +} + +class SqliteStatementMock : public Sqlite::StatementImplementation<NiceMock<BaseSqliteStatementMock>> +{ +public: + explicit SqliteStatementMock() + : Sqlite::StatementImplementation<NiceMock<BaseSqliteStatementMock>>() + {} + + +protected: + void checkIsWritableStatement(); +}; diff --git a/tests/unit/unittest/sqlitetransactionbackendmock.h b/tests/unit/unittest/sqlitetransactionbackendmock.h new file mode 100644 index 0000000000..7aad4f1f6a --- /dev/null +++ b/tests/unit/unittest/sqlitetransactionbackendmock.h @@ -0,0 +1,46 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + + +#include "googletest.h" + +#include <sqlitetransaction.h> + +class SqliteTransactionBackendMock : public Sqlite::TransactionInterface +{ +public: + MOCK_METHOD(void, deferredBegin, (), (override)); + MOCK_METHOD(void, immediateBegin, (), (override)); + MOCK_METHOD(void, exclusiveBegin, (), (override)); + MOCK_METHOD(void, commit, (), (override)); + MOCK_METHOD(void, rollback, (), (override)); + MOCK_METHOD(void, lock, (), (override)); + MOCK_METHOD(void, unlock, (), (override)); + MOCK_METHOD(void, immediateSessionBegin, (), (override)); + MOCK_METHOD(void, sessionCommit, (), (override)); + MOCK_METHOD(void, sessionRollback, (), (override)); +}; diff --git a/tests/unit/unittest/sqlitewritestatementmock.cpp b/tests/unit/unittest/sqlitewritestatementmock.cpp new file mode 100644 index 0000000000..a3612833b2 --- /dev/null +++ b/tests/unit/unittest/sqlitewritestatementmock.cpp @@ -0,0 +1,35 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "sqlitewritestatementmock.h" + +#include "sqlitedatabasemock.h" + +SqliteWriteStatementMock::SqliteWriteStatementMock(Utils::SmallStringView sqlStatement, + SqliteDatabaseMock &database) + : sqlStatement(sqlStatement) +{ + database.prepare(sqlStatement); +} diff --git a/tests/unit/unittest/sqlitewritestatementmock.h b/tests/unit/unittest/sqlitewritestatementmock.h new file mode 100644 index 0000000000..3a8d159329 --- /dev/null +++ b/tests/unit/unittest/sqlitewritestatementmock.h @@ -0,0 +1,84 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "googletest.h" + +#include <sqliteblob.h> +#include <sqlitevalue.h> + +class SqliteDatabaseMock; + +class SqliteWriteStatementMock +{ +public: + SqliteWriteStatementMock() = default; + SqliteWriteStatementMock(Utils::SmallStringView sqlStatement, SqliteDatabaseMock &database); + + MOCK_METHOD(void, execute, (), ()); + + MOCK_METHOD(void, write, (Utils::SmallStringView), ()); + MOCK_METHOD(void, write, (long long), ()); + MOCK_METHOD(void, write, (Utils::SmallStringView, Utils::SmallStringView), ()); + MOCK_METHOD(void, write, (long long, Utils::SmallStringView), ()); + MOCK_METHOD(void, + write, + (Utils::SmallStringView, Utils::SmallStringView, Utils::SmallStringView), + ()); + MOCK_METHOD(void, write, (Utils::SmallStringView, Utils::SmallStringView, long long), ()); + MOCK_METHOD(void, write, (Utils::SmallStringView, Utils::SmallStringView, double), ()); + MOCK_METHOD(void, write, (long long, Utils::SmallStringView, Utils::SmallStringView), ()); + MOCK_METHOD(void, write, (long long, Utils::SmallStringView, const Sqlite::Value &), ()); + MOCK_METHOD(void, write, (Utils::SmallStringView, long long, Sqlite::BlobView, Sqlite::BlobView), ()); + MOCK_METHOD(void, + write, + (Utils::SmallStringView, long long, Sqlite::NullValue, Sqlite::NullValue), + ()); + MOCK_METHOD(void, + write, + (Utils::SmallStringView, + Utils::SmallStringView, + Utils::SmallStringView, + Utils::SmallStringView), + ()); + MOCK_METHOD(void, + write, + (long long, Utils::SmallStringView, Utils::SmallStringView, Utils::SmallStringView), + ()); + MOCK_METHOD(void, write, (long long, long long, Utils::SmallStringView, Utils::SmallStringView), ()); + MOCK_METHOD(void, + write, + (Utils::SmallStringView, + Utils::SmallStringView, + Utils::SmallStringView, + Utils::SmallStringView, + Utils::SmallStringView), + ()); + + MOCK_METHOD(void, write, (void *, long long), ()); + + Utils::SmallString sqlStatement; +}; diff --git a/tests/unit/unittest/unittest.pro b/tests/unit/unittest/unittest.pro index 967463bbdc..6200a24d7f 100644 --- a/tests/unit/unittest/unittest.pro +++ b/tests/unit/unittest/unittest.pro @@ -66,6 +66,9 @@ SOURCES += \ filepathview-test.cpp \ gtest-creator-printing.cpp \ gtest-qt-printing.cpp \ + imagecache-test.cpp \ + imagecachegenerator-test.cpp \ + imagecachestorage-test.cpp \ lastchangedrowid-test.cpp \ lineprefixer-test.cpp \ listmodeleditor-test.cpp \ @@ -134,7 +137,9 @@ SOURCES += \ sqlitestatement-test.cpp \ sqlitetable-test.cpp \ sqlstatementbuilder-test.cpp \ - createtablesqlstatementbuilder-test.cpp + createtablesqlstatementbuilder-test.cpp \ + sqlitereadstatementmock.cpp \ + sqlitewritestatementmock.cpp !isEmpty(QTC_UNITTEST_BUILD_CPP_PARSER):SOURCES += matchingtext-test.cpp @@ -240,12 +245,15 @@ HEADERS += \ gtest-llvm-printing.h \ gtest-qt-printing.h \ gtest-std-printing.h \ + imagecachecollectormock.h \ mimedatabase-utilities.h \ mockclangcodemodelclient.h \ mockclangcodemodelserver.h \ mockclangpathwatcher.h \ mockclangpathwatchernotifier.h \ mockfilesystem.h \ + mockimagecachegenerator.h \ + mockimagecachestorage.h \ mocklistmodeleditorview.h \ mockpchcreator.h \ mockpchmanagerclient.h \ @@ -258,6 +266,8 @@ HEADERS += \ mocksearchhandle.h \ mocksearchresult.h \ mocksyntaxhighligher.h \ + mocktimestampprovider.h \ + notification.h \ processevents-utilities.h \ sourcerangecontainer-matcher.h \ spydummy.h \ @@ -301,7 +311,12 @@ HEADERS += \ mockpchtaskgenerator.h \ ../mockup/qmldesigner/designercore/include/nodeinstanceview.h \ ../mockup/qmldesigner/designercore/include/rewriterview.h \ - ../mockup/qmldesigner/designercore/include/itemlibraryitem.h + ../mockup/qmldesigner/designercore/include/itemlibraryitem.h\ + sqlitedatabasemock.h \ + sqlitereadstatementmock.h \ + sqlitestatementmock.h \ + sqlitetransactionbackendmock.h \ + sqlitewritestatementmock.h !isEmpty(LIBCLANG_LIBS) { diff --git a/tests/unit/unittest/unittest.qbs b/tests/unit/unittest/unittest.qbs index 5ceb723b22..ac7eb08b82 100644 --- a/tests/unit/unittest/unittest.qbs +++ b/tests/unit/unittest/unittest.qbs @@ -333,6 +333,19 @@ Project { "unittests-main.cpp", "usedmacrofilter-test.cpp", "utf8-test.cpp", + "imagecache-test.cpp", + "imagecachegenerator-test.cpp", + "imagecachestorage-test.cpp", + "sqlitedatabasemock.h", + "sqlitereadstatementmock.h", + "sqlitestatementmock.h", + "sqlitetransactionbackendmock.h", + "sqlitewritestatementmock.h", + "notification.h", + "mocktimestampprovider.h", + "imagecachecollectormock.h", + "mockimagecachegenerator.h", + "mockimagecachestorage.h", ] Group { |