diff options
author | Eike Ziller <eike.ziller@qt.io> | 2020-10-19 15:22:37 +0200 |
---|---|---|
committer | Eike Ziller <eike.ziller@qt.io> | 2020-10-19 15:22:37 +0200 |
commit | 80951d3e59f8b6ff270333692adfe2d65adf6c9c (patch) | |
tree | 8a5da6c06f7558d847e24dac386bb60775bd7d76 | |
parent | af0e135e7328d5672e54d08ba35765308e554f23 (diff) | |
parent | 436f111a4609462fcaae501b3d6d9b072adbb0b0 (diff) |
Merge remote-tracking branch 'origin/4.14' into master
Conflicts:
src/plugins/cppeditor/cppquickfix_test.cpp
Change-Id: Ib2984a3b3d9d071d11304b6cf132c2f8cef77e1c
247 files changed, 6127 insertions, 701 deletions
diff --git a/cmake/CreatePythonXY.cmake b/cmake/CreatePythonXY.cmake new file mode 100644 index 0000000000..8aeeb156cb --- /dev/null +++ b/cmake/CreatePythonXY.cmake @@ -0,0 +1,79 @@ +# create_python_xy function will precompile the Python/lib/*.py files +# and create a zip file containing all the pyc files +function(create_python_xy PythonExe PythonZipFilePath) + get_filename_component(python_lib_dir "${PythonExe}" DIRECTORY) + get_filename_component(python_lib_dir "${python_lib_dir}/Lib" ABSOLUTE) + foreach(dir collections encodings importlib json urllib) + file(COPY ${python_lib_dir}/${dir} + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/python-lib + FILES_MATCHING PATTERN "*.py" + ) + endforeach() + file(GLOB python_lib_files "${python_lib_dir}/*.py") + foreach(not_needed + aifc.py imghdr.py socket.py + antigravity.py imp.py socketserver.py + argparse.py ipaddress.py ssl.py + asynchat.py locale.py statistics.py + asyncore.py lzma.py string.py + bdb.py mailbox.py stringprep.py + binhex.py mailcap.py sunau.py + bisect.py mimetypes.py symbol.py + bz2.py modulefinder.py symtable.py + calendar.py netrc.py tabnanny.py + cgi.py nntplib.py tarfile.py + cgitb.py nturl2path.py telnetlib.py + chunk.py numbers.py tempfile.py + cmd.py optparse.py textwrap.py + code.py pathlib.py this.py + codeop.py pdb.py timeit.py + colorsys.py pickle.py trace.py + compileall.py pickletools.py tracemalloc.py + configparser.py pipes.py tty.py + contextvars.py plistlib.py turtle.py + cProfile.py poplib.py typing.py + crypt.py pprint.py uu.py + csv.py profile.py uuid.py + dataclasses.py pstats.py wave.py + datetime.py pty.py webbrowser.py + decimal.py pyclbr.py xdrlib.py + difflib.py py_compile.py zipapp.py + doctest.py queue.py zipfile.py + dummy_threading.py quopri.py zipimport.py + filecmp.py random.py _compat_pickle.py + fileinput.py rlcompleter.py _compression.py + formatter.py runpy.py _dummy_thread.py + fractions.py sched.py _markupbase.py + ftplib.py secrets.py _osx_support.py + getopt.py selectors.py _pydecimal.py + getpass.py shelve.py _pyio.py + gettext.py shlex.py _py_abc.py + gzip.py shutil.py _strptime.py + hashlib.py smtpd.py _threading_local.py + hmac.py smtplib.py __future__.py + imaplib.py sndhdr.py __phello__.foo.py + ) + list(FIND python_lib_files "${python_lib_dir}/${not_needed}" found_not_needed) + if (NOT found_not_needed STREQUAL "-1") + list(REMOVE_AT python_lib_files ${found_not_needed}) + endif() + endforeach() + + file(COPY ${python_lib_files} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/python-lib") + + set(ENV{PYTHONOPTIMIZE} "2") + execute_process( + COMMAND "${PythonExe}" -OO -m compileall "${CMAKE_CURRENT_BINARY_DIR}/python-lib" -b + ) + + file(GLOB_RECURSE python_lib_files "${CMAKE_CURRENT_BINARY_DIR}/python-lib/*.py") + file(REMOVE ${python_lib_files}) + + file(GLOB_RECURSE python_lib_files LIST_DIRECTORIES ON "${CMAKE_CURRENT_BINARY_DIR}/python-lib/*/__pycache__$") + file(REMOVE_RECURSE ${python_lib_files}) + + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar cf "${PythonZipFilePath}" . --format=zip + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/python-lib/" + ) +endfunction() diff --git a/doc/qtcreator/images/qtcreator-cmake-build-settings.png b/doc/qtcreator/images/qtcreator-cmake-build-settings.png Binary files differindex 33be38d67c..e05e124f8a 100644 --- a/doc/qtcreator/images/qtcreator-cmake-build-settings.png +++ b/doc/qtcreator/images/qtcreator-cmake-build-settings.png diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdocinc b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdocinc index d197facbc6..9b6aa3f0d6 100644 --- a/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdocinc +++ b/doc/qtcreator/src/cmake/creator-projects-cmake-building.qdocinc @@ -46,24 +46,32 @@ of the parameters that are passed to CMake. Parameter names are listed in the \uicontrol Key column. Names with a common prefix (up to the first underscore character) are grouped under the prefix. To view all parameters, - select the \uicontrol Advanced check box. + select the \uicontrol Advanced check box. To add parameters, select \uicontrol Add, and then select the type of the parameter that you are adding: \uicontrol Boolean, \uicontrol String, \uicontrol Directory, or \uicontrol File. - To change the type of the selected parameter, select + To change the type of the selected parameter, right-click the + parameter name in the \uicontrol Key column, and then select \uicontrol {Force to bool}, \uicontrol {Force to file}, - \uicontrol {Force to directory}, or \uicontrol {Force to string}. + \uicontrol {Force to directory}, or \uicontrol {Force to string} + in the context menu. To modify the value of a parameter, double-click it, or select it, - and then select \uicontrol Edit. To save the changes, select - \uicontrol {Apply Configuration Changes}. Keep in mind that a - configuration change might trigger a follow-up configuration change. + and then select \uicontrol Edit. - To remove a parameter, select \uicontrol Unset. + You can apply actions to multiple parameters at a time. To clear + the selection, select \uicontrol {Clear Selection}. - To reset the changes that you made, select \uicontrol Reset. + To remove the selected parameters, select \uicontrol Unset. To undo + the removal, select \uicontrol Set. + + To reset all the changes that you made, select \uicontrol Reset. + + To save the changes, select \uicontrol {Apply Configuration Changes}. + Keep in mind that a configuration change might trigger a follow-up + configuration change. The parameter values that you change are passed via \c -D<option>=<value> to CMake, which stores the options in the CMakeCache.txt file. This means 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/images/non-visual-component.png b/share/qtcreator/qml/qmlpuppet/images/non-visual-component.png Binary files differnew file mode 100644 index 0000000000..f66e2b8b11 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/images/non-visual-component.png diff --git a/share/qtcreator/qml/qmlpuppet/images/non-visual-component@2x.png b/share/qtcreator/qml/qmlpuppet/images/non-visual-component@2x.png Binary files differnew file mode 100644 index 0000000000..25a136ff25 --- /dev/null +++ b/share/qtcreator/qml/qmlpuppet/images/non-visual-component@2x.png 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/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp index 45d5fce845..183f036bf7 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp @@ -72,22 +72,23 @@ #include <requestmodelnodepreviewimagecommand.h> #include <changelanguagecommand.h> +#include <designersupportdelegate.h> +#include <QAbstractAnimation> #include <QDebug> -#include <QQmlEngine> -#include <QQmlApplicationEngine> -#include <QFileSystemWatcher> -#include <QUrl> -#include <QSet> #include <QDir> -#include <QVariant> +#include <QFileSystemWatcher> #include <QMetaType> +#include <QMutableVectorIterator> +#include <QQmlApplicationEngine> #include <QQmlComponent> #include <QQmlContext> -#include <qqmllist.h> -#include <QAbstractAnimation> +#include <QQmlEngine> +#include <QQuickItemGrabResult> #include <QQuickView> #include <QSet> -#include <designersupportdelegate.h> +#include <QUrl> +#include <QVariant> +#include <qqmllist.h> #include <algorithm> @@ -1461,4 +1462,19 @@ void NodeInstanceServer::disableTimer() m_timerMode = TimerMode::DisableTimer; } +void NodeInstanceServer::sheduleRootItemRender() +{ + QSharedPointer<QQuickItemGrabResult> result = m_rootNodeInstance.createGrabResult(); + qint32 instanceId = m_rootNodeInstance.instanceId(); + + if (result) { + connect(result.data(), &QQuickItemGrabResult::ready, [this, result, instanceId] { + QVector<ImageContainer> imageVector; + ImageContainer container(instanceId, result->image(), instanceId); + imageVector.append(container); + nodeInstanceClient()->pixmapChanged(PixmapChangedCommand(imageVector)); + }); + } +} + } // namespace QmlDesigner diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h index bd6349d29a..d5726a31f6 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h @@ -241,6 +241,8 @@ protected: ComponentCompletedCommand createComponentCompletedCommand(const QList<ServerNodeInstance> &instanceList); ChangeSelectionCommand createChangeSelectionCommand(const QList<ServerNodeInstance> &instanceList); + void sheduleRootItemRender(); + void addChangedProperty(const InstancePropertyPair &property); virtual void startRenderTimer(); 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/objectnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp index 5991e2b28e..d69846d400 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.cpp @@ -908,6 +908,11 @@ QImage ObjectNodeInstance::renderPreviewImage(const QSize & /*previewImageSize*/ return QImage(); } +QSharedPointer<QQuickItemGrabResult> ObjectNodeInstance::createGrabResult() const +{ + return {}; +} + QObject *ObjectNodeInstance::parent() const { if (!object()) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h index 3026ffefce..29e7dc640b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/objectnodeinstance.h @@ -84,6 +84,8 @@ public: virtual QImage renderImage() const; virtual QImage renderPreviewImage(const QSize &previewImageSize) const; + virtual QSharedPointer<QQuickItemGrabResult> createGrabResult() const; + virtual QObject *parent() const; Pointer parentInstance() const; 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/qt5informationnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 2963119def..ecd066d16c 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -101,6 +101,30 @@ static QVariant objectToVariant(QObject *object) return QVariant::fromValue(object); } +static QImage nonVisualComponentPreviewImage() +{ + static double ratio = qgetenv("FORMEDITOR_DEVICE_PIXEL_RATIO").toDouble(); + if (ratio == 1.) { + static const QImage image(":/qtquickplugin/images/non-visual-component.png"); + return image; + } else { + static const QImage image(":/qtquickplugin/images/non-visual-component@2x.png"); + return image; + } +} + +static bool imageHasContent(const QImage &image) +{ + // Check if any image pixel contains non-zero data + const uchar *pData = image.constBits(); + const qsizetype size = image.sizeInBytes(); + for (qsizetype i = 0; i < size; ++i) { + if (*(pData++) != 0) + return true; + } + return false; +} + QQuickView *Qt5InformationNodeInstanceServer::createAuxiliaryQuickView(const QUrl &url, QQuickItem *&rootItem) { @@ -556,7 +580,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode3DImageView() ServerNodeInstance instance = instanceForId(m_modelNodePreviewImageCommand.instanceId()); instanceObj = instance.internalObject(); } - QSize renderSize = m_modelNodePreviewImageCommand.size() * 2; + QSize renderSize = m_modelNodePreviewImageCommand.size(); QMetaObject::invokeMethod(m_ModelNode3DImageViewRootItem, "createViewForObject", Q_ARG(QVariant, objectToVariant(instanceObj)), @@ -652,7 +676,7 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() // Some component may expect to always be shown at certain size, so their layouts may // not support scaling, so let's always render at the default size if item has one and // scale the resulting image instead. - QSize finalSize = m_modelNodePreviewImageCommand.size() * 2; + QSize finalSize = m_modelNodePreviewImageCommand.size(); QRectF renderRect = itemBoundingRect(instanceItem); QSize renderSize = renderRect.size().toSize(); if (renderSize.isEmpty()) { @@ -665,6 +689,9 @@ void Qt5InformationNodeInstanceServer::doRenderModelNode2DImageView() renderImage = designerSupport()->renderImageForItem(m_ModelNode2DImageViewContentItem, renderRect, renderSize); + if (!imageHasContent(renderImage)) + renderImage = nonVisualComponentPreviewImage(); + if (renderSize != finalSize) renderImage = renderImage.scaled(finalSize, Qt::KeepAspectRatio); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index defbf5614a..93e6e786cd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -28,11 +28,13 @@ #include <QCoreApplication> #include "capturenodeinstanceserverdispatcher.h" +#include "qt5captureimagenodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" #include "qt5previewnodeinstanceserver.h" #include "qt5rendernodeinstanceserver.h" #include "qt5testnodeinstanceserver.h" +#include "quickitemnodeinstance.h" #include <designersupportdelegate.h> @@ -56,7 +58,19 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : NodeInstanceClientProxy(parent) { prioritizeDown(); - DesignerSupport::activateDesignerWindowManager(); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + const bool qt6 = false; +#else + const bool qt6 = true; +#endif + + const bool unifiedRenderPath = qt6 || qEnvironmentVariableIsSet("QMLPUPPET_UNIFIED_RENDER_PATH"); + + if (unifiedRenderPath) + Internal::QuickItemNodeInstance::enableUnifiedRenderPath(true); + else + DesignerSupport::activateDesignerWindowManager(); + if (QCoreApplication::arguments().at(1) == QLatin1String("--readcapturedstream")) { qputenv("DESIGNER_DONT_USE_SHARED_MEMORY", "1"); setNodeInstanceServer(std::make_unique<Qt5TestNodeInstanceServer>(this)); @@ -79,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/qt5nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp index 8a4b086aa5..c2015299c5 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5nodeinstanceserver.cpp @@ -64,6 +64,8 @@ void Qt5NodeInstanceServer::initializeView() m_quickView = new QQuickView; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + /* enables grab window without show */ QSurfaceFormat surfaceFormat = m_quickView->requestedFormat(); surfaceFormat.setVersion(4, 1); surfaceFormat.setProfile(QSurfaceFormat::CoreProfile); @@ -72,6 +74,7 @@ void Qt5NodeInstanceServer::initializeView() m_quickView->setFormat(surfaceFormat); DesignerSupport::createOpenGLContext(m_quickView.data()); +#endif if (qEnvironmentVariableIsSet("QML_FILE_SELECTORS")) { QQmlFileSelector *fileSelector = new QQmlFileSelector(engine(), engine()); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp index 08b20ccd22..fd3446f396 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5rendernodeinstanceserver.cpp @@ -87,17 +87,27 @@ void Qt5RenderNodeInstanceServer::collectItemChangesAndSendChangeCommands() if (ancestorInstance.isValid()) m_dirtyInstanceSet.insert(ancestorInstance); } - DesignerSupport::updateDirtyNode(item); + Internal::QuickItemNodeInstance::updateDirtyNode(item); } } clearChangedPropertyList(); - if (!m_dirtyInstanceSet.isEmpty()) { - nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand(QtHelpers::toList(m_dirtyInstanceSet))); - m_dirtyInstanceSet.clear(); + if (Internal::QuickItemNodeInstance::unifiedRenderPath()) { + /* QQuickItem::grabToImage render path */ + /* TODO implement QQuickItem::grabToImage based rendering */ + /* sheduleRootItemRender(); */ + nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand({rootNodeInstance()})); + } else { + if (!m_dirtyInstanceSet.isEmpty()) { + nodeInstanceClient()->pixmapChanged( + createPixmapChangedCommand(QtHelpers::toList(m_dirtyInstanceSet))); + m_dirtyInstanceSet.clear(); + } } + m_dirtyInstanceSet.clear(); + resetAllItems(); slowDownRenderTimer(); @@ -137,6 +147,11 @@ void Qt5RenderNodeInstanceServer::createScene(const CreateSceneCommand &command) } nodeInstanceClient()->pixmapChanged(createPixmapChangedCommand(instanceList)); + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +#else + quickView()->show(); +#endif } void Qt5RenderNodeInstanceServer::clearScene(const ClearSceneCommand &command) diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp index 6e67d58b92..1f54bffdbd 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.cpp @@ -41,6 +41,7 @@ namespace QmlDesigner { namespace Internal { bool QuickItemNodeInstance::s_createEffectItem = false; +bool QuickItemNodeInstance::s_unifiedRenderPath = false; QuickItemNodeInstance::QuickItemNodeInstance(QQuickItem *item) : ObjectNodeInstance(item), @@ -58,7 +59,7 @@ QuickItemNodeInstance::QuickItemNodeInstance(QQuickItem *item) QuickItemNodeInstance::~QuickItemNodeInstance() { - if (quickItem()) + if (quickItem() && checkIfRefFromEffect(instanceId())) designerSupport()->derefFromEffectItem(quickItem()); } @@ -156,6 +157,19 @@ void QuickItemNodeInstance::createEffectItem(bool createEffectItem) s_createEffectItem = createEffectItem; } +void QuickItemNodeInstance::enableUnifiedRenderPath(bool unifiedRenderPath) +{ + s_unifiedRenderPath = unifiedRenderPath; +} + +bool QuickItemNodeInstance::checkIfRefFromEffect(qint32 id) +{ + if (s_unifiedRenderPath) + return false; + + return (s_createEffectItem || id == 0); +} + void QuickItemNodeInstance::initialize(const ObjectNodeInstance::Pointer &objectNodeInstance, InstanceContainer::NodeFlags flags) { @@ -166,10 +180,10 @@ void QuickItemNodeInstance::initialize(const ObjectNodeInstance::Pointer &object quickItem()->setParentItem(qobject_cast<QQuickItem*>(nodeInstanceServer()->quickView()->rootObject())); } - if (quickItem()->window()) { - if (s_createEffectItem || instanceId() == 0) - designerSupport()->refFromEffectItem(quickItem(), - !flags.testFlag(InstanceContainer::ParentTakesOverRendering)); + if (quickItem()->window() && checkIfRefFromEffect(instanceId())) { + designerSupport()->refFromEffectItem(quickItem(), + !flags.testFlag( + InstanceContainer::ParentTakesOverRendering)); } ObjectNodeInstance::initialize(objectNodeInstance, flags); @@ -246,6 +260,20 @@ QStringList QuickItemNodeInstance::allStates() const return list; } +void QuickItemNodeInstance::updateDirtyNode(QQuickItem *item) +{ + if (s_unifiedRenderPath) { + item->update(); + return; + } + DesignerSupport::updateDirtyNode(item); +} + +bool QuickItemNodeInstance::unifiedRenderPath() +{ + return s_unifiedRenderPath; +} + QRectF QuickItemNodeInstance::contentItemBoundingBox() const { if (contentItem()) { @@ -378,6 +406,9 @@ double QuickItemNodeInstance::y() const QImage QuickItemNodeInstance::renderImage() const { + if (s_unifiedRenderPath && !isRootNodeInstance()) + return {}; + updateDirtyNodesRecursive(quickItem()); QRectF renderBoundingRect = boundingRect(); @@ -390,7 +421,16 @@ QImage QuickItemNodeInstance::renderImage() const nodeInstanceServer()->quickView()->beforeSynchronizing(); nodeInstanceServer()->quickView()->beforeRendering(); - QImage renderImage = designerSupport()->renderImageForItem(quickItem(), renderBoundingRect, size); + QImage renderImage; + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (s_unifiedRenderPath) + renderImage = nodeInstanceServer()->quickView()->grabWindow(); + else + renderImage = designerSupport()->renderImageForItem(quickItem(), renderBoundingRect, size); +#else + renderImage = nodeInstanceServer()->quickView()->grabWindow(); +#endif nodeInstanceServer()->quickView()->afterRendering(); @@ -411,7 +451,20 @@ QImage QuickItemNodeInstance::renderPreviewImage(const QSize &previewImageSize) nodeInstanceServer()->quickView()->beforeSynchronizing(); nodeInstanceServer()->quickView()->beforeRendering(); - QImage image = designerSupport()->renderImageForItem(quickItem(), previewItemBoundingRect, size); + QImage image; +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + if (s_unifiedRenderPath) + image = nodeInstanceServer()->quickView()->grabWindow(); + else + image = designerSupport()->renderImageForItem(quickItem(), + previewItemBoundingRect, + size); + +#else + image = nodeInstanceServer()->quickView()->grabWindow(); +#endif + + image = image.scaledToWidth(size.width()); nodeInstanceServer()->quickView()->afterRendering(); @@ -426,6 +479,11 @@ QImage QuickItemNodeInstance::renderPreviewImage(const QSize &previewImageSize) return QImage(); } +QSharedPointer<QQuickItemGrabResult> QuickItemNodeInstance::createGrabResult() const +{ + return quickItem()->grabToImage(size().toSize()); +} + void QuickItemNodeInstance::updateAllDirtyNodesRecursive() { updateAllDirtyNodesRecursive(quickItem()); @@ -490,10 +548,11 @@ void QuickItemNodeInstance::updateDirtyNodesRecursive(QQuickItem *parentItem) co void QuickItemNodeInstance::updateAllDirtyNodesRecursive(QQuickItem *parentItem) const { - foreach (QQuickItem *childItem, parentItem->childItems()) - updateAllDirtyNodesRecursive(childItem); + const QList<QQuickItem *> children = parentItem->childItems(); + for (QQuickItem *childItem : children) + updateAllDirtyNodesRecursive(childItem); - DesignerSupport::updateDirtyNode(parentItem); + updateDirtyNode(parentItem); } static inline bool isRectangleSane(const QRectF &rect) @@ -507,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)) @@ -515,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/instances/quickitemnodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.h index f42e45a8ff..2c68fe7974 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/quickitemnodeinstance.h @@ -45,6 +45,7 @@ public: static Pointer create(QObject *objectToBeWrapped); static void createEffectItem(bool createEffectItem); + static void enableUnifiedRenderPath(bool createEffectItem); void initialize(const ObjectNodeInstance::Pointer &objectNodeInstance, InstanceContainer::NodeFlags flags) override; @@ -70,6 +71,8 @@ public: QImage renderImage() const override; QImage renderPreviewImage(const QSize &previewImageSize) const override; + QSharedPointer<QQuickItemGrabResult> createGrabResult() const override; + void updateAllDirtyNodesRecursive() override; @@ -98,6 +101,9 @@ public: QList<QQuickItem*> allItemsRecursive() const override; QStringList allStates() const override; + static void updateDirtyNode(QQuickItem *item); + static bool unifiedRenderPath(); + protected: explicit QuickItemNodeInstance(QQuickItem*); QQuickItem *quickItem() const; @@ -118,6 +124,7 @@ protected: double x() const; double y() const; + bool checkIfRefFromEffect(qint32 id); private: //variables QPointer<QQuickItem> m_contentItem; @@ -131,6 +138,7 @@ private: //variables double m_width; double m_height; static bool s_createEffectItem; + static bool s_unifiedRenderPath; }; } // namespace Internal diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp index 69c854f3e9..511a89b62b 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.cpp @@ -120,6 +120,11 @@ QImage ServerNodeInstance::renderPreviewImage(const QSize &previewImageSize) con return m_nodeInstance->renderPreviewImage(previewImageSize); } +QSharedPointer<QQuickItemGrabResult> ServerNodeInstance::createGrabResult() const +{ + return m_nodeInstance->createGrabResult(); +} + bool ServerNodeInstance::isRootNodeInstance() const { return isValid() && m_nodeInstance->isRootNodeInstance(); diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h index 975d0e8c37..9150b2bd4e 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/servernodeinstance.h @@ -40,6 +40,7 @@ class QGraphicsItem; class QGraphicsTransform; #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) class QQuickItem; +class QQuickItemGrabResult; #endif QT_END_NAMESPACE @@ -102,6 +103,8 @@ public: QImage renderImage() const; QImage renderPreviewImage(const QSize &previewImageSize) const; + QSharedPointer<QQuickItemGrabResult> createGrabResult() const; + ServerNodeInstance parent() const; bool hasParent() const; diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri index f772fd8a55..1f4714d4d2 100644 --- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri +++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri @@ -1,7 +1,7 @@ QT += core gui widgets qml quick network QT += core-private qml-private quick-private gui-private -CONFIG += c++11 +CONFIG += c++17 DEFINES -= QT_CREATOR 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/qml/qmlpuppet/qmlpuppet.qrc b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc index d5a50ed566..e094303014 100644 --- a/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc +++ b/share/qtcreator/qml/qmlpuppet/qmlpuppet.qrc @@ -3,6 +3,8 @@ <file>images/template_image.png</file> <file>html/welcome.html</file> <file>images/webkit.png</file> + <file>images/non-visual-component.png</file> + <file>images/non-visual-component@2x.png</file> <file>mockfiles/Window.qml</file> <file>mockfiles/SwipeView.qml</file> <file>mockfiles/GenericBackend.qml</file> 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/UrlChooser.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml index a1627fb874..3c51254337 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/UrlChooser.qml @@ -221,7 +221,7 @@ RowLayout { onClicked: { fileModel.openFileDialog() if (fileModel.path !== "") - urlChooser.backendValue.value = fileModel.path + urlChooser.backendValue.value = fileModel.fileName } } } 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/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml index 808da702d5..928b8c4df7 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/InternalConstants.qml @@ -98,33 +98,37 @@ QtObject { readonly property string idAliasOff: "\u005B" readonly property string idAliasOn: "\u005C" readonly property string listView: "\u005D" - readonly property string mergeCells: "\u005E" - readonly property string minus: "\u005F" - readonly property string plus: "\u0060" - readonly property string redo: "\u0061" - readonly property string splitColumns: "\u0062" - readonly property string splitRows: "\u0063" - readonly property string startNode: "\u0064" - readonly property string testIcon: "\u0065" - readonly property string textAlignBottom: "\u0066" - readonly property string textAlignCenter: "\u0067" - readonly property string textAlignLeft: "\u0068" - readonly property string textAlignMiddle: "\u0069" - readonly property string textAlignRight: "\u006A" - readonly property string textAlignTop: "\u006B" - readonly property string textBulletList: "\u006C" - readonly property string textFullJustification: "\u006D" - readonly property string textNumberedList: "\u006E" - readonly property string tickIcon: "\u006F" - readonly property string triState: "\u0070" - readonly property string undo: "\u0071" - readonly property string upDownIcon: "\u0072" - readonly property string upDownSquare2: "\u0073" - readonly property string wildcard: "\u0074" - readonly property string zoomAll: "\u0075" - readonly property string zoomIn: "\u0076" - readonly property string zoomOut: "\u0077" - readonly property string zoomSelection: "\u0078" + readonly property string lockOff: "\u005E" + readonly property string lockOn: "\u005F" + readonly property string mergeCells: "\u0060" + readonly property string minus: "\u0061" + readonly property string plus: "\u0062" + readonly property string redo: "\u0063" + readonly property string splitColumns: "\u0064" + readonly property string splitRows: "\u0065" + readonly property string startNode: "\u0066" + readonly property string testIcon: "\u0067" + readonly property string textAlignBottom: "\u0068" + readonly property string textAlignCenter: "\u0069" + readonly property string textAlignLeft: "\u006A" + readonly property string textAlignMiddle: "\u006B" + readonly property string textAlignRight: "\u006C" + readonly property string textAlignTop: "\u006D" + readonly property string textBulletList: "\u006E" + readonly property string textFullJustification: "\u006F" + readonly property string textNumberedList: "\u0070" + readonly property string tickIcon: "\u0071" + readonly property string triState: "\u0072" + readonly property string undo: "\u0073" + readonly property string upDownIcon: "\u0074" + readonly property string upDownSquare2: "\u0075" + readonly property string visibilityOff: "\u0076" + readonly property string visibilityOn: "\u0077" + readonly property string wildcard: "\u0078" + readonly property string zoomAll: "\u0079" + readonly property string zoomIn: "\u007A" + readonly property string zoomOut: "\u007B" + readonly property string zoomSelection: "\u007C" readonly property font iconFont: Qt.font({ "family": controlIcons.name, diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf Binary files differindex e548392747..b8addaf835 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/StudioTheme/icons.ttf diff --git a/src/libs/qtcreatorcdbext/CMakeLists.txt b/src/libs/qtcreatorcdbext/CMakeLists.txt index 0a45ef0cc3..f024eaa482 100644 --- a/src/libs/qtcreatorcdbext/CMakeLists.txt +++ b/src/libs/qtcreatorcdbext/CMakeLists.txt @@ -59,8 +59,12 @@ if (_library_enabled) foreach(lib IN LISTS PYTHON_LIBRARIES) if (lib MATCHES ${PythonRegex}) + set(PythonZipFileName "python${CMAKE_MATCH_4}.zip") + set(PythonDll "${CMAKE_MATCH_1}/${CMAKE_MATCH_3}${CMAKE_SHARED_LIBRARY_SUFFIX}") - set(PythonZip "${CMAKE_MATCH_1}/python${CMAKE_MATCH_4}.zip") + set(PythonExe "${CMAKE_MATCH_1}/python${CMAKE_EXECUTABLE_SUFFIX}") + set(PythonZip "${CMAKE_MATCH_1}/${PythonZipFileName}") + break() endif() endforeach() @@ -85,16 +89,27 @@ if (_library_enabled) pyvalue.cpp pyvalue.h ) - install(FILES - "${PythonDll}" - "${PythonZip}" + if (NOT EXISTS "${PythonZip}" AND + NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${PythonZipFileName}") + include(CreatePythonXY) + create_python_xy("${PythonExe}" "${CMAKE_CURRENT_BINARY_DIR}/${PythonZipFileName}") + endif() + + if (NOT EXISTS "${PythonZip}" AND + EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${PythonZipFileName}") + set(PythonZip "${CMAKE_CURRENT_BINARY_DIR}/${PythonZipFileName}") + endif() + + list(APPEND deployPythonFiles "${PythonDll}") + list(APPEND deployPythonFiles "${PythonZip}") + + install(FILES ${deployPythonFiles} DESTINATION lib/qtcreatorcdbext${ArchSuffix}/ COMPONENT qtcreatorcdbext) add_custom_target(copy_python_dll ALL VERBATIM) add_custom_command(TARGET copy_python_dll POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy "${PythonDll}" "${PROJECT_BINARY_DIR}/lib/qtcreatorcdbext${ArchSuffix}/" - COMMAND "${CMAKE_COMMAND}" -E copy "${PythonZip}" "${PROJECT_BINARY_DIR}/lib/qtcreatorcdbext${ArchSuffix}/" + COMMAND "${CMAKE_COMMAND}" -E copy ${deployPythonFiles} "${PROJECT_BINARY_DIR}/lib/qtcreatorcdbext${ArchSuffix}/" VERBATIM ) endif() 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/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index 77b4eda306..9abb0193ec 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -1161,13 +1161,14 @@ void IntegerAspect::setToolTip(const QString &tooltip) Its visual representation is a QComboBox with three items. */ -TriStateAspect::TriStateAspect() +TriStateAspect::TriStateAspect(const QString onString, const QString &offString, + const QString &defaultString) { setDisplayStyle(DisplayStyle::ComboBox); setDefaultValue(2); - addOption(tr("Enable")); - addOption(tr("Disable")); - addOption(tr("Leave at Default")); + addOption(onString); + addOption(offString); + addOption(defaultString); } TriState TriStateAspect::setting() const diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h index 8313bcc83d..dc259e84a6 100644 --- a/src/libs/utils/aspects.h +++ b/src/libs/utils/aspects.h @@ -322,7 +322,10 @@ class QTCREATOR_UTILS_EXPORT TriStateAspect : public SelectionAspect { Q_OBJECT public: - TriStateAspect(); + TriStateAspect( + const QString onString = tr("Enable"), + const QString &offString = tr("Disable"), + const QString &defaultString = tr("Leave at Default")); TriState setting() const; void setSetting(TriState setting); diff --git a/src/libs/utils/buildablehelperlibrary.cpp b/src/libs/utils/buildablehelperlibrary.cpp index b1988bf8c3..fdebca957e 100644 --- a/src/libs/utils/buildablehelperlibrary.cpp +++ b/src/libs/utils/buildablehelperlibrary.cpp @@ -179,7 +179,13 @@ QStringList BuildableHelperLibrary::possibleQMakeCommands() // On Unix some distributions renamed qmake with a postfix to avoid clashes // On OS X, Qt 4 binary packages also has renamed qmake. There are also symbolic links that are // named "qmake", but the file dialog always checks against resolved links (native Cocoa issue) - return QStringList(HostOsInfo::withExecutableSuffix("qmake*")); + QStringList commands(HostOsInfo::withExecutableSuffix("qmake*")); + + // Qt 6 CMake built targets, such as Android, are dependent on the host installation + // and use a script wrapper around the host qmake executable + if (HostOsInfo::isWindowsHost()) + commands.append("qmake*.bat"); + return commands; } // Copy helper source files to a target directory, replacing older files. diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp index 5601b763a3..bb096e59bf 100644 --- a/src/libs/utils/filesearch.cpp +++ b/src/libs/utils/filesearch.cpp @@ -29,6 +29,7 @@ #include "fileutils.h" #include "mapreduce.h" #include "qtcassert.h" +#include "stringutils.h" #include <QCoreApplication> #include <QMutex> @@ -474,7 +475,7 @@ QString matchCaseReplacement(const QString &originalText, const QString &replace static QList<QRegularExpression> filtersToRegExps(const QStringList &filters) { return Utils::transform(filters, [](const QString &filter) { - return QRegularExpression(QRegularExpression::wildcardToRegularExpression(filter), + return QRegularExpression(Utils::wildcardToRegularExpression(filter), QRegularExpression::CaseInsensitiveOption); }); } diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp index 888428790b..d8483df552 100644 --- a/src/libs/utils/stringutils.cpp +++ b/src/libs/utils/stringutils.cpp @@ -393,4 +393,68 @@ QString formatElapsedTime(qint64 elapsed) return QCoreApplication::translate("StringUtils", "Elapsed time: %1.").arg(time); } +/* + * Basically QRegularExpression::wildcardToRegularExpression(), but let wildcards match + * path separators as well + */ +QString wildcardToRegularExpression(const QString &original) +{ + const qsizetype wclen = original.size(); + QString rx; + rx.reserve(wclen + wclen / 16); + qsizetype i = 0; + const QChar *wc = original.data(); + + const QLatin1String starEscape(".*"); + const QLatin1String questionMarkEscape("."); + + while (i < wclen) { + const QChar c = wc[i++]; + switch (c.unicode()) { + case '*': + rx += starEscape; + break; + case '?': + rx += questionMarkEscape; + break; + case '\\': + case '$': + case '(': + case ')': + case '+': + case '.': + case '^': + case '{': + case '|': + case '}': + rx += QLatin1Char('\\'); + rx += c; + break; + case '[': + rx += c; + // Support for the [!abc] or [!a-c] syntax + if (i < wclen) { + if (wc[i] == QLatin1Char('!')) { + rx += QLatin1Char('^'); + ++i; + } + + if (i < wclen && wc[i] == QLatin1Char(']')) + rx += wc[i++]; + + while (i < wclen && wc[i] != QLatin1Char(']')) { + if (wc[i] == QLatin1Char('\\')) + rx += QLatin1Char('\\'); + rx += wc[i++]; + } + } + break; + default: + rx += c; + break; + } + } + + return QRegularExpression::anchoredPattern(rx); +} } // namespace Utils diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h index 21a01de359..fbafa7708c 100644 --- a/src/libs/utils/stringutils.h +++ b/src/libs/utils/stringutils.h @@ -104,4 +104,11 @@ T makeUniquelyNumbered(const T &preferred, const Container &reserved) QTCREATOR_UTILS_EXPORT QString formatElapsedTime(qint64 elapsed); +/* This function is only necessary if you need to match the wildcard expression against a + * string that might contain path separators - otherwise + * QRegularExpression::wildcardToRegularExpression() can be used. + * Working around QRegularExpression::wildcardToRegularExpression() taking native separators + * into account and handling them to disallow matching a wildcard characters. + */ +QTCREATOR_UTILS_EXPORT QString wildcardToRegularExpression(const QString &original); } // namespace Utils diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp index d3331d5994..4c33d6b567 100644 --- a/src/plugins/android/androidbuildapkstep.cpp +++ b/src/plugins/android/androidbuildapkstep.cpp @@ -324,7 +324,7 @@ QWidget *AndroidBuildApkWidget::createAdvancedGroup() auto vbox = new QVBoxLayout(group); QtSupport::BaseQtVersion *version = QtSupport::QtKitAspect::qtVersion(m_step->kit()); if (version && version->supportsMultipleQtAbis()) { - auto buildAAB = new QCheckBox(tr("Build .aab (Android App Bundle)"), group); + auto buildAAB = new QCheckBox(tr("Build Android App Bundle (*.aab)"), group); buildAAB->setChecked(m_step->buildAAB()); connect(buildAAB, &QAbstractButton::toggled, m_step, &AndroidBuildApkStep::setBuildAAB); vbox->addWidget(buildAAB); diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp index 0de5dc0d46..3776b55c33 100644 --- a/src/plugins/android/androiddeployqtstep.cpp +++ b/src/plugins/android/androiddeployqtstep.cpp @@ -51,6 +51,7 @@ #include <qtsupport/qtkitinformation.h> #include <utils/algorithm.h> +#include <utils/layoutbuilder.h> #include <utils/qtcassert.h> #include <utils/qtcprocess.h> #include <utils/synchronousprocess.h> @@ -86,8 +87,18 @@ AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Utils::Id id) : BuildStep(parent, id) { setImmutable(true); + + m_uninstallPreviousPackage = addAspect<BoolAspect>(); + m_uninstallPreviousPackage->setSettingsKey(UninstallPreviousPackageKey); + m_uninstallPreviousPackage->setLabel(tr("Uninstall the existing app first")); + m_uninstallPreviousPackage->setValue(false); + const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit()); - m_uninstallPreviousPackage = qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0); + const bool forced = qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0); + if (forced) { + m_uninstallPreviousPackage->setValue(true); + m_uninstallPreviousPackage->setEnabled(false); + } connect(this, &AndroidDeployQtStep::askForUninstall, this, &AndroidDeployQtStep::slotAskForUninstall, @@ -167,7 +178,7 @@ bool AndroidDeployQtStep::init() emit addOutput(tr("Deploying to %1").arg(m_serialNumber), OutputFormat::Stdout); - m_uninstallPreviousPackageRun = m_uninstallPreviousPackage; + m_uninstallPreviousPackageRun = m_uninstallPreviousPackage->value(); if (m_uninstallPreviousPackageRun) m_manifestName = AndroidManager::manifestPath(target()); @@ -480,14 +491,6 @@ QWidget *AndroidDeployQtStep::createConfigWidget() setDisplayName(QString("<b>%1</b>").arg(displayName())); setSummaryText(displayName()); - auto uninstallPreviousCheckBox = new QCheckBox(widget); - uninstallPreviousCheckBox->setText(tr("Uninstall the existing app first")); - uninstallPreviousCheckBox->setChecked(uninstallPreviousPackage() > Keep); - uninstallPreviousCheckBox->setEnabled(uninstallPreviousPackage() != ForceUninstall); - - connect(uninstallPreviousCheckBox, &QAbstractButton::toggled, - this, &AndroidDeployQtStep::setUninstallPreviousPackage); - auto resetDefaultDevices = new QPushButton(widget); resetDefaultDevices->setText(tr("Reset Default Deployment Devices")); @@ -508,10 +511,10 @@ QWidget *AndroidDeployQtStep::createConfigWidget() AndroidManager::installQASIPackage(target(), packagePath); }); - auto layout = new QVBoxLayout(widget); - layout->addWidget(uninstallPreviousCheckBox); - layout->addWidget(resetDefaultDevices); - layout->addWidget(installCustomApkButton); + LayoutBuilder builder(widget); + builder.addRow(m_uninstallPreviousPackage); + builder.addRow(resetDefaultDevices); + builder.addRow(installCustomApkButton); return widget; } @@ -569,32 +572,6 @@ AndroidDeployQtStep::DeployErrorCode AndroidDeployQtStep::parseDeployErrors(QStr return errorCode; } -bool AndroidDeployQtStep::fromMap(const QVariantMap &map) -{ - m_uninstallPreviousPackage = map.value(UninstallPreviousPackageKey, m_uninstallPreviousPackage).toBool(); - return ProjectExplorer::BuildStep::fromMap(map); -} - -QVariantMap AndroidDeployQtStep::toMap() const -{ - QVariantMap map = ProjectExplorer::BuildStep::toMap(); - map.insert(UninstallPreviousPackageKey, m_uninstallPreviousPackage); - return map; -} - -void AndroidDeployQtStep::setUninstallPreviousPackage(bool uninstall) -{ - m_uninstallPreviousPackage = uninstall; -} - -AndroidDeployQtStep::UninstallType AndroidDeployQtStep::uninstallPreviousPackage() -{ - const QtSupport::BaseQtVersion * const qt = QtSupport::QtKitAspect::qtVersion(kit()); - if (qt && qt->qtVersion() < QtSupport::QtVersionNumber(5, 4, 0)) - return ForceUninstall; - return m_uninstallPreviousPackage ? Uninstall : Keep; -} - // AndroidDeployQtStepFactory AndroidDeployQtStepFactory::AndroidDeployQtStepFactory() diff --git a/src/plugins/android/androiddeployqtstep.h b/src/plugins/android/androiddeployqtstep.h index 450213de5a..3090bbb2e8 100644 --- a/src/plugins/android/androiddeployqtstep.h +++ b/src/plugins/android/androiddeployqtstep.h @@ -59,20 +59,8 @@ class AndroidDeployQtStep : public ProjectExplorer::BuildStep }; public: - enum UninstallType { - Keep, - Uninstall, - ForceUninstall - }; - AndroidDeployQtStep(ProjectExplorer::BuildStepList *bc, Utils::Id id); - bool fromMap(const QVariantMap &map) override; - QVariantMap toMap() const override; - - UninstallType uninstallPreviousPackage(); - void setUninstallPreviousPackage(bool uninstall); - signals: void askForUninstall(DeployErrorCode errorCode); @@ -105,7 +93,7 @@ private: QMap<QString, QString> m_filesToPull; QStringList m_androidABIs; - bool m_uninstallPreviousPackage = false; + Utils::BoolAspect *m_uninstallPreviousPackage = nullptr; bool m_uninstallPreviousPackageRun = false; bool m_useAndroiddeployqt = false; bool m_askForUninstall = false; diff --git a/src/plugins/android/androidqtversion.cpp b/src/plugins/android/androidqtversion.cpp index 32a58ffc66..2f306671eb 100644 --- a/src/plugins/android/androidqtversion.cpp +++ b/src/plugins/android/androidqtversion.cpp @@ -86,7 +86,8 @@ QString AndroidQtVersion::invalidReason() const bool AndroidQtVersion::supportsMultipleQtAbis() const { - return qtVersion() >= QtSupport::QtVersionNumber{5, 14}; + return qtVersion() >= QtSupport::QtVersionNumber{5, 14} + && qtVersion() < QtSupport::QtVersionNumber{6, 0}; } Abis AndroidQtVersion::detectQtAbis() const diff --git a/src/plugins/autotest/boost/boosttestsettingspage.ui b/src/plugins/autotest/boost/boosttestsettingspage.ui index 1596cd7d41..22ddef51f6 100644 --- a/src/plugins/autotest/boost/boosttestsettingspage.ui +++ b/src/plugins/autotest/boost/boosttestsettingspage.ui @@ -10,9 +10,6 @@ <height>284</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <layout class="QHBoxLayout" name="horizontalLayout_4"> diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp index 10709e38a6..785abe4f75 100644 --- a/src/plugins/autotest/testconfiguration.cpp +++ b/src/plugins/autotest/testconfiguration.cpp @@ -185,8 +185,11 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode) qCDebug(LOG) << " LocalExecutable" << localExecutable; qCDebug(LOG) << " DeployedExecutable" << deployedExecutable; - qCDebug(LOG) << "Iterating run configurations"; - for (RunConfiguration *runConfig : target->runConfigurations()) { + qCDebug(LOG) << "Iterating run configurations - prefer active over others"; + QList<RunConfiguration *> runConfigurations = target->runConfigurations(); + runConfigurations.removeOne(target->activeRunConfiguration()); + runConfigurations.prepend(target->activeRunConfiguration()); + for (RunConfiguration *runConfig : qAsConst(runConfigurations)) { qCDebug(LOG) << "RunConfiguration" << runConfig->id(); if (!isLocal(target)) { // TODO add device support qCDebug(LOG) << " Skipped as not being local"; diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp index b1ce0cd6eb..bb45147eb8 100644 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp +++ b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp @@ -58,10 +58,11 @@ #include <QThread> using namespace ClangBackEnd; -using namespace ClangCodeModel; -using namespace ClangCodeModel::Internal; using namespace ProjectExplorer; +namespace ClangCodeModel { +namespace Internal { + static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg); static int timeOutFromEnvironmentVariable() @@ -78,7 +79,7 @@ static int timeOutFromEnvironmentVariable() return intervalAsInt; } -static int timeOutInMs() +int timeOutInMs() { static int timeOut = timeOutFromEnvironmentVariable(); return timeOut; @@ -747,9 +748,6 @@ bool BatchFileParser::parseLine(const QString &line) } // anonymous namespace -namespace ClangCodeModel { -namespace Internal { - static QString applySubstitutions(const QString &filePath, const QString &text) { const QString dirPath = QFileInfo(filePath).absolutePath(); diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h index 40f0bceee3..8608229f98 100644 --- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h +++ b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h @@ -30,6 +30,8 @@ namespace ClangCodeModel { namespace Internal { +int timeOutInMs(); + bool runClangBatchFile(const QString &filePath); } // namespace Internal diff --git a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp index 0a38744a38..e32e22d8e1 100644 --- a/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp +++ b/src/plugins/clangcodemodel/test/clangcodecompletion_test.cpp @@ -26,6 +26,7 @@ #include "clangcodecompletion_test.h" #include "clangautomationutils.h" +#include "clangbatchfileprocessor.h" #include "../clangcompletionassistinterface.h" #include "../clangmodelmanagersupport.h" @@ -344,7 +345,7 @@ public: if (!textToInsert.isEmpty()) openEditor.editor()->insert(textToInsert); - proposal = completionResults(openEditor.editor(), includePaths, 15000); + proposal = completionResults(openEditor.editor(), includePaths, timeOutInMs()); } TextEditor::ProposalModelPtr proposal; @@ -657,7 +658,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCode() OpenEditorAtCursorPosition openEditor(testDocument); QVERIFY(openEditor.succeeded()); - TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "projectConfiguration1")); } @@ -670,7 +672,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje QVERIFY(openEditor.succeeded()); // Check completion without project - TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openEditor.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "noProjectConfigurationDetected")); { @@ -681,7 +684,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje QVERIFY(projectLoader.load()); openEditor.waitUntilProjectPartChanged(QLatin1String("myproject.project")); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(hasItem(proposal, "projectConfiguration1")); QVERIFY(!hasItem(proposal, "projectConfiguration2")); @@ -689,7 +692,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje // Check completion with project configuration 2 QVERIFY(projectLoader.updateProject({{"PROJECT_CONFIGURATION_2"}})); openEditor.waitUntilBackendIsNotified(); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(!hasItem(proposal, "projectConfiguration1")); QVERIFY(hasItem(proposal, "projectConfiguration2")); @@ -697,7 +700,7 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeAfterChangingProje // Check again completion without project openEditor.waitUntilProjectPartChanged(QLatin1String("")); - proposal = completionResults(openEditor.editor()); + proposal = completionResults(openEditor.editor(), {}, timeOutInMs()); QVERIFY(hasItem(proposal, "noProjectConfigurationDetected")); } @@ -723,7 +726,8 @@ void ClangCodeCompletionTest::testCompleteProjectDependingCodeInGeneratedUiFile( QVERIFY(openSource.succeeded()); // ...and check comletions - TextEditor::ProposalModelPtr proposal = completionResults(openSource.editor()); + TextEditor::ProposalModelPtr proposal = completionResults(openSource.editor(), {}, + timeOutInMs()); QVERIFY(hasItem(proposal, "menuBar")); QVERIFY(hasItem(proposal, "statusBar")); QVERIFY(hasItem(proposal, "centralWidget")); diff --git a/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui b/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui index b10fd91bee..404d207d5a 100644 --- a/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui +++ b/src/plugins/clangpchmanager/clangindexingprojectsettingswidget.ui @@ -10,9 +10,6 @@ <height>300</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <layout class="QHBoxLayout" name="horizontalLayout"> diff --git a/src/plugins/clangtools/clangtoolsunittests.cpp b/src/plugins/clangtools/clangtoolsunittests.cpp index 1e76c577e1..512e6e7135 100644 --- a/src/plugins/clangtools/clangtoolsunittests.cpp +++ b/src/plugins/clangtools/clangtoolsunittests.cpp @@ -125,7 +125,7 @@ void ClangToolsUnitTests::testProject() ClangToolsSettings::instance()->runSettings(), diagnosticConfig); QSignalSpy waitForFinishedTool(tool, &ClangTool::finished); - QVERIFY(waitForFinishedTool.wait(90000)); + QVERIFY(waitForFinishedTool.wait(m_timeout)); // Check for errors const QString errorText = waitForFinishedTool.takeFirst().first().toString(); @@ -186,5 +186,11 @@ void ClangToolsUnitTests::addTestRow(const QByteArray &relativeFilePath, << absoluteFilePath << expectedDiagCount << diagnosticConfig; } +int ClangToolsUnitTests::getTimeout() +{ + const int t = qEnvironmentVariableIntValue("QTC_CLANGTOOLS_TEST_TIMEOUT"); + return t > 0 ? t : 480000; +} + } // namespace Internal } // namespace ClangTools diff --git a/src/plugins/clangtools/clangtoolsunittests.h b/src/plugins/clangtools/clangtoolsunittests.h index 87dd5a114e..20da247894 100644 --- a/src/plugins/clangtools/clangtoolsunittests.h +++ b/src/plugins/clangtools/clangtoolsunittests.h @@ -56,8 +56,11 @@ private: const CppTools::ClangDiagnosticConfig &diagnosticConfig); private: + static int getTimeout(); + CppTools::Tests::TemporaryCopiedDir *m_tmpDir = nullptr; ProjectExplorer::Kit *m_kit = nullptr; + int m_timeout = getTimeout(); }; } // namespace Internal diff --git a/src/plugins/clangtools/runsettingswidget.ui b/src/plugins/clangtools/runsettingswidget.ui index 370932c9a8..8dc283c8d0 100644 --- a/src/plugins/clangtools/runsettingswidget.ui +++ b/src/plugins/clangtools/runsettingswidget.ui @@ -10,9 +10,6 @@ <height>125</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <property name="leftMargin"> <number>0</number> diff --git a/src/plugins/cppcheck/cppchecktool.cpp b/src/plugins/cppcheck/cppchecktool.cpp index 2c17247d0a..91660020bb 100644 --- a/src/plugins/cppcheck/cppchecktool.cpp +++ b/src/plugins/cppcheck/cppchecktool.cpp @@ -38,6 +38,7 @@ #include <utils/algorithm.h> #include <utils/macroexpander.h> #include <utils/qtcassert.h> +#include <utils/stringutils.h> #include <QThread> @@ -67,7 +68,7 @@ void CppcheckTool::updateOptions(const CppcheckOptions &options) if (trimmedPattern.isEmpty()) continue; - const QRegularExpression re(QRegularExpression::wildcardToRegularExpression(trimmedPattern)); + const QRegularExpression re(Utils::wildcardToRegularExpression(trimmedPattern)); if (re.isValid()) m_filters.push_back(re); } diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index 9adc6a9fc4..a68c2edf72 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -226,6 +226,8 @@ private slots: void test_quickfix_removeUsingNamespace_data(); void test_quickfix_removeUsingNamespace(); + void test_quickfix_removeUsingNamespace_simple_data(); + void test_quickfix_removeUsingNamespace_simple(); void test_quickfix_removeUsingNamespace_differentSymbols(); void test_quickfix_InsertVirtualMethods_data(); diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 05b8e1b2d9..fc026a6c25 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -606,6 +606,32 @@ void CppEditorPlugin::test_quickfix_data() "" ); + // Checks: complete switch statement where enum is goes via a template type parameter + QTest::newRow("CompleteSwitchCaseStatement_QTCREATORBUG-24752") + << CppQuickFixFactoryPtr(new CompleteSwitchCaseStatement) << _( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " @switch (S<E>::theType()) {\n" + " }\n" + "}\n" + ) << _( + "enum E {EA, EB};\n" + "template<typename T> struct S {\n" + " static T theType() { return T(); }\n" + "};\n" + "int main() {\n" + " switch (S<E>::theType()) {\n" + " case EA:\n" + " break;\n" + " case EB:\n" + " break;\n" + " }\n" + "}\n" + ); + // Checks: No special treatment for reference to non const. // Check: Quick fix is not triggered on a member function. @@ -7083,6 +7109,49 @@ void CppEditorPlugin::test_quickfix_removeUsingNamespace() QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), operation); } +void CppEditorPlugin::test_quickfix_removeUsingNamespace_simple_data() +{ + QTest::addColumn<QByteArray>("header"); + QTest::addColumn<QByteArray>("expected"); + + const QByteArray common = R"--( +namespace N{ + template<typename T> + struct vector{ + using iterator = T*; + }; + using int_vector = vector<int>; +} +)--"; + const QByteArray header = common + R"--( +using namespace N@; +int_vector ints; +int_vector::iterator intIter; +using vec = vector<int>; +vec::iterator it; +)--"; + const QByteArray expected = common + R"--( +N::int_vector ints; +N::int_vector::iterator intIter; +using vec = N::vector<int>; +vec::iterator it; +)--"; + + QTest::newRow("nested typedefs with Namespace") << header << expected; +} + +void CppEditorPlugin::test_quickfix_removeUsingNamespace_simple() +{ + QFETCH(QByteArray, header); + QFETCH(QByteArray, expected); + + QList<QuickFixTestDocument::Ptr> testDocuments; + testDocuments << QuickFixTestDocument::create("header.h", header, expected); + + RemoveUsingNamespace factory; + QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths()); +} + void CppEditorPlugin::test_quickfix_removeUsingNamespace_differentSymbols() { QByteArray header = "namespace test{\n" diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index f5a89d4bb4..47a0575994 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -2752,6 +2752,7 @@ Enum *conditionEnum(const CppQuickFixInterface &interface, SwitchStatementAST *s Block *block = statement->symbol; Scope *scope = interface.semanticInfo().doc->scopeAt(block->line(), block->column()); TypeOfExpression typeOfExpression; + typeOfExpression.setExpandTemplates(true); typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot()); const QList<LookupItem> results = typeOfExpression(statement->condition, interface.semanticInfo().doc, @@ -7795,6 +7796,33 @@ private: }; /** + * @brief getBaseName returns the base name of a qualified name or nullptr. + * E.g.: foo::bar => foo; bar => bar + * @param name The Name, maybe qualified + * @return The base name of the qualified name or nullptr + */ +const Identifier *getBaseName(const Name *name) +{ + class GetBaseName : public NameVisitor + { + void visit(const Identifier *name) override { baseName = name; } + void visit(const QualifiedNameId *name) override + { + if (name->base()) + accept(name->base()); + else + accept(name->name()); + } + + public: + const Identifier *baseName = nullptr; + }; + GetBaseName getter; + getter.accept(name); + return getter.baseName; +} + +/** * @brief countNames counts the parts of the Name. * E.g. if the name is std::vector, the function returns 2, if the name is variant, returns 1 * @param name The name that should be counted @@ -7993,11 +8021,24 @@ private: { if (m_start) { Scope *scope = m_file->scopeAt(ast->firstToken()); - const QList<LookupItem> lookups = m_context.lookup(ast->name->name, scope); + const Name *wantToLookup = ast->name->name; + // first check if the base name is a typedef. Consider the following example: + // using namespace std; + // using vec = std::vector<int>; + // vec::iterator it; // we have to lookup 'vec' and not iterator (would result in + // std::vector<int>::iterator => std::vec::iterator, which is wrong) + const Name *baseName = getBaseName(wantToLookup); + QList<LookupItem> typedefCandidates = m_context.lookup(baseName, scope); + if (!typedefCandidates.isEmpty()) { + if (typedefCandidates.front().declaration()->isTypedef()) + wantToLookup = baseName; + } + + const QList<LookupItem> lookups = m_context.lookup(wantToLookup, scope); if (!lookups.empty()) { QList<const Name *> fullName = m_context.fullyQualifiedName( lookups.first().declaration()); - const int currentNameCount = countNames(ast->name->name); + const int currentNameCount = countNames(wantToLookup); const bool needNamespace = needMissingNamespaces(std::move(fullName), currentNameCount); if (needNamespace) diff --git a/src/plugins/cpptools/cppmodelmanager_test.cpp b/src/plugins/cpptools/cppmodelmanager_test.cpp index a23c73f18a..78de5f1583 100644 --- a/src/plugins/cpptools/cppmodelmanager_test.cpp +++ b/src/plugins/cpptools/cppmodelmanager_test.cpp @@ -1117,12 +1117,12 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() const MyTestDataDir testDir2(_("testdata_project2")); QFile foobar2000Header(testDir2.file("foobar2000.h")); - QVERIFY(foobar2000Header.open(QFile::ReadOnly)); + QVERIFY(foobar2000Header.open(QFile::ReadOnly | QFile::Text)); const auto foobar2000HeaderContents = foobar2000Header.readAll(); foobar2000Header.close(); QFile renamedHeader(renamedHeaderWithNormalGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); auto renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, foobar2000HeaderContents); @@ -1133,12 +1133,12 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() Core::HandleIncludeGuards::Yes)); QFile foobar4000Header(testDir2.file("foobar4000.h")); - QVERIFY(foobar4000Header.open(QFile::ReadOnly)); + QVERIFY(foobar4000Header.open(QFile::ReadOnly | QFile::Text)); const auto foobar4000HeaderContents = foobar4000Header.readAll(); foobar4000Header.close(); renamedHeader.setFileName(renamedHeaderWithUnderscoredGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, foobar4000HeaderContents); @@ -1146,7 +1146,7 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() // test the renaming of a header with a malformed guard to verify we do not make // accidental refactors renamedHeader.setFileName(headerWithMalformedGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); auto originalMalformedGuardContents = renamedHeader.readAll(); renamedHeader.close(); @@ -1154,7 +1154,7 @@ void CppToolsPlugin::test_modelmanager_renameIncludesInEditor() Core::HandleIncludeGuards::Yes)); renamedHeader.setFileName(renamedHeaderWithMalformedGuard); - QVERIFY(renamedHeader.open(QFile::ReadOnly)); + QVERIFY(renamedHeader.open(QFile::ReadOnly | QFile::Text)); renamedHeaderContents = renamedHeader.readAll(); renamedHeader.close(); QCOMPARE(renamedHeaderContents, originalMalformedGuardContents); diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index e538aa6505..3e76d5a782 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -4166,7 +4166,7 @@ void GdbEngine::setupInferior() } if (!symbolFile.isEmpty()) { - runCommand({"-file-exec-and-symbols \"" + symbolFile + '"', + runCommand({"-file-symbol-file \"" + symbolFile + '"', CB(handleFileExecAndSymbols)}); } diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 593f16fc4d..09053e77ba 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -2951,14 +2951,12 @@ bool GitClient::addAndCommit(const QString &repositoryDirectory, const SynchronousProcessResponse resp = vcsSynchronousExec(repositoryDirectory, arguments, VcsCommand::NoFullySync); - const QString stdErr = resp.stdErr(); if (resp.result == SynchronousProcessResponse::Finished) { VcsOutputWindow::appendMessage(msgCommitted(amendSHA1, commitCount)); - VcsOutputWindow::appendError(stdErr); GitPlugin::updateCurrentBranch(); return true; } else { - VcsOutputWindow::appendError(tr("Cannot commit %n files: %1\n", nullptr, commitCount).arg(stdErr)); + VcsOutputWindow::appendError(tr("Cannot commit %n files\n", nullptr, commitCount)); return false; } } diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp index fd39b03d37..c238ba454a 100644 --- a/src/plugins/languageclient/client.cpp +++ b/src/plugins/languageclient/client.cpp @@ -325,10 +325,9 @@ void Client::sendContent(const IContent &content) QString error; if (!QTC_GUARD(content.isValid(&error))) Core::MessageManager::write(error); - LanguageClientManager::logBaseMessage(LspLogMessage::ClientMessage, - name(), - content.toBaseMessage()); - m_clientInterface->sendMessage(content.toBaseMessage()); + const BaseMessage message = content.toBaseMessage(); + LanguageClientManager::logBaseMessage(LspLogMessage::ClientMessage, name(), message); + m_clientInterface->sendMessage(message); } void Client::sendContent(const DocumentUri &uri, const IContent &content) diff --git a/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui b/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui index 6efac5a04f..bedfa074fe 100644 --- a/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui +++ b/src/plugins/mesonprojectmanager/project/buildoptions/mesonbuildsettingswidget.ui @@ -10,9 +10,6 @@ <height>300</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QGridLayout" name="gridLayout"> <property name="leftMargin"> <number>0</number> diff --git a/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui b/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui index 6323bf302e..78a3c14d77 100644 --- a/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui +++ b/src/plugins/mesonprojectmanager/settings/general/generalsettingswidget.ui @@ -10,9 +10,6 @@ <height>349</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QWidget" name="widget" native="true"> diff --git a/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui b/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui index 5e1b2a5953..bedf7296c2 100644 --- a/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui +++ b/src/plugins/mesonprojectmanager/settings/tools/toolitemsettings.ui @@ -10,9 +10,6 @@ <height>70</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QFormLayout" name="formLayout"> <item row="0" column="0"> <widget class="QLabel" name="_nameLbl"> diff --git a/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui b/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui index ed6409ab13..137bfced8f 100644 --- a/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui +++ b/src/plugins/mesonprojectmanager/settings/tools/toolssettingswidget.ui @@ -10,9 +10,6 @@ <height>349</height> </rect> </property> - <property name="windowTitle"> - <string>Form</string> - </property> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <layout class="QVBoxLayout" name="verticalLayout"> diff --git a/src/plugins/nim/project/nimbletaskstep.cpp b/src/plugins/nim/project/nimbletaskstep.cpp index 1cb0505089..f41d53da90 100644 --- a/src/plugins/nim/project/nimbletaskstep.cpp +++ b/src/plugins/nim/project/nimbletaskstep.cpp @@ -124,9 +124,6 @@ QWidget *NimbleTaskStep::createConfigWidget() connect(buildSystem, &NimbleBuildSystem::tasksChanged, this, &NimbleTaskStep::updateTaskList); - connect(m_taskName, &StringAspect::changed, this, &BuildStep::recreateSummary); - connect(m_taskArgs, &StringAspect::changed, this, &BuildStep::recreateSummary); - setSummaryUpdater([this] { return QString("<b>%1:</b> nimble %2 %3") .arg(displayName(), m_taskName->value(), m_taskArgs->value()); diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp index 16ae265bca..9b257c00f0 100644 --- a/src/plugins/projectexplorer/buildmanager.cpp +++ b/src/plugins/projectexplorer/buildmanager.cpp @@ -477,6 +477,7 @@ void BuildManager::finish() { const QString elapsedTime = Utils::formatElapsedTime(d->m_elapsed.elapsed()); m_instance->addToOutputWindow(elapsedTime, BuildStep::OutputFormat::NormalMessage); + d->m_outputWindow->flush(); QApplication::alert(ICore::dialogParent(), 3000); } @@ -696,7 +697,7 @@ void BuildManager::nextStep() } static const auto finishedHandler = [](bool success) { - d->m_outputWindow->outputFormatter()->flush(); + d->m_outputWindow->flush(); d->m_lastStepSucceeded = success; disconnect(d->m_currentBuildStep, nullptr, instance(), nullptr); BuildManager::nextBuildQueue(); diff --git a/src/plugins/projectexplorer/buildpropertiessettings.h b/src/plugins/projectexplorer/buildpropertiessettings.h index 17432b45e0..63c289b5fb 100644 --- a/src/plugins/projectexplorer/buildpropertiessettings.h +++ b/src/plugins/projectexplorer/buildpropertiessettings.h @@ -35,6 +35,7 @@ class PROJECTEXPLORER_EXPORT BuildPropertiesSettings { public: QString buildDirectoryTemplate; + QString buildDirectoryTemplateOld; // TODO: Remove in ~4.16 Utils::TriState separateDebugInfo; Utils::TriState qmlDebugging; Utils::TriState qtQuickCompiler; diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp index d5aaf6ea78..6e20887d70 100644 --- a/src/plugins/projectexplorer/buildstep.cpp +++ b/src/plugins/projectexplorer/buildstep.cpp @@ -157,6 +157,26 @@ void BuildStep::cancel() doCancel(); } +QWidget *BuildStep::doCreateConfigWidget() +{ + QWidget *widget = createConfigWidget(); + + const auto recreateSummary = [this] { + if (m_summaryUpdater) + setSummaryText(m_summaryUpdater()); + }; + + for (BaseAspect *aspect : qAsConst(m_aspects)) + connect(aspect, &BaseAspect::changed, widget, recreateSummary); + + connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, + widget, recreateSummary); + + recreateSummary(); + + return widget; +} + QWidget *BuildStep::createConfigWidget() { auto widget = new QWidget; @@ -165,12 +185,8 @@ QWidget *BuildStep::createConfigWidget() for (BaseAspect *aspect : qAsConst(m_aspects)) { if (aspect->isVisible()) aspect->addToLayout(builder.finishRow()); - connect(aspect, &BaseAspect::changed, this, &BuildStep::recreateSummary); } - connect(buildConfiguration(), &BuildConfiguration::buildDirectoryChanged, - this, &BuildStep::recreateSummary); - if (m_addMacroExpander) VariableChooser::addSupportForChildWidgets(widget, macroExpander()); @@ -500,13 +516,6 @@ void BuildStep::setSummaryText(const QString &summaryText) void BuildStep::setSummaryUpdater(const std::function<QString()> &summaryUpdater) { m_summaryUpdater = summaryUpdater; - recreateSummary(); -} - -void BuildStep::recreateSummary() -{ - if (m_summaryUpdater) - setSummaryText(m_summaryUpdater()); } } // ProjectExplorer diff --git a/src/plugins/projectexplorer/buildstep.h b/src/plugins/projectexplorer/buildstep.h index 6fda3e7b03..78d5331034 100644 --- a/src/plugins/projectexplorer/buildstep.h +++ b/src/plugins/projectexplorer/buildstep.h @@ -70,7 +70,6 @@ public: virtual bool init() = 0; void run(); void cancel(); - virtual QWidget *createConfigWidget(); bool fromMap(const QVariantMap &map) override; QVariantMap toMap() const override; @@ -120,7 +119,7 @@ public: QString summaryText() const; void setSummaryText(const QString &summaryText); - void recreateSummary(); + QWidget *doCreateConfigWidget(); signals: void updateSummary(); @@ -141,6 +140,8 @@ signals: void finished(bool result); protected: + virtual QWidget *createConfigWidget(); + void runInThread(const std::function<bool()> &syncImpl); std::function<bool()> cancelChecker() const; diff --git a/src/plugins/projectexplorer/buildstepspage.cpp b/src/plugins/projectexplorer/buildstepspage.cpp index cc642d0d93..f23434d6ac 100644 --- a/src/plugins/projectexplorer/buildstepspage.cpp +++ b/src/plugins/projectexplorer/buildstepspage.cpp @@ -170,7 +170,7 @@ void ToolWidget::setDownVisible(bool b) BuildStepsWidgetData::BuildStepsWidgetData(BuildStep *s) : step(s), widget(nullptr), detailsWidget(nullptr) { - widget = s->createConfigWidget(); + widget = s->doCreateConfigWidget(); Q_ASSERT(widget); detailsWidget = new DetailsWidget; diff --git a/src/plugins/projectexplorer/makestep.cpp b/src/plugins/projectexplorer/makestep.cpp index 86c1fe74a8..c411ac34c9 100644 --- a/src/plugins/projectexplorer/makestep.cpp +++ b/src/plugins/projectexplorer/makestep.cpp @@ -416,8 +416,6 @@ QWidget *MakeStep::createConfigWidget() m_nonOverrideWarning->setVisible(makeflagsJobCountMismatch() && !jobCountOverridesMakeflags()); disableInSubDirsCheckBox->setChecked(!m_enabledForSubDirs); - - recreateSummary(); }; updateDetails(); diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp index 344f9c1766..b10a492970 100644 --- a/src/plugins/projectexplorer/projectexplorer.cpp +++ b/src/plugins/projectexplorer/projectexplorer.cpp @@ -259,7 +259,8 @@ const char PROJECT_OPEN_LOCATIONS_CONTEXT_MENU[] = "Project.P.OpenLocation.CtxM // Default directories: const char DEFAULT_BUILD_DIRECTORY_TEMPLATE[] = "../%{JS: Util.asciify(\"build-%{Project:Name}-%{Kit:FileSystemName}-%{BuildConfig:Name}\")}"; -const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY[] = "Directories/BuildDirectory.Template"; +const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD[] = "Directories/BuildDirectory.Template"; // TODO: Remove in ~4.16 +const char DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY[] = "Directories/BuildDirectory.TemplateV2"; const char BUILD_BEFORE_DEPLOY_SETTINGS_KEY[] = "ProjectExplorer/Settings/BuildBeforeDeploy"; const char DEPLOY_BEFORE_RUN_SETTINGS_KEY[] = "ProjectExplorer/Settings/DeployBeforeRun"; @@ -1532,8 +1533,14 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er dd->m_projectExplorerSettings.lowBuildPriority = s->value(Constants::LOW_BUILD_PRIORITY_SETTINGS_KEY, false).toBool(); + dd->m_buildPropertiesSettings.buildDirectoryTemplateOld + = s->value(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD).toString(); dd->m_buildPropertiesSettings.buildDirectoryTemplate = s->value(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY).toString(); + if (dd->m_buildPropertiesSettings.buildDirectoryTemplate.isEmpty()) { + dd->m_buildPropertiesSettings.buildDirectoryTemplate + = dd->m_buildPropertiesSettings.buildDirectoryTemplateOld; + } if (dd->m_buildPropertiesSettings.buildDirectoryTemplate.isEmpty()) dd->m_buildPropertiesSettings.buildDirectoryTemplate = Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE; // TODO: Remove in ~4.16 @@ -2110,6 +2117,8 @@ void ProjectExplorerPluginPrivate::savePersistentSettings() s->setValue(Constants::STOP_BEFORE_BUILD_SETTINGS_KEY, int(dd->m_projectExplorerSettings.stopBeforeBuild)); // Store this in the Core directory scope for backward compatibility! + s->setValue(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY_OLD, + dd->m_buildPropertiesSettings.buildDirectoryTemplateOld); s->setValue(Constants::DEFAULT_BUILD_DIRECTORY_TEMPLATE_KEY, dd->m_buildPropertiesSettings.buildDirectoryTemplate); 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/qmakeprojectmanager/qmakebuildconfiguration.cpp b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp index 3d3ec6af01..64a12ab081 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.cpp @@ -75,6 +75,17 @@ using namespace QmakeProjectManager::Internal; namespace QmakeProjectManager { +class RunSystemAspect : public TriStateAspect +{ + Q_OBJECT +public: + RunSystemAspect() : TriStateAspect(tr("Run"), tr("Ignore"), tr("Use global setting")) + { + setSettingsKey("RunSystemFunction"); + setDisplayName(tr("qmake system() behavior when parsing:")); + } +}; + QmakeExtraBuildInfo::QmakeExtraBuildInfo() { const BuildPropertiesSettings &settings = ProjectExplorerPlugin::buildPropertiesSettings(); @@ -198,6 +209,8 @@ QmakeBuildConfiguration::QmakeBuildConfiguration(Target *target, Utils::Id id) emit qmakeBuildConfigurationChanged(); qmakeBuildSystem()->scheduleUpdateAllNowOrLater(); }); + + addAspect<RunSystemAspect>(); } QmakeBuildConfiguration::~QmakeBuildConfiguration() @@ -439,6 +452,17 @@ void QmakeBuildConfiguration::forceQtQuickCompiler(bool enable) aspect<QtQuickCompilerAspect>()->setSetting(enable ? TriState::Enabled : TriState::Disabled); } +bool QmakeBuildConfiguration::runSystemFunction() const +{ + switch (aspect<RunSystemAspect>()->value()) { + case 0: + return true; + case 1: + return false; + } + return QmakeSettings::runSystemFunction(); +} + QStringList QmakeBuildConfiguration::configCommandLineArguments() const { QStringList result; @@ -875,3 +899,5 @@ void QmakeBuildConfiguration::restrictNextBuild(const RunConfiguration *rc) } } // namespace QmakeProjectManager + +#include <qmakebuildconfiguration.moc> diff --git a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h index 71f88256a9..19fbf519a5 100644 --- a/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h +++ b/src/plugins/qmakeprojectmanager/qmakebuildconfiguration.h @@ -106,6 +106,8 @@ public: Utils::TriState useQtQuickCompiler() const; void forceQtQuickCompiler(bool enable); + bool runSystemFunction() const; + signals: /// emitted for setQMakeBuildConfig, not emitted for Qt version changes, even /// if those change the qmakebuildconfig diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp index 3a564fd20b..0babec02e8 100644 --- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp @@ -1839,15 +1839,24 @@ QStringList QmakeProFile::includePaths(QtSupport::ProFileReader *reader, const F } bool tryUnfixified = false; + + // These paths should not be checked for existence, to ensure consistent include path lists + // before and after building. + const QString mocDir = mocDirPath(reader, buildDir); + const QString uiDir = uiDirPath(reader, buildDir); + foreach (const ProFileEvaluator::SourceFile &el, reader->fixifiedValues(QLatin1String("INCLUDEPATH"), projectDir, buildDir.toString(), false)) { const QString sysrootifiedPath = sysrootify(el.fileName, sysroot.toString(), projectDir, buildDir.toString()); - if (IoUtils::isAbsolutePath(sysrootifiedPath) && IoUtils::exists(sysrootifiedPath)) + if (IoUtils::isAbsolutePath(sysrootifiedPath) + && (IoUtils::exists(sysrootifiedPath) || sysrootifiedPath == mocDir + || sysrootifiedPath == uiDir)) { paths << sysrootifiedPath; - else + } else { tryUnfixified = true; + } } // If sysrootifying a fixified path does not yield a valid path, try again with the @@ -1862,10 +1871,6 @@ QStringList QmakeProFile::includePaths(QtSupport::ProFileReader *reader, const F } } - // paths already contains moc dir and ui dir, due to corrrectly parsing uic.prf and moc.prf - // except if those directories don't exist at the time of parsing - // thus we add those directories manually (without checking for existence) - paths << mocDirPath(reader, buildDir) << uiDirPath(reader, buildDir); paths.removeDuplicates(); return paths; } diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp index da0df14b70..da6dcd0eb6 100644 --- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp +++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp @@ -797,6 +797,7 @@ QtSupport::ProFileReader *QmakeBuildSystem::createProFileReader(const QmakeProFi m_qmakeGlobals->environment.insert(env.key(eit), env.expandedValueForKey(env.key(eit))); m_qmakeGlobals->setCommandLineArguments(buildDir(rootProFile()->filePath()).toString(), qmakeArgs); + m_qmakeGlobals->runSystemFunction = bc->runSystemFunction(); QtSupport::ProFileCacheManager::instance()->incRefCount(); diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.cpp b/src/plugins/qmakeprojectmanager/qmakesettings.cpp index dd2fdb4e5d..b0edf3a404 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.cpp +++ b/src/plugins/qmakeprojectmanager/qmakesettings.cpp @@ -38,11 +38,13 @@ namespace Internal { const char BUILD_DIR_WARNING_KEY[] = "QmakeProjectManager/WarnAgainstUnalignedBuildDir"; const char ALWAYS_RUN_QMAKE_KEY[] = "QmakeProjectManager/AlwaysRunQmake"; +const char RUN_SYSTEM_KEY[] = "QmakeProjectManager/RunSystemFunction"; static bool operator==(const QmakeSettingsData &s1, const QmakeSettingsData &s2) { return s1.warnAgainstUnalignedBuildDir == s2.warnAgainstUnalignedBuildDir - && s1.alwaysRunQmake == s2.alwaysRunQmake; + && s1.alwaysRunQmake == s2.alwaysRunQmake + && s1.runSystemFunction == s2.runSystemFunction; } static bool operator!=(const QmakeSettingsData &s1, const QmakeSettingsData &s2) { @@ -59,6 +61,11 @@ bool QmakeSettings::alwaysRunQmake() return instance().m_settings.alwaysRunQmake; } +bool QmakeSettings::runSystemFunction() +{ + return instance().m_settings.runSystemFunction; +} + QmakeSettings &QmakeSettings::instance() { static QmakeSettings theSettings; @@ -85,6 +92,7 @@ void QmakeSettings::loadSettings() m_settings.warnAgainstUnalignedBuildDir = s->value( BUILD_DIR_WARNING_KEY, Utils::HostOsInfo::isWindowsHost()).toBool(); m_settings.alwaysRunQmake = s->value(ALWAYS_RUN_QMAKE_KEY, false).toBool(); + m_settings.runSystemFunction = s->value(RUN_SYSTEM_KEY, false).toBool(); } void QmakeSettings::storeSettings() const @@ -92,6 +100,7 @@ void QmakeSettings::storeSettings() const QSettings * const s = Core::ICore::settings(); s->setValue(BUILD_DIR_WARNING_KEY, warnAgainstUnalignedBuildDir()); s->setValue(ALWAYS_RUN_QMAKE_KEY, alwaysRunQmake()); + s->setValue(RUN_SYSTEM_KEY, runSystemFunction()); } class QmakeSettingsPage::SettingsWidget : public QWidget @@ -110,9 +119,15 @@ public: m_alwaysRunQmakeCheckbox.setToolTip(tr("This option can help to prevent failures on " "incremental builds, but might slow them down unnecessarily in the general case.")); m_alwaysRunQmakeCheckbox.setChecked(QmakeSettings::alwaysRunQmake()); + m_ignoreSystemCheckbox.setText(tr("Ignore qmake's system() function " + "when parsing a project")); + m_ignoreSystemCheckbox.setToolTip(tr("Unchecking this option can help getting more exact " + "parsing results, but can have unwanted side effects.")); + m_ignoreSystemCheckbox.setChecked(!QmakeSettings::runSystemFunction()); const auto layout = new QVBoxLayout(this); layout->addWidget(&m_warnAgainstUnalignedBuildDirCheckbox); layout->addWidget(&m_alwaysRunQmakeCheckbox); + layout->addWidget(&m_ignoreSystemCheckbox); layout->addStretch(1); } @@ -121,12 +136,14 @@ public: QmakeSettingsData settings; settings.warnAgainstUnalignedBuildDir = m_warnAgainstUnalignedBuildDirCheckbox.isChecked(); settings.alwaysRunQmake = m_alwaysRunQmakeCheckbox.isChecked(); + settings.runSystemFunction = !m_ignoreSystemCheckbox.isChecked(); QmakeSettings::setSettingsData(settings); } private: QCheckBox m_warnAgainstUnalignedBuildDirCheckbox; QCheckBox m_alwaysRunQmakeCheckbox; + QCheckBox m_ignoreSystemCheckbox; }; QmakeSettingsPage::QmakeSettingsPage() diff --git a/src/plugins/qmakeprojectmanager/qmakesettings.h b/src/plugins/qmakeprojectmanager/qmakesettings.h index b2b1a75a9d..0225a00bd7 100644 --- a/src/plugins/qmakeprojectmanager/qmakesettings.h +++ b/src/plugins/qmakeprojectmanager/qmakesettings.h @@ -37,6 +37,7 @@ class QmakeSettingsData { public: bool warnAgainstUnalignedBuildDir = false; bool alwaysRunQmake = false; + bool runSystemFunction = false; }; class QmakeSettings : public QObject @@ -46,6 +47,7 @@ public: static QmakeSettings &instance(); static bool warnAgainstUnalignedBuildDir(); static bool alwaysRunQmake(); + static bool runSystemFunction(); static void setSettingsData(const QmakeSettingsData &settings); signals: diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 11be30c80e..ca98f2c424 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}\" @@ -208,6 +208,7 @@ extend_qtc_plugin(QmlDesigner modelnodecontextmenu.cpp modelnodecontextmenu.h modelnodecontextmenu_helper.cpp modelnodecontextmenu_helper.h modelnodeoperations.cpp modelnodeoperations.h + navigation2d.cpp navigation2d.h qmldesignericonprovider.cpp qmldesignericonprovider.h selectioncontext.cpp selectioncontext.h theme.cpp theme.h @@ -316,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) @@ -501,6 +503,7 @@ extend_qtc_plugin(QmlDesigner include/textmodifier.h include/variantproperty.h include/viewmanager.h + include/imagecache.h ) extend_qtc_plugin(QmlDesigner @@ -585,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 @@ -645,6 +663,16 @@ extend_qtc_plugin(QmlDesigner ) extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/previewtooltip + SOURCES + previewimagetooltip.cpp + previewimagetooltip.h + previewimagetooltip.ui + previewtooltipbackend.cpp + previewtooltipbackend.h +) + +extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/richtexteditor SOURCES hyperlinkdialog.cpp hyperlinkdialog.h hyperlinkdialog.ui diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp index 5c46f76f05..c04a666165 100644 --- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp +++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp @@ -28,12 +28,12 @@ #include "assetexportpluginconstants.h" #include "filepathmodel.h" -#include "coreplugin/fileutils.h" -#include "coreplugin/icore.h" -#include "projectexplorer/task.h" -#include "projectexplorer/taskhub.h" -#include "utils/fileutils.h" -#include "utils/outputformatter.h" +#include <coreplugin/fileutils.h> +#include <coreplugin/icore.h> +#include <projectexplorer/task.h> +#include <projectexplorer/taskhub.h> +#include <utils/fileutils.h> +#include <utils/outputformatter.h> #include <QCheckBox> #include <QPushButton> diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp index 4f958d59f6..c7e1ab192c 100644 --- a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.cpp @@ -28,18 +28,34 @@ #include "richtexteditor/richtexteditor.h" +#include "QStringListModel" + namespace QmlDesigner { -AnnotationCommentTab::AnnotationCommentTab(QWidget *parent) : - QWidget(parent), - ui(new Ui::AnnotationCommentTab) +AnnotationCommentTab::AnnotationCommentTab(QWidget *parent) + : QWidget(parent) + , ui(new Ui::AnnotationCommentTab) { ui->setupUi(this); m_editor = new RichTextEditor; ui->formLayout->setWidget(3, QFormLayout::FieldRole, m_editor); - connect(ui->titleEdit, &QLineEdit::textEdited, + ui->titleEdit->setModel(new QStringListModel{QStringList{"Description", + "Display Condition", + "helper_lines" + "highlight" + "project author", + "project confirmed", + "project developer", + "project distributor", + "project modified", + "project type" + "project version", + "Screen Description" + "Section"}}); + + connect(ui->titleEdit, &QComboBox::currentTextChanged, this, &AnnotationCommentTab::commentTitleChanged); } @@ -52,7 +68,7 @@ Comment AnnotationCommentTab::currentComment() const { Comment result; - result.setTitle(ui->titleEdit->text().trimmed()); + result.setTitle(ui->titleEdit->currentText().trimmed()); result.setAuthor(ui->authorEdit->text().trimmed()); result.setText(m_editor->richText().trimmed()); @@ -77,7 +93,7 @@ void AnnotationCommentTab::setComment(const Comment &comment) void AnnotationCommentTab::resetUI() { - ui->titleEdit->setText(m_comment.title()); + ui->titleEdit->setCurrentText(m_comment.title()); ui->authorEdit->setText(m_comment.author()); m_editor->setRichText(m_comment.deescapedText()); diff --git a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui index 4a69703d57..b3a9a85e6c 100644 --- a/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui +++ b/src/plugins/qmldesigner/components/annotationeditor/annotationcommenttab.ui @@ -24,7 +24,11 @@ </widget> </item> <item row="1" column="1"> - <widget class="QLineEdit" name="titleEdit"/> + <widget class="QComboBox" name="titleEdit"> + <property name="editable"> + <bool>true</bool> + </property> + </widget> </item> <item row="3" column="0"> <widget class="QLabel" name="textLabel"> diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore.pri b/src/plugins/qmldesigner/components/componentcore/componentcore.pri index 14337508ec..decf24a5a8 100644 --- a/src/plugins/qmldesigner/components/componentcore/componentcore.pri +++ b/src/plugins/qmldesigner/components/componentcore/componentcore.pri @@ -14,6 +14,7 @@ SOURCES += modelnodecontextmenu_helper.cpp SOURCES += selectioncontext.cpp SOURCES += designeractionmanager.cpp SOURCES += modelnodeoperations.cpp +SOURCES += navigation2d.cpp SOURCES += crumblebar.cpp SOURCES += qmldesignericonprovider.cpp SOURCES += zoomaction.cpp @@ -33,6 +34,7 @@ HEADERS += selectioncontext.h HEADERS += componentcore_constants.h HEADERS += designeractionmanager.h HEADERS += modelnodeoperations.h +HEADERS += navigation2d.h HEADERS += actioninterface.h HEADERS += crumblebar.h HEADERS += qmldesignericonprovider.h diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp index f7487d8a3f..ea34b091cd 100644 --- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp +++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp @@ -347,25 +347,27 @@ public: && !selectionContext().currentSingleSelectedNode().isRootNode() && selectionContext().currentSingleSelectedNode().hasParentProperty()) { - ActionTemplate *selectionAction = new ActionTemplate(QString(), &ModelNodeOperations::select); - selectionAction->setParent(menu()); - parentNode = selectionContext().currentSingleSelectedNode().parentProperty().parentModelNode(); - selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( - captionForModelNode(parentNode))); + if (!ModelNode::isThisOrAncestorLocked(parentNode)) { + ActionTemplate *selectionAction = new ActionTemplate(QString(), &ModelNodeOperations::select); + selectionAction->setParent(menu()); + selectionAction->setText(QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select parent: %1")).arg( + captionForModelNode(parentNode))); - SelectionContext nodeSelectionContext = selectionContext(); - nodeSelectionContext.setTargetNode(parentNode); - selectionAction->setSelectionContext(nodeSelectionContext); + SelectionContext nodeSelectionContext = selectionContext(); + nodeSelectionContext.setTargetNode(parentNode); + selectionAction->setSelectionContext(nodeSelectionContext); - menu()->addAction(selectionAction); + menu()->addAction(selectionAction); + } } - foreach (const ModelNode &node, selectionContext().view()->allModelNodes()) { + for (const ModelNode &node : selectionContext().view()->allModelNodes()) { if (node != selectionContext().currentSingleSelectedNode() && node != parentNode && contains(node, selectionContext().scenePosition()) - && !node.isRootNode()) { + && !node.isRootNode() + && !ModelNode::isThisOrAncestorLocked(node)) { selectionContext().setTargetNode(node); QString what = QString(QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Select: %1")).arg(captionForModelNode(node)); ActionTemplate *selectionAction = new ActionTemplate(what, &ModelNodeOperations::select); @@ -377,6 +379,9 @@ public: menu()->addAction(selectionAction); } } + + if (menu()->isEmpty()) + action()->setEnabled(false); } } }; @@ -574,11 +579,6 @@ bool multiSelection(const SelectionContext &context) return !singleSelection(context) && selectionNotEmpty(context); } -bool singleSelectionAndInBaseState(const SelectionContext &context) -{ - return singleSelection(context) && inBaseState(context); -} - bool multiSelectionAndInBaseState(const SelectionContext &context) { return multiSelection(context) && inBaseState(context); @@ -828,6 +828,11 @@ bool studioComponentsAvailable(const SelectionContext &context) return context.view()->model()->isImportPossible(import, true, true); } +bool studioComponentsAvailableAndSelectionCanBeLayouted(const SelectionContext &context) +{ + return selectionCanBeLayouted(context) && studioComponentsAvailable(context); +} + bool singleSelectedAndUiFile(const SelectionContext &context) { if (!singleSelection(context)) @@ -882,6 +887,12 @@ bool raiseAvailable(const SelectionContext &selectionState) return parentProperty.indexOf(modelNode) < parentProperty.count() - 1; } +bool anchorsMenuEnabled(const SelectionContext &context) +{ + return singleSelectionItemIsNotAnchoredAndSingleSelectionNotRoot(context) + || singleSelectionItemIsAnchored(context); +} + void DesignerActionManager::createDefaultDesignerActions() { using namespace SelectionContextFunctors; @@ -996,11 +1007,10 @@ void DesignerActionManager::createDefaultDesignerActions() &setVisible, &singleSelectedItem)); - addDesignerAction(new ActionGroup( - anchorsCategoryDisplayName, - anchorsCategory, - priorityAnchorsCategory, - &singleSelectionAndInBaseState)); + addDesignerAction(new ActionGroup(anchorsCategoryDisplayName, + anchorsCategory, + priorityAnchorsCategory, + &anchorsMenuEnabled)); addDesignerAction(new ModelNodeAction( anchorsFillCommandId, @@ -1042,7 +1052,7 @@ void DesignerActionManager::createDefaultDesignerActions() addDesignerAction(new ActionGroup(groupCategoryDisplayName, groupCategory, priorityGroupCategory, - &studioComponentsAvailable)); + &studioComponentsAvailableAndSelectionCanBeLayouted)); addDesignerAction(new ActionGroup( flowCategoryDisplayName, diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp new file mode 100644 index 0000000000..94d6b5ad38 --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.cpp @@ -0,0 +1,91 @@ +/**************************************************************************** +** +** 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 "navigation2d.h" + +#include <QGestureEvent> +#include <QWheelEvent> + +namespace QmlDesigner { + +Navigation2dScrollBar::Navigation2dScrollBar(QWidget *parent) + : QScrollBar(parent) +{} + +bool Navigation2dScrollBar::postEvent(QEvent *event) +{ + if (event->type() == QEvent::Wheel) { + wheelEvent(static_cast<QWheelEvent *>(event)); + return true; + } + return false; +} + +void Navigation2dScrollBar::wheelEvent(QWheelEvent *event) +{ + if (!event->angleDelta().isNull()) + QScrollBar::wheelEvent(event); +} + + +Navigation2dFilter::Navigation2dFilter(QWidget *parent, Navigation2dScrollBar *scrollbar) + : QObject(parent) + , m_scrollbar(scrollbar) +{ + if (parent) + parent->grabGesture(Qt::PinchGesture); +} + +bool Navigation2dFilter::eventFilter(QObject *, QEvent *event) +{ + if (event->type() == QEvent::Gesture) + return gestureEvent(static_cast<QGestureEvent *>(event)); + else if (event->type() == QEvent::Wheel && m_scrollbar) + return wheelEvent(static_cast<QWheelEvent *>(event)); + + return QObject::event(event); +} + +bool Navigation2dFilter::gestureEvent(QGestureEvent *event) +{ + if (QPinchGesture *pinch = static_cast<QPinchGesture *>(event->gesture(Qt::PinchGesture))) { + QPinchGesture::ChangeFlags changeFlags = pinch->changeFlags(); + if (changeFlags & QPinchGesture::ScaleFactorChanged) { + emit zoomChanged(-(1.0 - pinch->scaleFactor()), pinch->startCenterPoint()); + event->accept(); + return true; + } + } + return false; +} + +bool Navigation2dFilter::wheelEvent(QWheelEvent *event) +{ + if (m_scrollbar->postEvent(event)) + event->ignore(); + + return false; +} + +} // End namespace QmlDesigner. diff --git a/src/plugins/qmldesigner/components/componentcore/navigation2d.h b/src/plugins/qmldesigner/components/componentcore/navigation2d.h new file mode 100644 index 0000000000..434b59055c --- /dev/null +++ b/src/plugins/qmldesigner/components/componentcore/navigation2d.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** +** 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 <QScrollBar> + +QT_FORWARD_DECLARE_CLASS(QGestureEvent) +QT_FORWARD_DECLARE_CLASS(QWheelEvent) + +namespace QmlDesigner { + +class Navigation2dScrollBar : public QScrollBar +{ + Q_OBJECT + +public: + Navigation2dScrollBar(QWidget *parent = nullptr); + + bool postEvent(QEvent *event); + +protected: + void wheelEvent(QWheelEvent *event) override; +}; + + +class Navigation2dFilter : public QObject +{ + Q_OBJECT + +signals: + void zoomChanged(double scale, const QPointF &pos); + +public: + Navigation2dFilter(QWidget *parent = nullptr, Navigation2dScrollBar *scrollbar = nullptr); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + bool gestureEvent(QGestureEvent *event); + bool wheelEvent(QWheelEvent *event); + Navigation2dScrollBar *m_scrollbar = nullptr; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/componentcore/theme.h b/src/plugins/qmldesigner/components/componentcore/theme.h index f6a1df97cc..513965b18f 100644 --- a/src/plugins/qmldesigner/components/componentcore/theme.h +++ b/src/plugins/qmldesigner/components/componentcore/theme.h @@ -107,6 +107,8 @@ public: idAliasOff, idAliasOn, listView, + lockOff, + lockOn, mergeCells, minus, plus, @@ -129,6 +131,8 @@ public: undo, upDownIcon, upDownSquare2, + visibilityOff, + visibilityOn, wildcard, zoomAll, zoomIn, diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp index 69e0e202f4..e325b02034 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp @@ -75,6 +75,23 @@ ConnectionModel::ConnectionModel(ConnectionView *parent) connect(this, &QStandardItemModel::dataChanged, this, &ConnectionModel::handleDataChanged); } +Qt::ItemFlags ConnectionModel::flags(const QModelIndex &modelIndex) const +{ + if (!modelIndex.isValid()) + return Qt::ItemIsEnabled; + + if (!m_connectionView || !m_connectionView->model()) + return Qt::ItemIsEnabled; + + const int internalId = data(index(modelIndex.row(), TargetModelNodeRow), UserRoles::InternalIdRole).toInt(); + ModelNode modelNode = m_connectionView->modelNodeForInternalId(internalId); + + if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode)) + return Qt::ItemIsEnabled; + + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + void ConnectionModel::resetModel() { beginResetModel(); @@ -82,7 +99,7 @@ void ConnectionModel::resetModel() setHorizontalHeaderLabels(QStringList({ tr("Target"), tr("Signal Handler"), tr("Action") })); if (connectionView()->isAttached()) { - for (const ModelNode modelNode : connectionView()->allModelNodes()) + for (const ModelNode &modelNode : connectionView()->allModelNodes()) addModelNode(modelNode); } @@ -94,8 +111,8 @@ void ConnectionModel::resetModel() SignalHandlerProperty ConnectionModel::signalHandlerPropertyForRow(int rowNumber) const { - const int internalId = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 1).toInt(); - const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), Qt::UserRole + 2).toString(); + const int internalId = data(index(rowNumber, TargetModelNodeRow), UserRoles::InternalIdRole).toInt(); + const QString targetPropertyName = data(index(rowNumber, TargetModelNodeRow), UserRoles::TargetPropertyNameRole).toString(); ModelNode modelNode = connectionView()->modelNodeForInternalId(internalId); if (modelNode.isValid()) @@ -256,8 +273,8 @@ void ConnectionModel::updateTargetNode(int rowNumber) void ConnectionModel::updateCustomData(QStandardItem *item, const SignalHandlerProperty &signalHandlerProperty) { - item->setData(signalHandlerProperty.parentModelNode().internalId(), Qt::UserRole + 1); - item->setData(signalHandlerProperty.name(), Qt::UserRole + 2); + item->setData(signalHandlerProperty.parentModelNode().internalId(), UserRoles::InternalIdRole); + item->setData(signalHandlerProperty.name(), UserRoles::TargetPropertyNameRole); } ModelNode ConnectionModel::getTargetNodeForConnection(const ModelNode &connection) const diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h index b7d24db719..5acf4f6402 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.h @@ -48,7 +48,14 @@ public: TargetPropertyNameRow = 1, SourceRow = 2 }; + enum UserRoles { + InternalIdRole = Qt::UserRole + 1, + TargetPropertyNameRole + }; ConnectionModel(ConnectionView *parent = nullptr); + + Qt::ItemFlags flags(const QModelIndex &modelIndex) const override; + void resetModel(); SignalHandlerProperty signalHandlerPropertyForRow(int rowNumber) const; ConnectionView *connectionView() const; diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp index 0410ccf908..88dd6a971d 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.cpp @@ -36,6 +36,8 @@ #include <variantproperty.h> #include <signalhandlerproperty.h> +#include <QTableView> + namespace QmlDesigner { namespace Internal { @@ -162,6 +164,33 @@ void ConnectionView::selectedNodesChanged(const QList<ModelNode> & selectedNodeL emit connectionViewWidget()->setEnabledAddButton(selectedNodeList.count() == 1); } +void ConnectionView::auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) +{ + Q_UNUSED(node) + + // Check if the auxiliary data is actually the locked property or if it is unlocked + if (name != QmlDesigner::lockedProperty || !data.toBool()) + return; + + QItemSelectionModel *selectionModel = connectionTableView()->selectionModel(); + if (!selectionModel->hasSelection()) + return; + + QModelIndex modelIndex = selectionModel->currentIndex(); + if (!modelIndex.isValid() || !model()) + return; + + const int internalId = connectionModel()->data(connectionModel()->index(modelIndex.row(), + ConnectionModel::TargetModelNodeRow), + ConnectionModel::UserRoles::InternalIdRole).toInt(); + ModelNode modelNode = modelNodeForInternalId(internalId); + + if (modelNode.isValid() && ModelNode::isThisOrAncestorLocked(modelNode)) + selectionModel->clearSelection(); +} + void ConnectionView::importsChanged(const QList<Import> & /*addedImports*/, const QList<Import> & /*removedImports*/) { backendModel()->resetModel(); diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h index 905fa02a58..dda496263d 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionview.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionview.h @@ -69,6 +69,7 @@ public: void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void importsChanged(const QList<Import> &addedImports, const QList<Import> &removedImports) override; diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp index 36bae9be0c..9fec8b2fc5 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/graphicsview.cpp @@ -27,6 +27,7 @@ #include "axis.h" #include "curveeditormodel.h" #include "curveitem.h" +#include "navigation2d.h" #include "treeitem.h" #include "utils.h" @@ -79,6 +80,13 @@ GraphicsView::GraphicsView(CurveEditorModel *model, QWidget *parent) applyZoom(m_zoomX, m_zoomY); update(); + + QmlDesigner::Navigation2dFilter *filter = new QmlDesigner::Navigation2dFilter(this); + auto zoomChanged = &QmlDesigner::Navigation2dFilter::zoomChanged; + connect(filter, zoomChanged, [this](double scale, const QPointF &pos) { + applyZoom(m_zoomX + scale, m_zoomY, mapToGlobal(pos.toPoint())); + }); + installEventFilter(filter); } GraphicsView::~GraphicsView() diff --git a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp index 891f6896ea..0fdb00ff1b 100644 --- a/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp +++ b/src/plugins/qmldesigner/components/curveeditor/detail/handleitem.cpp @@ -41,8 +41,11 @@ struct HandleGeometry handle = QRectF(topLeft, -topLeft); toKeyframe = QLineF(QPointF(0.0, 0.0), -pos); angle = -toKeyframe.angle() + 45.0; + bbox = handle.united(QRectF(-pos, QSizeF(1.0,1.0))); } + QRectF bbox; + QRectF handle; QLineF toKeyframe; @@ -97,7 +100,7 @@ HandleItem::Slot HandleItem::slot() const QRectF HandleItem::boundingRect() const { HandleGeometry geom(pos(), m_style); - return geom.handle; + return geom.bbox; } bool HandleItem::contains(const QPointF &point) const diff --git a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp index 0ed7abaefe..fc7e8b97c7 100644 --- a/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/abstractformeditortool.cpp @@ -189,7 +189,7 @@ FormEditorItem *AbstractFormEditorTool::topMovableFormEditorItem(const QList<QGr FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &point, const QList<QGraphicsItem*> &itemList) { FormEditorItem* nearestItem = nullptr; - foreach (QGraphicsItem *item, itemList) { + for (QGraphicsItem *item : itemList) { FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item); if (formEditorItem && formEditorItem->flowHitTest(point)) @@ -201,6 +201,9 @@ FormEditorItem* AbstractFormEditorTool::nearestFormEditorItem(const QPointF &poi if (formEditorItem->parentItem() && !formEditorItem->parentItem()->isContentVisible()) continue; + if (formEditorItem && ModelNode::isThisOrAncestorLocked(formEditorItem->qmlItemNode().modelNode())) + continue; + if (!nearestItem) nearestItem = formEditorItem; else if (formEditorItem->selectionWeigth(point, 1) < nearestItem->selectionWeigth(point, 0)) diff --git a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp index 9a643edef8..8bd2d827f5 100644 --- a/src/plugins/qmldesigner/components/formeditor/dragtool.cpp +++ b/src/plugins/qmldesigner/components/formeditor/dragtool.cpp @@ -250,7 +250,6 @@ void DragTool::dropEvent(const QList<QGraphicsItem *> &/*itemList*/, QGraphicsSc if (m_dragNode.isValid()) view()->setSelectedModelNode(m_dragNode); - m_dragNode = QmlItemNode(); view()->changeToSelectionTool(); diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp index b621dacb4a..4cc7c027b2 100644 --- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp +++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp @@ -286,6 +286,7 @@ bool FormEditorItem::flowHitTest(const QPointF & ) const void FormEditorItem::setFrameColor(const QColor &color) { m_frameColor = color; + update(); } FormEditorItem::~FormEditorItem() diff --git a/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp b/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp index 242b5649d2..fa399f8860 100644 --- a/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp +++ b/src/plugins/qmldesigner/components/formeditor/rubberbandselectionmanipulator.cpp @@ -90,10 +90,10 @@ void RubberBandSelectionManipulator::select(SelectionType selectionType) if (!m_beginFormEditorItem) return; - QList<QGraphicsItem*> itemList = m_editorView->scene()->items(m_selectionRectangleElement.rect(), Qt::IntersectsItemBoundingRect); + QList<QGraphicsItem *> itemList = m_editorView->scene()->items(m_selectionRectangleElement.rect(), Qt::IntersectsItemBoundingRect); QList<QmlItemNode> newNodeList; - foreach (QGraphicsItem* item, itemList) + for (QGraphicsItem *item : itemList) { FormEditorItem *formEditorItem = FormEditorItem::fromQGraphicsItem(item); @@ -137,7 +137,7 @@ void RubberBandSelectionManipulator::select(SelectionType selectionType) } -void RubberBandSelectionManipulator::setItems(const QList<FormEditorItem*> &itemList) +void RubberBandSelectionManipulator::setItems(const QList<FormEditorItem *> &itemList) { m_itemList = itemList; } diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp index 05929fbd0e..b572d55f0b 100644 --- a/src/plugins/qmldesigner/components/integration/designdocument.cpp +++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp @@ -49,6 +49,7 @@ #include <coreplugin/icore.h> #include <coreplugin/idocument.h> #include <coreplugin/editormanager/editormanager.h> +#include <utils/algorithm.h> #include <qmljs/qmljsmodelmanagerinterface.h> @@ -57,6 +58,7 @@ #include <QDebug> #include <QApplication> +#include <QMessageBox> #include <QPlainTextEdit> #include <QRandomGenerator> @@ -375,9 +377,41 @@ void DesignDocument::deleteSelected() if (!currentModel()) return; + QStringList lockedNodes; + for (const ModelNode &modelNode : view()->selectedModelNodes()) { + for (const ModelNode &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.isValid() && !node.isRootNode() && node.locked()) + lockedNodes.push_back(node.id()); + } + } + + if (!lockedNodes.empty()) { + Utils::sort(lockedNodes); + QString detailedText = QString("<b>" + tr("Locked items:") + "</b><br>"); + + for (const auto &id : qAsConst(lockedNodes)) + detailedText.append("- " + id + "<br>"); + + detailedText.chop(QString("<br>").size()); + + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("Delete/Cut Item")); + msgBox.setText(QString(tr("Deleting or cutting this item will modify locked items.") + "<br><br>%1") + .arg(detailedText)); + msgBox.setInformativeText(tr("Do you want to continue by removing the item (Delete) or removing it and copying it to the clipboard (Cut)?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + } + rewriterView()->executeInTransaction("DesignDocument::deleteSelected", [this](){ QList<ModelNode> toDelete = view()->selectedModelNodes(); - foreach (ModelNode node, toDelete) { + + for (ModelNode node : toDelete) { if (node.isValid() && !node.isRootNode() && QmlObjectNode::isValidQmlObjectNode(node)) QmlObjectNode(node).destroy(); } @@ -545,7 +579,7 @@ void DesignDocument::paste() } view.setSelectedModelNodes({pastedNode}); }); - NodeMetaInfo::clearCache(); + view.model()->clearMetaInfoCache(); } } 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/iconcheckboxitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp index 0371d0f1bb..5cd5721812 100644 --- a/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/iconcheckboxitemdelegate.cpp @@ -82,6 +82,12 @@ void IconCheckboxItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &styleOption, const QModelIndex &modelIndex) const { + bool isVisibilityIcon = modelIndex.column() != NavigatorTreeModel::ColumnType::Visibility; + // We need to invert the check status if visibility icon + bool checked = isVisibilityIcon ? isChecked(modelIndex) : !isChecked(modelIndex); + if (!(styleOption.state & QStyle::State_MouseOver) && !checked) + return; + if (rowIsPropertyRole(modelIndex.model(), modelIndex)) return; //Do not paint icons for property rows diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index 5538a9cb8d..602a0f7316 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -200,10 +200,10 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const if (!modelNode.isValid()) return QVariant(); - if (role == ItemIsVisibleRole) //independent of column + if (role == ItemIsVisibleRole) // independent of column return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked; - if (index.column() == 0) { + if (index.column() == ColumnType::Name) { if (role == Qt::DisplayRole) { return modelNode.displayName(); } else if (role == Qt::DecorationRole) { @@ -240,18 +240,24 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const } else if (role == ModelNodeRole) { return QVariant::fromValue<ModelNode>(modelNode); } - } else if (index.column() == 1) { //export + } else if (index.column() == ColumnType::Alias) { // export if (role == Qt::CheckStateRole) - return currentQmlObjectNode.isAliasExported() ? Qt::Checked : Qt::Unchecked; + return currentQmlObjectNode.isAliasExported() ? Qt::Checked : Qt::Unchecked; else if (role == Qt::ToolTipRole) return tr("Toggles whether this item is exported as an " "alias property of the root item."); - } else if (index.column() == 2) { //visible + } else if (index.column() == ColumnType::Visibility) { // visible if (role == Qt::CheckStateRole) return m_view->isNodeInvisible(modelNode) ? Qt::Unchecked : Qt::Checked; else if (role == Qt::ToolTipRole) return tr("Toggles the visibility of this item in the form editor.\n" "This is independent of the visibility property in QML."); + } else if (index.column() == ColumnType::Lock) { // lock + if (role == Qt::CheckStateRole) + return modelNode.locked() ? Qt::Checked : Qt::Unchecked; + else if (role == Qt::ToolTipRole) + return tr("Toggles whether this item is locked.\n" + "Locked items can't be modified or selected."); } return QVariant(); @@ -259,7 +265,16 @@ QVariant NavigatorTreeModel::data(const QModelIndex &index, int role) const Qt::ItemFlags NavigatorTreeModel::flags(const QModelIndex &index) const { - if (index.column() == 0) + if (index.column() == ColumnType::Alias + || index.column() == ColumnType::Visibility + || index.column() == ColumnType::Lock) + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren; + + const ModelNode modelNode = modelNodeForIndex(index); + if (ModelNode::isThisOrAncestorLocked(modelNode)) + return Qt::NoItemFlags; + + if (index.column() == ColumnType::Name) return Qt::ItemIsEditable | Qt::ItemIsDropEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | Qt::ItemIsEnabled; return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable @@ -378,7 +393,7 @@ int NavigatorTreeModel::columnCount(const QModelIndex &parent) const if (parent.column() > 0) return 0; - return 3; + return ColumnType::Count; } ModelNode NavigatorTreeModel::modelNodeForIndex(const QModelIndex &index) const @@ -755,7 +770,8 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper auto doMoveNodesInteractive = [&parentProperty, modelNodes, targetIndex](){ const TypeName propertyQmlType = parentProperty.parentModelNode().metaInfo().propertyTypeName(parentProperty.name()); - foreach (const ModelNode &modelNode, modelNodes) { + int idx = targetIndex; + for (const ModelNode &modelNode : modelNodes) { if (modelNode.isValid() && modelNode != parentProperty.parentModelNode() && !modelNode.isAncestorOf(parentProperty.parentModelNode()) @@ -764,10 +780,9 @@ void NavigatorTreeModel::moveNodesInteractive(NodeAbstractProperty &parentProper //once the MetaInfo is part of instances we can do this right bool nodeCanBeMovedToParentProperty = removeModelNodeFromNodeProperty(parentProperty, modelNode); - if (nodeCanBeMovedToParentProperty) { reparentModelNodeToNodeProperty(parentProperty, modelNode); - slideModelNodeInList(parentProperty, modelNode, targetIndex); + slideModelNodeInList(parentProperty, modelNode, idx++); } } } @@ -792,11 +807,13 @@ Qt::DropActions NavigatorTreeModel::supportedDragActions() const bool NavigatorTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) { ModelNode modelNode = modelNodeForIndex(index); - if (index.column() == 1 && role == Qt::CheckStateRole) { + if (index.column() == ColumnType::Alias && role == Qt::CheckStateRole) { QTC_ASSERT(m_view, return false); m_view->handleChangedExport(modelNode, value.toInt() != 0); - } else if (index.column() == 2 && role == Qt::CheckStateRole) { + } else if (index.column() == ColumnType::Visibility && role == Qt::CheckStateRole) { QmlVisualNode(modelNode).setVisibilityOverride(value.toInt() == 0); + } else if (index.column() == ColumnType::Lock && role == Qt::CheckStateRole) { + modelNode.setLocked(value.toInt() != 0); } return true; @@ -806,7 +823,7 @@ void NavigatorTreeModel::notifyDataChanged(const ModelNode &modelNode) { const QModelIndex index = indexForModelNode(modelNode); const QAbstractItemModel *model = index.model(); - const QModelIndex sibling = model ? model->sibling(index.row(), 2, index) : QModelIndex(); + const QModelIndex sibling = model ? model->sibling(index.row(), ColumnType::Count - 1, index) : QModelIndex(); emit dataChanged(index, sibling); } diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h index d474ee98b9..bb2712148d 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.h @@ -49,6 +49,14 @@ class NavigatorTreeModel : public QAbstractItemModel, public NavigatorModelInter public: + enum ColumnType { + Name = 0, + Alias, + Visibility, + Lock, + Count + }; + explicit NavigatorTreeModel(QObject *parent = nullptr); ~NavigatorTreeModel() override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp index ddf8862c89..8be526278c 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreeview.cpp @@ -168,6 +168,7 @@ NavigatorTreeView::NavigatorTreeView(QWidget *parent) setMinimumWidth(240); setRootIsDecorated(false); setIndentation(indentation() * 0.5); + viewport()->setAttribute(Qt::WA_Hover); m_toolTipHideTimer.setSingleShot(true); connect(&m_toolTipHideTimer, &QTimer::timeout, [this]() { diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp index 87952613b1..e1702bdac4 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp @@ -41,6 +41,7 @@ #include <qmlitemnode.h> #include <rewritingexception.h> #include <nodeinstanceview.h> +#include <theme.h> #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/icore.h> @@ -48,6 +49,7 @@ #include <utils/algorithm.h> #include <utils/icon.h> #include <utils/utilsicons.h> +#include <utils/stylehelper.h> #include <QHeaderView> #include <QTimer> @@ -138,13 +140,14 @@ void NavigatorView::modelAttached(Model *model) QTreeView *treeView = treeWidget(); - treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); - treeView->header()->resizeSection(1,26); + treeView->header()->setSectionResizeMode(NavigatorTreeModel::ColumnType::Name, QHeaderView::Stretch); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Alias, 26); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Visibility, 26); + treeView->header()->resizeSection(NavigatorTreeModel::ColumnType::Lock, 26); treeView->setIndentation(20); m_currentModelInterface->setFilter(false); - QTimer::singleShot(0, this, [this, treeView]() { m_currentModelInterface->setFilter( DesignerSettings::getValue(DesignerSettingsKey::NAVIGATOR_SHOW_ONLY_VISIBLE_ITEMS).toBool()); @@ -166,10 +169,6 @@ void NavigatorView::modelAttached(Model *model) } } }); - -#ifdef _LOCK_ITEMS_ - treeView->header()->resizeSection(2,20); -#endif } void NavigatorView::modelAboutToBeDetached(Model *model) @@ -304,7 +303,7 @@ void NavigatorView::nodeIdChanged(const ModelNode& modelNode, const QString & /* m_currentModelInterface->notifyDataChanged(modelNode); } -void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& /*propertyList*/) +void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &/*propertyList*/) { } @@ -321,7 +320,7 @@ void NavigatorView::propertiesRemoved(const QList<AbstractProperty> &propertyLis m_currentModelInterface->notifyModelNodesRemoved(modelNodes); } -void NavigatorView::rootNodeTypeChanged(const QString & /*type*/, int /*majorVersion*/, int /*minorVersion*/) +void NavigatorView::rootNodeTypeChanged(const QString &/*type*/, int /*majorVersion*/, int /*minorVersion*/) { m_currentModelInterface->notifyDataChanged(rootModelNode()); } @@ -332,9 +331,12 @@ void NavigatorView::nodeTypeChanged(const ModelNode &modelNode, const TypeName & } void NavigatorView::auxiliaryDataChanged(const ModelNode &modelNode, - const PropertyName & /*name*/, - const QVariant & /*data*/) + const PropertyName &name, + const QVariant &data) { + Q_UNUSED(name) + Q_UNUSED(data) + m_currentModelInterface->notifyDataChanged(modelNode); } @@ -344,8 +346,8 @@ void NavigatorView::instanceErrorChanged(const QVector<ModelNode> &errorNodeList m_currentModelInterface->notifyDataChanged(modelNode); } -void NavigatorView::nodeOrderChanged(const NodeListProperty & listProperty, - const ModelNode & /*node*/, +void NavigatorView::nodeOrderChanged(const NodeListProperty &listProperty, + const ModelNode &/*node*/, int /*oldIndex*/) { m_currentModelInterface->notifyModelNodesMoved(listProperty.directSubNodes()); @@ -613,33 +615,50 @@ void NavigatorView::setupWidget() connect(m_widget.data(), &NavigatorWidget::reverseOrderToggled, this, &NavigatorView::reverseOrderToggled); #ifndef QMLDESIGNER_TEST + const QString fontName = "qtds_propertyIconFont.ttf"; + + const QIcon visibilityOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::visibilityOn), + 28, 28, QColor(Qt::white)); + const QIcon visibilityOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::visibilityOff), + 28, 28, QColor(Qt::white)); + + const QIcon aliasOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::idAliasOn), + 28, 28, QColor(Qt::red)); + const QIcon aliasOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::idAliasOff), + 28, 28, QColor(Qt::white)); + + const QIcon lockOnIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::lockOn), + 28, 28, QColor(Qt::white)); + const QIcon lockOffIcon = + Utils::StyleHelper::getIconFromIconFont(fontName, + Theme::getIconUnicode(Theme::Icon::lockOff), + 28, 28, QColor(Qt::white)); + auto idDelegate = new NameItemDelegate(this); - IconCheckboxItemDelegate *showDelegate = - new IconCheckboxItemDelegate(this, - Utils::Icons::EYE_OPEN_TOOLBAR.icon(), - Utils::Icons::EYE_CLOSED_TOOLBAR.icon()); - IconCheckboxItemDelegate *exportDelegate = - new IconCheckboxItemDelegate(this, - Icons::EXPORT_CHECKED.icon(), - Icons::EXPORT_UNCHECKED.icon()); + IconCheckboxItemDelegate *visibilityDelegate = + new IconCheckboxItemDelegate(this, visibilityOnIcon, visibilityOffIcon); -#ifdef _LOCK_ITEMS_ - IconCheckboxItemDelegate *lockDelegate = - new IconCheckboxItemDelegate(this, - Utils::Icons::LOCKED_TOOLBAR.icon(), - Utils::Icons::UNLOCKED_TOOLBAR.icon()); -#endif + IconCheckboxItemDelegate *aliasDelegate = + new IconCheckboxItemDelegate(this, aliasOnIcon, aliasOffIcon); + IconCheckboxItemDelegate *lockDelegate = + new IconCheckboxItemDelegate(this, lockOnIcon, lockOffIcon); - treeWidget()->setItemDelegateForColumn(0, idDelegate); -#ifdef _LOCK_ITEMS_ - treeWidget()->setItemDelegateForColumn(1,lockDelegate); - treeWidget()->setItemDelegateForColumn(2,showDelegate); -#else - treeWidget()->setItemDelegateForColumn(1, exportDelegate); - treeWidget()->setItemDelegateForColumn(2, showDelegate); -#endif + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Name, idDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Alias, aliasDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Visibility, visibilityDelegate); + treeWidget()->setItemDelegateForColumn(NavigatorTreeModel::ColumnType::Lock, lockDelegate); #endif //QMLDESIGNER_TEST } diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.h b/src/plugins/qmldesigner/components/navigator/navigatorview.h index 67042634e9..6189b24559 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorview.h +++ b/src/plugins/qmldesigner/components/navigator/navigatorview.h @@ -82,7 +82,7 @@ public: void propertiesRemoved(const QList<AbstractProperty>& propertyList) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList , - const QList<ModelNode> &lastSelectedNodeList) override; + const QList<ModelNode> &lastSelectedNodeList) override; void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override; void instanceErrorChanged(const QVector<ModelNode> &errorNodeList) override; diff --git a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp index 3813881f55..649b53b5ff 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp @@ -78,7 +78,7 @@ NavigatorWidget::NavigatorWidget(NavigatorView *view) #endif } -void NavigatorWidget::setTreeModel(QAbstractItemModel* model) +void NavigatorWidget::setTreeModel(QAbstractItemModel *model) { m_treeView->setModel(model); } @@ -92,7 +92,6 @@ QList<QToolButton *> NavigatorWidget::createToolBarWidgets() { QList<QToolButton *> buttons; - auto button = new QToolButton(); button->setIcon(Icons::ARROW_LEFT.icon()); button->setToolTip(tr("Become last sibling of parent (CTRL + Left).")); @@ -180,7 +179,6 @@ void NavigatorWidget::enableNavigator() m_treeView->setEnabled(true); } - NavigatorView *NavigatorWidget::navigatorView() const { return m_navigatorView.data(); 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/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp index a42db492d9..71e2c99627 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp @@ -29,15 +29,21 @@ #include <model.h> +#include <utils/algorithm.h> + #include <QFileDialog> #include <QDirIterator> #include <qmlmodelnodeproxy.h> static QString s_lastBrowserPath; -FileResourcesModel::FileResourcesModel(QObject *parent) : - QObject(parent), m_filter(QLatin1String("(*.*)")), m_lock(false) +FileResourcesModel::FileResourcesModel(QObject *parent) + : QObject(parent) + , m_filter(QLatin1String("(*.*)")) + , m_fileSystemWatcher(new Utils::FileSystemWatcher(this)) { + connect(m_fileSystemWatcher, &Utils::FileSystemWatcher::directoryChanged, + this, &FileResourcesModel::refreshModel); } void FileResourcesModel::setModelNodeBackend(const QVariant &modelNodeBackend) @@ -163,7 +169,6 @@ QVariant FileResourcesModel::modelNodeBackend() const bool filterMetaIcons(const QString &fileName) { - QFileInfo info(fileName); if (info.dir().path().split('/').contains("designer")) { @@ -189,12 +194,20 @@ bool filterMetaIcons(const QString &fileName) void FileResourcesModel::setupModel() { - m_lock = true; + m_dirPath = QFileInfo(m_path.toLocalFile()).dir(); + + refreshModel(); + + m_fileSystemWatcher->removeDirectories(m_fileSystemWatcher->directories()); + m_fileSystemWatcher->addDirectory(m_dirPath.absolutePath(), + Utils::FileSystemWatcher::WatchAllChanges); +} + +void FileResourcesModel::refreshModel() +{ m_fullPathModel.clear(); m_fileNameModel.clear(); - m_dirPath = QFileInfo(m_path.toLocalFile()).dir(); - QStringList filterList = m_filter.split(QLatin1Char(' ')); QDirIterator it(m_dirPath.absolutePath(), filterList, QDir::Files, QDirIterator::Subdirectories); @@ -203,11 +216,15 @@ void FileResourcesModel::setupModel() if (filterMetaIcons(absolutePath)) { QString filePath = m_dirPath.relativeFilePath(absolutePath); m_fullPathModel.append(filePath); - m_fileNameModel.append(filePath.mid(filePath.lastIndexOf('/') + 1)); } } - m_lock = false; + Utils::sort(m_fullPathModel, [](const QString &s1, const QString &s2) { + return s1.mid(s1.lastIndexOf('/') + 1).toLower() < s2.mid(s2.lastIndexOf('/') + 1).toLower(); + }); + + for (const QString &fullPath : qAsConst(m_fullPathModel)) + m_fileNameModel.append(fullPath.mid(fullPath.lastIndexOf('/') + 1)); emit fullPathModelChanged(); emit fileNameModelChanged(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h index e0a1964347..226421f441 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h +++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.h @@ -27,6 +27,8 @@ #include <qmlitemnode.h> +#include <utils/filesystemwatcher.h> + #include <QDir> #include <QObject> #include <QStringList> @@ -60,6 +62,7 @@ public: QStringList fullPathModel() const; QStringList fileNameModel() const; void setupModel(); + void refreshModel(); Q_INVOKABLE void openFileDialog(); @@ -79,12 +82,11 @@ private: QUrl m_path; QDir m_dirPath; QString m_filter; - bool m_lock; QString m_currentPath; QString m_lastModelPath; QStringList m_fullPathModel; QStringList m_fileNameModel; - + Utils::FileSystemWatcher *m_fileSystemWatcher; }; QML_DECLARE_TYPE(FileResourcesModel) diff --git a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp index 3a15283663..a8792ef204 100644 --- a/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp +++ b/src/plugins/qmldesigner/components/stateseditor/stateseditorview.cpp @@ -30,6 +30,7 @@ #include <QDebug> #include <QRegularExpression> +#include <QMessageBox> #include <cmath> #include <memory> @@ -42,6 +43,7 @@ #include <qmlitemnode.h> #include <qmlstate.h> #include <annotationeditor/annotationeditor.h> +#include <utils/algorithm.h> namespace QmlDesigner { @@ -92,11 +94,46 @@ void StatesEditorView::removeState(int nodeId) if (nodeId > 0 && hasModelNodeForInternalId(nodeId)) { ModelNode stateNode(modelNodeForInternalId(nodeId)); Q_ASSERT(stateNode.metaInfo().isSubclassOf("QtQuick.State")); + + QmlModelState modelState(stateNode); + if (modelState.isValid()) { + QStringList lockedTargets; + const auto propertyChanges = modelState.propertyChanges(); + for (const QmlPropertyChanges &change : propertyChanges) { + const ModelNode target = change.target(); + if (target.locked()) + lockedTargets.push_back(target.id()); + } + + if (!lockedTargets.empty()) { + Utils::sort(lockedTargets); + QString detailedText = QString("<b>" + tr("Locked items:") + "</b><br>"); + + for (const auto &id : qAsConst(lockedTargets)) + detailedText.append("- " + id + "<br>"); + + detailedText.chop(QString("<br>").size()); + + QMessageBox msgBox; + msgBox.setTextFormat(Qt::RichText); + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("Remove State")); + msgBox.setText(QString(tr("Removing this state will modify locked items.") + "<br><br>%1") + .arg(detailedText)); + msgBox.setInformativeText(tr("Do you want to continue by removing the state?")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ok); + + if (msgBox.exec() == QMessageBox::Cancel) + return; + } + } + NodeListProperty parentProperty = stateNode.parentProperty().toNodeListProperty(); if (parentProperty.count() <= 1) { setCurrentState(baseState()); - } else if (parentProperty.isValid()){ + } else if (parentProperty.isValid()) { int index = parentProperty.indexOf(stateNode); if (index == 0) setCurrentState(parentProperty.at(1)); @@ -104,7 +141,6 @@ void StatesEditorView::removeState(int nodeId) setCurrentState(parentProperty.at(index - 1)); } - stateNode.destroy(); } } catch (const RewritingException &e) { diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp index a0f0eedf29..007e8ae571 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.cpp @@ -63,6 +63,11 @@ TimelineGraphicsLayout::TimelineGraphicsLayout(TimelineGraphicsScene *scene, Tim TimelineGraphicsLayout::~TimelineGraphicsLayout() = default; +int TimelineGraphicsLayout::zoom() const +{ + return m_rulerItem->zoom(); +} + double TimelineGraphicsLayout::rulerWidth() const { return m_rulerItem->preferredWidth(); @@ -133,12 +138,12 @@ void TimelineGraphicsLayout::setTimeline(const QmlTimeline &timeline) if (auto *scene = timelineScene()) if (auto *view = scene->timelineView()) if (!timeline.isValid() && view->isAttached()) - emit scaleFactorChanged(0); + emit zoomChanged(0); } -void TimelineGraphicsLayout::setRulerScaleFactor(int factor) +void TimelineGraphicsLayout::setZoom(int factor) { - m_rulerItem->setRulerScaleFactor(factor); + m_rulerItem->setZoom(factor); } void TimelineGraphicsLayout::invalidate() diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h index f4d2ae5836..ce2423dfe9 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicslayout.h @@ -44,7 +44,7 @@ class TimelineGraphicsLayout : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int factor); + void zoomChanged(int factor); public: TimelineGraphicsLayout(TimelineGraphicsScene *scene, TimelineItem *parent = nullptr); @@ -52,6 +52,8 @@ public: ~TimelineGraphicsLayout() override; public: + int zoom() const; + double rulerWidth() const; double rulerScaling() const; @@ -66,7 +68,7 @@ public: void setTimeline(const QmlTimeline &timeline); - void setRulerScaleFactor(int factor); + void setZoom(int factor); void invalidate(); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp index 3b87f22c51..5c2621300f 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp @@ -123,9 +123,9 @@ TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *parent) auto changeScale = [this](int factor) { timelineWidget()->changeScaleFactor(factor); - setRulerScaling(qreal(factor)); + setZoom(factor); }; - connect(m_layout, &TimelineGraphicsLayout::scaleFactorChanged, changeScale); + connect(m_layout, &TimelineGraphicsLayout::zoomChanged, changeScale); } TimelineGraphicsScene::~TimelineGraphicsScene() @@ -144,7 +144,7 @@ void TimelineGraphicsScene::onShow() setCurrentFrame(cf); } - emit m_layout->scaleFactorChanged(0); + emit m_layout->zoomChanged(0); } } @@ -271,6 +271,11 @@ void TimelineGraphicsScene::setEndFrame(int frame) timeline.modelNode().variantProperty("endFrame").setValue(frame); } +int TimelineGraphicsScene::zoom() const +{ + return m_layout->zoom(); +} + qreal TimelineGraphicsScene::rulerScaling() const { return m_layout->rulerScaling(); @@ -332,15 +337,20 @@ QVector<qreal> TimelineGraphicsScene::keyframePositions(const QmlTimelineKeyfram return positions; } -void TimelineGraphicsScene::setRulerScaling(int scaleFactor) +void TimelineGraphicsScene::setZoom(int scaleFactor) +{ + setZoom(scaleFactor, currentFramePosition()); +} + +void TimelineGraphicsScene::setZoom(int scaleFactor, double pivot) { const qreal oldOffset = scrollOffset(); const qreal oldScaling = m_layout->rulerScaling(); - const qreal oldPosition = mapToScene(currentFramePosition()); - m_layout->setRulerScaleFactor(scaleFactor); + const qreal oldPosition = mapToScene(pivot); + m_layout->setZoom(scaleFactor); const qreal newScaling = m_layout->rulerScaling(); - const qreal newPosition = mapToScene(currentFramePosition()); + const qreal newPosition = mapToScene(pivot); const qreal newOffset = oldOffset + (newPosition - oldPosition); @@ -428,6 +438,18 @@ void TimelineGraphicsScene::invalidateKeyframesForTarget(const ModelNode &target TimelineSectionItem::updateFramesForTarget(child, target); } +void TimelineGraphicsScene::invalidateHeightForTarget(const ModelNode &target) +{ + if (!target.isValid()) + return; + + const auto children = m_layout->childItems(); + for (auto child : children) + TimelineSectionItem::updateHeightForTarget(child, target); + + invalidateLayout(); +} + void TimelineGraphicsScene::invalidateScene() { ModelNode node = timelineView()->modelNodeForId( @@ -502,7 +524,7 @@ QRectF AbstractScrollGraphicsScene::selectionBounds() const } void AbstractScrollGraphicsScene::selectKeyframes(const SelectionMode &mode, - const QList<TimelineKeyframeItem *> &items) + const QList<TimelineKeyframeItem *> &items) { if (mode == SelectionMode::Remove || mode == SelectionMode::Toggle) { for (auto *item : items) { @@ -731,7 +753,7 @@ void TimelineGraphicsScene::deleteKeyframeGroup(const ModelNode &group) if (!QmlTimelineKeyframeGroup::isValidQmlTimelineKeyframeGroup(group)) return; - timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeGroupDeletion", [group](){ + timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeGroupDeletion", [group]() { ModelNode nonConst = group; nonConst.destroy(); }); @@ -739,7 +761,7 @@ void TimelineGraphicsScene::deleteKeyframeGroup(const ModelNode &group) void TimelineGraphicsScene::deleteKeyframes(const QList<ModelNode> &frames) { - timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeDeletion", [frames](){ + timelineView()->executeInTransaction("TimelineGraphicsScene::handleKeyframeDeletion", [frames]() { for (auto keyframe : frames) { if (keyframe.isValid()) { ModelNode frame = keyframe; @@ -764,7 +786,7 @@ AbstractView *TimelineGraphicsScene::abstractView() const int AbstractScrollGraphicsScene::getScrollOffset(QGraphicsScene *scene) { - auto scrollScene = qobject_cast<AbstractScrollGraphicsScene*>(scene); + auto scrollScene = qobject_cast<AbstractScrollGraphicsScene *>(scene); if (scrollScene) return scrollScene->scrollOffset(); return 0; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h index 7413cb1dbb..fc4da39f32 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h @@ -58,7 +58,6 @@ class AbstractScrollGraphicsScene : public QGraphicsScene public: AbstractScrollGraphicsScene(QWidget *parent); - ; int scrollOffset() const; void setScrollOffset(int offset); @@ -74,6 +73,7 @@ public: bool isKeyframeSelected(TimelineKeyframeItem *keyframe) const; bool multipleKeyframesSelected() const; + virtual int zoom() const = 0; virtual qreal rulerScaling() const = 0; virtual int rulerWidth() const = 0; virtual qreal rulerDuration() const = 0; @@ -134,6 +134,7 @@ public: TimelineWidget *timelineWidget() const; TimelineToolBar *toolBar() const; + int zoom() const override; qreal rulerScaling() const override; int rulerWidth() const override; qreal rulerDuration() const override; @@ -152,12 +153,14 @@ public: qreal snap(qreal frame, bool snapToPlayhead = true) override; - void setRulerScaling(int scaling); + void setZoom(int scaling); + void setZoom(int scaling, double pivot); void commitCurrentFrame(qreal frame); void invalidateSectionForTarget(const ModelNode &modelNode); void invalidateKeyframesForTarget(const ModelNode &modelNode); + void invalidateHeightForTarget(const ModelNode &modelNode); void invalidateScene(); void invalidateScrollbar() override; @@ -203,7 +206,6 @@ private: QList<QGraphicsItem *> itemsAt(const QPointF &pos); private: - TimelineWidget *m_parent = nullptr; TimelineGraphicsLayout *m_layout = nullptr; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp index 12345a404e..1b0b2cb7e5 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp @@ -156,4 +156,9 @@ TimelineFrameHandle *TimelineMovableAbstractItem::asTimelineFrameHandle() return nullptr; } +bool TimelineMovableAbstractItem::isLocked() const +{ + return false; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h index 199a78ad99..d79101b4d9 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h @@ -69,6 +69,8 @@ public: virtual TimelineKeyframeItem *asTimelineKeyframeItem(); virtual TimelineFrameHandle *asTimelineFrameHandle(); + virtual bool isLocked() const; + protected: int scrollOffset() const; void mousePressEvent(QGraphicsSceneMouseEvent *event) override; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp index 5dc52bbc9b..0508606c11 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp @@ -70,6 +70,9 @@ void TimelineMoveTool::mousePressEvent(TimelineMovableAbstractItem *item, { Q_UNUSED(item) + if (currentItem() && currentItem()->isLocked()) + return; + if (auto *current = currentItem()->asTimelineKeyframeItem()) { const qreal sourceFrame = qRound(current->mapFromSceneToFrame(current->rect().center().x())); const qreal targetFrame = qRound(current->mapFromSceneToFrame(event->scenePos().x())); @@ -85,6 +88,9 @@ void TimelineMoveTool::mouseMoveEvent(TimelineMovableAbstractItem *item, if (!currentItem()) return; + if (currentItem()->isLocked()) + return; + if (auto *current = currentItem()->asTimelineKeyframeItem()) { // prevent dragging if deselecting a keyframe (Ctrl+click and drag a selected keyframe) if (!current->highlighted()) diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp index c77d466585..1cff801235 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp @@ -171,6 +171,17 @@ void TimelineSectionItem::updateFramesForTarget(QGraphicsItem *item, const Model } } +void TimelineSectionItem::updateHeightForTarget(QGraphicsItem *item, const ModelNode &target) +{ + if (!target.isValid()) + return; + + if (auto sectionItem = qgraphicsitem_cast<TimelineSectionItem *>(item)) { + if (sectionItem->targetNode() == target) + sectionItem->updateHeight(); + } +} + void TimelineSectionItem::moveAllFrames(qreal offset) { if (m_timeline.isValid()) @@ -313,7 +324,8 @@ void TimelineSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) if (event->button() == Qt::LeftButton) { event->accept(); - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } } @@ -345,7 +357,8 @@ void TimelineSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } update(); } @@ -414,6 +427,12 @@ void TimelineSectionItem::updateFrames() update(); } +void TimelineSectionItem::updateHeight() +{ + invalidateHeight(); + update(); +} + void TimelineSectionItem::invalidateHeight() { int height = 0; @@ -464,7 +483,8 @@ void TimelineSectionItem::invalidateFrames() bool TimelineSectionItem::collapsed() const { - return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded"); + return m_targetNode.isValid() + && (!m_targetNode.hasAuxiliaryData("timeline_expanded") || m_targetNode.locked()); } void TimelineSectionItem::createPropertyItems() @@ -549,9 +569,9 @@ void TimelineRulerSectionItem::invalidateRulerSize(const qreal length) m_end = length; } -void TimelineRulerSectionItem::setRulerScaleFactor(int scaling) +void TimelineRulerSectionItem::setZoom(int zoom) { - qreal blend = qreal(scaling) / 100.0; + qreal blend = qreal(zoom) / 100.0; qreal width = size().width() - qreal(TimelineConstants::sectionWidth); qreal duration = rulerDuration(); @@ -572,7 +592,7 @@ void TimelineRulerSectionItem::setRulerScaleFactor(int scaling) update(); } -int TimelineRulerSectionItem::getRulerScaleFactor() const +int TimelineRulerSectionItem::zoom() const { qreal width = size().width() - qreal(TimelineConstants::sectionWidth); qreal duration = rulerDuration(); @@ -589,7 +609,7 @@ int TimelineRulerSectionItem::getRulerScaleFactor() const qreal rcount = width / m_scaling; qreal rblend = TimelineUtils::reverseLerp(rcount, minCount, maxCount); - int rfactor = std::round(rblend * 100); + int rfactor = static_cast<int>(std::round(rblend * 100)); return TimelineUtils::clamp(rfactor, 0, 100); } @@ -766,7 +786,7 @@ void TimelineRulerSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event) { QGraphicsWidget::resizeEvent(event); - auto factor = getRulerScaleFactor(); + auto factor = zoom(); if (factor < 0) { if (event->oldSize().width() < event->newSize().width()) @@ -775,7 +795,7 @@ void TimelineRulerSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event) factor = 100; } - emit scaleFactorChanged(factor); + emit zoomChanged(factor); } void TimelineRulerSectionItem::setSizeHints(int width) @@ -845,6 +865,11 @@ void TimelineBarItem::commitPosition(const QPointF & /*point*/) m_oldRect = QRectF(); } +bool TimelineBarItem::isLocked() const +{ + return sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); +} + void TimelineBarItem::scrollOffsetChanged() { sectionItem()->invalidateBar(); @@ -904,7 +929,9 @@ void TimelineBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) const auto p = event->pos(); QRectF left, right; - if (handleRects(rect(), left, right)) { + if (isLocked() && rect().contains(p)) { + setCursor(QCursor(Qt::ForbiddenCursor)); + } else if (handleRects(rect(), left, right)) { if (left.contains(p) || right.contains(p)) { if (cursor().shape() != Qt::SizeHorCursor) setCursor(QCursor(Qt::SizeHorCursor)); @@ -920,6 +947,9 @@ void TimelineBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) void TimelineBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { + if (isLocked()) + return; + QMenu menu; QAction* overrideColor = menu.addAction(tr("Override Color")); diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h index e5403bcb74..956ef31ef7 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h @@ -50,6 +50,8 @@ public: void itemMoved(const QPointF &start, const QPointF &end) override; void commitPosition(const QPointF &point) override; + bool isLocked() const override; + protected: void scrollOffsetChanged() override; void paint(QPainter *painter, @@ -100,6 +102,7 @@ public: static void updateData(QGraphicsItem *item); static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b); static void updateFramesForTarget(QGraphicsItem *item, const ModelNode &target); + static void updateHeightForTarget(QGraphicsItem *item, const ModelNode &target); void moveAllFrames(qreal offset); void scaleAllFrames(qreal scale); @@ -121,6 +124,7 @@ protected: private: void updateData(); void updateFrames(); + void updateHeight(); void invalidateHeight(); void invalidateProperties(); void invalidateFrames(); @@ -145,7 +149,7 @@ class TimelineRulerSectionItem : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int scale); + void zoomChanged(int zoom); public: static TimelineRulerSectionItem *create(QGraphicsScene *parentScene, TimelineItem *parent); @@ -153,9 +157,9 @@ public: void invalidateRulerSize(const QmlTimeline &timeline); void invalidateRulerSize(const qreal length); - void setRulerScaleFactor(int scaling); + void setZoom(int zoom); - int getRulerScaleFactor() const; + int zoom() const; qreal getFrameTick() const; qreal rulerScaling() const; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp index d0679c80bb..dab580a146 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.cpp @@ -236,6 +236,18 @@ void TimelineView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeL m_timelineWidget->graphicsScene()->update(); } +void TimelineView::auxiliaryDataChanged(const ModelNode &modelNode, + const PropertyName &name, + const QVariant &data) +{ + if (name == QmlDesigner::lockedProperty && data.toBool() && modelNode.isValid()) { + for (const auto &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.hasAuxiliaryData("timeline_expanded")) + m_timelineWidget->graphicsScene()->invalidateHeightForTarget(node); + } + } +} + void TimelineView::propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) { for (const auto &property : propertyList) { diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h index fe3f5903ff..f1f39b6a35 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelineview.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelineview.h @@ -62,6 +62,9 @@ public: PropertyChangeFlags propertyChange) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) override; void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override; void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp index e740fb924e..6c7c15451d 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.cpp @@ -34,6 +34,7 @@ #include "timelinepropertyitem.h" #include "timelinetoolbar.h" #include "timelineview.h" +#include "navigation2d.h" #include <qmldesignerplugin.h> #include <qmlstate.h> @@ -60,6 +61,8 @@ #include <QtGlobal> #include <QSpacerItem> +#include <cmath> + namespace QmlDesigner { class Eventfilter : public QObject @@ -114,7 +117,7 @@ TimelineWidget::TimelineWidget(TimelineView *view) , m_toolbar(new TimelineToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Navigation2dScrollBar(this)) , m_statusBar(new QLabel(this)) , m_timelineView(view) , m_graphicsScene(new TimelineGraphicsScene(this)) @@ -153,6 +156,7 @@ TimelineWidget::TimelineWidget(TimelineView *view) m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); m_graphicsView->setLineWidth(0); + m_graphicsView->setVerticalScrollBar(new Navigation2dScrollBar); m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -247,6 +251,14 @@ TimelineWidget::TimelineWidget(TimelineView *view) connect(m_addButton, &QPushButton::clicked, this, [this]() { m_timelineView->addNewTimelineDialog(); }); + + Navigation2dFilter *filter = new Navigation2dFilter(this, m_scrollbar); + connect(filter, &Navigation2dFilter::zoomChanged, [this](double scale, const QPointF& pos) { + int s = static_cast<int>(std::round(scale*100.)); + double ps = m_graphicsScene->mapFromScene(pos.x()); + m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps); + }); + installEventFilter(filter); } void TimelineWidget::connectToolbar() @@ -258,8 +270,8 @@ void TimelineWidget::connectToolbar() connect(graphicsScene(), &TimelineGraphicsScene::scroll, this, &TimelineWidget::scroll); - auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; - connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setRulerScaling); + auto setZoomFactor = [this](int val) { m_graphicsScene->setZoom(val); }; + connect(m_toolbar, &TimelineToolBar::scaleFactorChanged, setZoomFactor); auto setToFirstFrame = [this]() { graphicsScene()->setCurrentFrame(graphicsScene()->startFrame()); @@ -428,7 +440,7 @@ void TimelineWidget::init() // setScaleFactor uses QSignalBlocker. m_toolbar->setScaleFactor(0); - m_graphicsScene->setRulerScaling(0); + m_graphicsScene->setZoom(0); } void TimelineWidget::reset() diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h index b546452e56..b1e24caf93 100644 --- a/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h +++ b/src/plugins/qmldesigner/components/timelineeditor/timelinewidget.h @@ -36,7 +36,6 @@ QT_FORWARD_DECLARE_CLASS(QComboBox) QT_FORWARD_DECLARE_CLASS(QGraphicsView) QT_FORWARD_DECLARE_CLASS(QLabel) QT_FORWARD_DECLARE_CLASS(QResizeEvent) -QT_FORWARD_DECLARE_CLASS(QScrollBar) QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) @@ -47,6 +46,7 @@ class TimelineToolBar; class TimelineView; class TimelineGraphicsScene; class QmlTimeline; +class Navigation2dScrollBar; class TimelineWidget : public QWidget { @@ -94,7 +94,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Navigation2dScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp index 02e1258dfd..5abff4d611 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp @@ -65,6 +65,11 @@ TransitionEditorGraphicsLayout::TransitionEditorGraphicsLayout(QGraphicsScene *s TransitionEditorGraphicsLayout::~TransitionEditorGraphicsLayout() = default; +int TransitionEditorGraphicsLayout::zoom() const +{ + return m_rulerItem->zoom(); +} + double TransitionEditorGraphicsLayout::rulerWidth() const { return m_rulerItem->preferredWidth(); @@ -133,7 +138,7 @@ void TransitionEditorGraphicsLayout::setTransition(const ModelNode &transition) if (auto *scene = timelineScene()) if (auto *view = scene->timelineView()) if (!transition.isValid() && view->isAttached()) - emit scaleFactorChanged(0); + emit zoomChanged(0); } void TransitionEditorGraphicsLayout::setDuration(qreal duration) @@ -141,9 +146,9 @@ void TransitionEditorGraphicsLayout::setDuration(qreal duration) m_rulerItem->invalidateRulerSize(duration); } -void TransitionEditorGraphicsLayout::setRulerScaleFactor(int factor) +void TransitionEditorGraphicsLayout::setZoom(int factor) { - m_rulerItem->setRulerScaleFactor(factor); + m_rulerItem->setZoom(factor); } void TransitionEditorGraphicsLayout::invalidate() diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h index 9362abffdf..67495db20d 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h @@ -44,7 +44,7 @@ class TransitionEditorGraphicsLayout : public TimelineItem signals: void rulerClicked(const QPointF &pos); - void scaleFactorChanged(int factor); + void zoomChanged(int factor); public: TransitionEditorGraphicsLayout(QGraphicsScene *scene, TimelineItem *parent = nullptr); @@ -52,6 +52,8 @@ public: ~TransitionEditorGraphicsLayout() override; public: + int zoom() const; + double rulerWidth() const; double rulerScaling() const; @@ -66,7 +68,7 @@ public: void setDuration(qreal duration); - void setRulerScaleFactor(int factor); + void setZoom(int factor); void invalidate(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp index 036fe173f5..c080a49314 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp @@ -104,9 +104,9 @@ TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWid auto changeScale = [this](int factor) { transitionEditorWidget()->changeScaleFactor(factor); - setRulerScaling(qreal(factor)); + setZoom(factor); }; - connect(m_layout, &TransitionEditorGraphicsLayout::scaleFactorChanged, changeScale); + connect(m_layout, &TransitionEditorGraphicsLayout::zoomChanged, changeScale); } TransitionEditorGraphicsScene::~TransitionEditorGraphicsScene() @@ -125,7 +125,7 @@ void TransitionEditorGraphicsScene::invalidateScrollbar() void TransitionEditorGraphicsScene::onShow() { - emit m_layout->scaleFactorChanged(0); + emit m_layout->zoomChanged(0); } void TransitionEditorGraphicsScene::setTransition(const ModelNode &transition) @@ -157,7 +157,12 @@ void TransitionEditorGraphicsScene::setDuration(int duration) m_transition.setAuxiliaryData("transitionDuration", duration); m_layout->setDuration(duration); qreal scaling = m_layout->rulerScaling(); - setRulerScaling(scaling); + setZoom(scaling); +} + +int TransitionEditorGraphicsScene::zoom() const +{ + return m_layout->zoom(); } qreal TransitionEditorGraphicsScene::rulerScaling() const @@ -199,9 +204,9 @@ qreal TransitionEditorGraphicsScene::mapFromScene(qreal x) const return xPosOffset / rulerScaling() + startFrame(); } -void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor) +void TransitionEditorGraphicsScene::setZoom(int scaleFactor) { - m_layout->setRulerScaleFactor(scaleFactor); + m_layout->setZoom(scaleFactor); setScrollOffset(0); invalidateSections(); @@ -209,6 +214,35 @@ void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor) update(); } +void TransitionEditorGraphicsScene::setZoom(int scaling, double pivot) +{ + const qreal oldOffset = scrollOffset(); + const qreal oldScaling = m_layout->rulerScaling(); + const qreal oldPosition = mapToScene(pivot); + m_layout->setZoom(scaling); + + const qreal newScaling = m_layout->rulerScaling(); + const qreal newPosition = mapToScene(pivot); + + const qreal newOffset = oldOffset + (newPosition - oldPosition); + + if (std::isinf(oldScaling) || std::isinf(newScaling)) + setScrollOffset(0); + else { + setScrollOffset(std::round(newOffset)); + + const qreal start = mapToScene(startFrame()); + const qreal head = TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset; + + if (start - head > 0) + setScrollOffset(0); + } + + invalidateSections(); + invalidateScrollbar(); + update(); +} + void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode &target) { if (!target.isValid()) @@ -218,7 +252,7 @@ void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode & const QList<QGraphicsItem *> items = m_layout->childItems(); for (auto child : items) - TimelineSectionItem::updateDataForTarget(child, target, &found); + TransitionEditorSectionItem::updateDataForTarget(child, target, &found); if (!found) invalidateScene(); @@ -227,6 +261,18 @@ void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode & invalidateLayout(); } +void TransitionEditorGraphicsScene::invalidateHeightForTarget(const ModelNode &target) +{ + if (!target.isValid()) + return; + + const auto children = m_layout->childItems(); + for (auto child : children) + TransitionEditorSectionItem::updateHeightForTarget(child, target); + + invalidateLayout(); +} + void TransitionEditorGraphicsScene::invalidateScene() { invalidateScrollbar(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h index 2f04c5b729..d0ade7064c 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h @@ -81,6 +81,7 @@ public: TransitionEditorWidget *transitionEditorWidget() const; TransitionEditorToolBar *toolBar() const; + int zoom() const override; qreal rulerScaling() const override; int rulerWidth() const override; qreal rulerDuration() const override; @@ -90,9 +91,11 @@ public: qreal mapToScene(qreal x) const; qreal mapFromScene(qreal x) const; - void setRulerScaling(int scaling); + void setZoom(int scaling); + void setZoom(int scaling, double pivot); void invalidateSectionForTarget(const ModelNode &modelNode); + void invalidateHeightForTarget(const ModelNode &modelNode); void invalidateScene(); void invalidateCurrentValues(); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp index 86442059d9..7172465e0d 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp @@ -196,6 +196,17 @@ void TransitionEditorSectionItem::updateData(QGraphicsItem *item) sectionItem->updateData(); } +void TransitionEditorSectionItem::updateHeightForTarget(QGraphicsItem *item, const ModelNode &target) +{ + if (!target.isValid()) + return; + + if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) { + if (sectionItem->targetNode() == target) + sectionItem->updateHeight(); + } +} + void TransitionEditorSectionItem::invalidateBar(QGraphicsItem *item) { if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) @@ -360,7 +371,8 @@ void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent if (event->button() == Qt::LeftButton) { event->accept(); - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } } @@ -392,7 +404,8 @@ void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *ev if (m_targetNode.isValid()) m_targetNode.view()->setSelectedModelNode(m_targetNode); } else { - toggleCollapsed(); + if (!ModelNode::isThisOrAncestorLocked(m_targetNode)) + toggleCollapsed(); } update(); } @@ -417,6 +430,12 @@ void TransitionEditorSectionItem::updateData() update(); } +void TransitionEditorSectionItem::updateHeight() +{ + invalidateHeight(); + update(); +} + const QList<QGraphicsItem *> TransitionEditorSectionItem::propertyItems() const { QList<QGraphicsItem *> list; @@ -488,7 +507,8 @@ void TransitionEditorSectionItem::invalidateProperties() bool TransitionEditorSectionItem::collapsed() const { - return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded"); + return m_targetNode.isValid() + && (!m_targetNode.hasAuxiliaryData("transition_expanded") || m_targetNode.locked()); } qreal TransitionEditorSectionItem::rulerWidth() const @@ -501,9 +521,9 @@ void TransitionEditorSectionItem::toggleCollapsed() QTC_ASSERT(m_targetNode.isValid(), return ); if (collapsed()) - m_targetNode.setAuxiliaryData("timeline_expanded", true); + m_targetNode.setAuxiliaryData("transition_expanded", true); else - m_targetNode.removeAuxiliaryData("timeline_expanded"); + m_targetNode.removeAuxiliaryData("transition_expanded"); invalidateHeight(); } @@ -592,6 +612,11 @@ void TransitionEditorBarItem::commitPosition(const QPointF & /*point*/) scrollOffsetChanged(); } +bool TransitionEditorBarItem::isLocked() const +{ + return sectionItem() && sectionItem()->targetNode().isValid() && sectionItem()->targetNode().locked(); +} + void TransitionEditorBarItem::scrollOffsetChanged() { if (sectionItem()) @@ -637,7 +662,9 @@ void TransitionEditorBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) const auto p = event->pos(); QRectF left, right; - if (handleRects(rect(), left, right)) { + if (isLocked() && rect().contains(p)) { + setCursor(QCursor(Qt::ForbiddenCursor)); + } else if (handleRects(rect(), left, right)) { if (left.contains(p) || right.contains(p)) { if (cursor().shape() != Qt::SizeHorCursor) setCursor(QCursor(Qt::SizeHorCursor)); diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h index d7ce78f56c..63828b52f5 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h @@ -54,6 +54,8 @@ public: void itemMoved(const QPointF &start, const QPointF &end) override; void commitPosition(const QPointF &point) override; + bool isLocked() const override; + protected: void scrollOffsetChanged() override; void paint(QPainter *painter, @@ -106,6 +108,7 @@ public: static void updateData(QGraphicsItem *item); static void invalidateBar(QGraphicsItem *item); static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b); + static void updateHeightForTarget(QGraphicsItem *item, const ModelNode &target); void moveAllDurations(qreal offset); void scaleAllDurations(qreal scale); @@ -125,6 +128,7 @@ protected: void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override; private: + void updateHeight(); void invalidateHeight(); void invalidateProperties(); bool collapsed() const; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp index ef83b17d90..e9cfb3fd72 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp @@ -142,6 +142,18 @@ void TransitionEditorView::selectedNodesChanged(const QList<ModelNode> & /*selec } +void TransitionEditorView::auxiliaryDataChanged(const ModelNode &modelNode, + const PropertyName &name, + const QVariant &data) +{ + if (name == QmlDesigner::lockedProperty && data.toBool() && modelNode.isValid()) { + for (const auto &node : modelNode.allSubModelNodesAndThisNode()) { + if (node.hasAuxiliaryData("transition_expanded")) + m_transitionEditorWidget->graphicsScene()->invalidateHeightForTarget(node); + } + } +} + void TransitionEditorView::propertiesAboutToBeRemoved( const QList<AbstractProperty> & /*propertyList */) { @@ -217,7 +229,7 @@ ModelNode TransitionEditorView::addNewTransition() QStringList newlist = idPropertyList.value(targetId); for (const QString &str :locList) if (!newlist.contains(str)) - newlist.append(str); + newlist.append(str); idPropertyList.insert(targetId, newlist); } else { if (!locList.isEmpty()) diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h index 1476a07353..857467e3cd 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h @@ -60,6 +60,9 @@ public: PropertyChangeFlags propertyChange) override; void selectedNodesChanged(const QList<ModelNode> &selectedNodeList, const QList<ModelNode> &lastSelectedNodeList) override; + void auxiliaryDataChanged(const ModelNode &node, + const PropertyName &name, + const QVariant &data) override; void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override; void propertiesRemoved(const QList<AbstractProperty> &propertyList) override; diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp index 3d7e4ec421..b2cb67c988 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp @@ -29,6 +29,7 @@ #include "transitioneditorpropertyitem.h" #include "transitioneditortoolbar.h" #include "transitioneditorview.h" +#include "navigation2d.h" #include <timelineeditor/easingcurvedialog.h> #include <timelineeditor/timelineconstants.h> @@ -63,6 +64,8 @@ #include <QVBoxLayout> #include <QtGlobal> +#include <cmath> + namespace QmlDesigner { class Eventfilter : public QObject @@ -87,7 +90,7 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) , m_toolbar(new TransitionEditorToolBar(this)) , m_rulerView(new QGraphicsView(this)) , m_graphicsView(new QGraphicsView(this)) - , m_scrollbar(new QScrollBar(this)) + , m_scrollbar(new Navigation2dScrollBar(this)) , m_statusBar(new QLabel(this)) , m_transitionEditorView(view) , m_graphicsScene(new TransitionEditorGraphicsScene(this)) @@ -126,6 +129,7 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) m_graphicsView->setFrameShape(QFrame::NoFrame); m_graphicsView->setFrameShadow(QFrame::Plain); m_graphicsView->setLineWidth(0); + m_graphicsView->setVerticalScrollBar(new Navigation2dScrollBar); m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -218,6 +222,14 @@ TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view) connect(m_addButton, &QPushButton::clicked, this, [this]() { m_transitionEditorView->addNewTransition(); }); + + Navigation2dFilter *filter = new Navigation2dFilter(this, m_scrollbar); + connect(filter, &Navigation2dFilter::zoomChanged, [this](double scale, const QPointF& pos) { + int s = static_cast<int>(std::round(scale*100.)); + double ps = m_graphicsScene->mapFromScene(pos.x()); + m_graphicsScene->setZoom(std::clamp(m_graphicsScene->zoom() + s, 0, 100), ps); + }); + installEventFilter(filter); } void TransitionEditorWidget::setTransitionActive(bool b) @@ -258,7 +270,7 @@ void TransitionEditorWidget::connectToolbar() this, &TransitionEditorWidget::scroll); - auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); }; + auto setRulerScaling = [this](int val) { m_graphicsScene->setZoom(val); }; connect(m_toolbar, &TransitionEditorToolBar::scaleFactorChanged, setRulerScaling); auto setDuration = [this](int end) { graphicsScene()->setDuration(end); }; @@ -335,7 +347,7 @@ void TransitionEditorWidget::init() m_toolbar->setDuration(duration); - m_graphicsScene->setRulerScaling(40); + m_graphicsScene->setZoom(40); } void TransitionEditorWidget::updateData(const ModelNode &transition) diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h index dbbe87a1ff..f1c4174418 100644 --- a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h +++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h @@ -37,7 +37,6 @@ QT_FORWARD_DECLARE_CLASS(QComboBox) QT_FORWARD_DECLARE_CLASS(QGraphicsView) QT_FORWARD_DECLARE_CLASS(QLabel) QT_FORWARD_DECLARE_CLASS(QResizeEvent) -QT_FORWARD_DECLARE_CLASS(QScrollBar) QT_FORWARD_DECLARE_CLASS(QShowEvent) QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QPushButton) @@ -48,6 +47,7 @@ class TransitionEditorView; class TransitionEditorToolBar; class TransitionEditorGraphicsScene; class ModelNode; +class Navigation2dScrollBar; class TransitionEditorWidget : public QWidget { @@ -88,7 +88,7 @@ private: QGraphicsView *m_graphicsView = nullptr; - QScrollBar *m_scrollbar = nullptr; + Navigation2dScrollBar *m_scrollbar = nullptr; QLabel *m_statusBar = nullptr; 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/abstractview.h b/src/plugins/qmldesigner/designercore/include/abstractview.h index 12cb457571..6b0276c8b1 100644 --- a/src/plugins/qmldesigner/designercore/include/abstractview.h +++ b/src/plugins/qmldesigner/designercore/include/abstractview.h @@ -147,6 +147,7 @@ public: void setSelectedModelNodes(const QList<ModelNode> &selectedNodeList); void setSelectedModelNode(const ModelNode &modelNode); + void selectModelNode(const ModelNode &node); void deselectModelNode(const ModelNode &node); void clearSelectedModelNodes(); 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/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 4d97eb8694..20611736e9 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -43,6 +43,7 @@ namespace QmlDesigner { namespace Internal { class ModelPrivate; class WriteLocker; +class NodeMetaInfoPrivate; } //Internal class AnchorLine; @@ -68,6 +69,7 @@ class QMLDESIGNERCORE_EXPORT Model : public QObject friend class QmlDesigner::AbstractView; friend class Internal::ModelPrivate; friend class Internal::WriteLocker; + friend class QmlDesigner::Internal::NodeMetaInfoPrivate; Q_OBJECT @@ -118,6 +120,8 @@ public: QList<ModelNode> selectedNodes(AbstractView *view) const; + void clearMetaInfoCache(); + protected: Model(); diff --git a/src/plugins/qmldesigner/designercore/include/modelnode.h b/src/plugins/qmldesigner/designercore/include/modelnode.h index 3197e746ab..19bed05a25 100644 --- a/src/plugins/qmldesigner/designercore/include/modelnode.h +++ b/src/plugins/qmldesigner/designercore/include/modelnode.h @@ -65,7 +65,9 @@ QMLDESIGNERCORE_EXPORT QList<Internal::InternalNodePointer> toInternalNodeList(c using PropertyListType = QList<QPair<PropertyName, QVariant> >; -class QMLDESIGNERCORE_EXPORT ModelNode +static const PropertyName lockedProperty = {("locked")}; + +class QMLDESIGNERCORE_EXPORT ModelNode { friend QMLDESIGNERCORE_EXPORT bool operator ==(const ModelNode &firstNode, const ModelNode &secondNode); friend QMLDESIGNERCORE_EXPORT bool operator !=(const ModelNode &firstNode, const ModelNode &secondNode); @@ -216,6 +218,11 @@ public: void setGlobalStatus(const GlobalAnnotationStatus &status); void removeGlobalStatus(); + bool locked() const; + void setLocked(bool value); + + static bool isThisOrAncestorLocked(const ModelNode &node); + qint32 internalId() const; void setNodeSource(const QString&); @@ -241,6 +248,8 @@ public: private: // functions Internal::InternalNodePointer internalNode() const; + void removeLocked(); + bool hasLocked() const; private: // variables Internal::InternalNodePointer m_internalNode; 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/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h index b5e2e7626c..869d327f5f 100644 --- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h +++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h @@ -107,8 +107,6 @@ public: QString importDirectoryPath() const; - static void clearCache(); - private: QSharedPointer<Internal::NodeMetaInfoPrivate> m_privateData; }; 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/include/rewriterview.h b/src/plugins/qmldesigner/designercore/include/rewriterview.h index 0601da3acf..5eae2f221f 100644 --- a/src/plugins/qmldesigner/designercore/include/rewriterview.h +++ b/src/plugins/qmldesigner/designercore/include/rewriterview.h @@ -190,7 +190,6 @@ protected: // functions private: //variables ModelNode nodeAtTextCursorPositionHelper(const ModelNode &root, int cursorPosition) const; void setupCanonicalHashes() const; - void handleLibraryInfoUpdate(); TextModifier *m_textModifier = nullptr; int transactionLevel = 0; @@ -211,7 +210,6 @@ private: //variables std::function<void(bool)> m_setWidgetStatusCallback; bool m_hasIncompleteTypeInformation = false; bool m_restoringAuxData = false; - bool m_modelAttachPending = false; mutable QHash<int, ModelNode> m_canonicalIntModelNode; mutable QHash<ModelNode, int> m_canonicalModelNodeInt; 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 2f85a9e1a4..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( @@ -534,11 +528,11 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &value) { - if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible") + if (((node.isRootNode() && (name == "width" || name == "height")) || name == "invisible" || name == "locked") || name.endsWith(PropertyName("@NodeInstance"))) { if (hasInstanceForModelNode(node)) { NodeInstance instance = instanceForModelNode(node); - if (value.isValid() || name == "invisible") { + if (value.isValid() || name == "invisible" || name == "locked") { PropertyValueContainer container{instance.instanceId(), name, value, TypeName()}; m_nodeInstanceServer->changeAuxiliaryValues({{container}}); } else { @@ -1496,7 +1490,15 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand if (hasModelNodeForInternalId(container.instanceId()) && !image.isNull()) { auto node = modelNodeForInternalId(container.instanceId()); if (node.isValid()) { - image.setDevicePixelRatio(2.); +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + if (image.height() != dim || image.width() != dim) + image = image.scaled(dim, dim, Qt::KeepAspectRatio); + image.setDevicePixelRatio(ratio); updatePreviewImageForNode(node, image); } } @@ -1540,12 +1542,15 @@ void NodeInstanceView::requestModelNodePreviewImage(const ModelNode &node, const } else if (node.isComponent()) { componentPath = node.metaInfo().componentFileName(); } +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; m_nodeInstanceServer->requestModelNodePreviewImage( - RequestModelNodePreviewImageCommand( - instance.instanceId(), - QSize(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS), - componentPath, renderItemId)); + RequestModelNodePreviewImageCommand(instance.instanceId(), QSize(dim, dim), + componentPath, renderItemId)); } } } @@ -1587,6 +1592,11 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo ModelNodePreviewImageData imageData; imageData.id = modelNode.id(); imageData.type = QString::fromLatin1(modelNode.type()); +#ifndef QMLDESIGNER_TEST + const double ratio = QmlDesignerPlugin::formEditorDevicePixelRatio(); +#else + const double ratio = 1; +#endif if (imageSource.isEmpty() && modelNode.isSubclassOf("QtQuick3D.Texture")) { // Texture node may have sourceItem instead @@ -1601,11 +1611,10 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo return previewImageDataForGenericNode(modelNode, boundNode); } else { QmlItemNode itemNode(boundNode); - imageData.pixmap = itemNode.instanceRenderPixmap().scaled( - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Qt::KeepAspectRatio); - imageData.pixmap.setDevicePixelRatio(2.); + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + imageData.pixmap = itemNode.instanceRenderPixmap().scaled(dim, dim, Qt::KeepAspectRatio); + imageData.pixmap.setDevicePixelRatio(ratio); + } imageData.info = QObject::tr("Source item: %1").arg(boundNode.id()); } @@ -1629,10 +1638,9 @@ QVariant NodeInstanceView::previewImageDataForImageNode(const ModelNode &modelNo QPixmap originalPixmap; originalPixmap.load(imageSource); if (!originalPixmap.isNull()) { - imageData.pixmap = originalPixmap.scaled(Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * 2, - Qt::KeepAspectRatio); - imageData.pixmap.setDevicePixelRatio(2.); + const int dim = Constants::MODELNODE_PREVIEW_IMAGE_DIMENSIONS * ratio; + imageData.pixmap = originalPixmap.scaled(dim, dim, Qt::KeepAspectRatio); + imageData.pixmap.setDevicePixelRatio(ratio); double imgSize = double(imageFi.size()); static QStringList units({QObject::tr("B"), QObject::tr("KB"), QObject::tr("MB"), QObject::tr("GB")}); 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/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp index afd2aaa9a5..e6a61800e6 100644 --- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp +++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp @@ -25,6 +25,7 @@ #include "nodemetainfo.h" #include "model.h" +#include "model/model_p.h" #include "metainfo.h" #include <enumeration.h> @@ -620,8 +621,6 @@ public: QSet<QByteArray> &prototypeCachePositives(); QSet<QByteArray> &prototypeCacheNegatives(); - static void clearCache(); - private: NodeMetaInfoPrivate(Model *model, TypeName type, int maj = -1, int min = -1); @@ -657,13 +656,10 @@ private: const Document *document() const; QPointer<Model> m_model; - static QHash<TypeName, Pointer> m_nodeMetaInfoCache; const ObjectValue *m_objectValue = nullptr; bool m_propertiesSetup = false; }; -QHash<TypeName, NodeMetaInfoPrivate::Pointer> NodeMetaInfoPrivate::m_nodeMetaInfoCache; - bool NodeMetaInfoPrivate::isFileComponent() const { return m_isFileComponent; @@ -705,11 +701,6 @@ QSet<QByteArray> &NodeMetaInfoPrivate::prototypeCacheNegatives() return m_prototypeCacheNegatives; } -void NodeMetaInfoPrivate::clearCache() -{ - m_nodeMetaInfoCache.clear(); -} - PropertyName NodeMetaInfoPrivate::defaultPropertyName() const { if (!m_defaultPropertyName.isEmpty()) @@ -724,17 +715,12 @@ static inline TypeName stringIdentifier( const TypeName &type, int maj, int min) NodeMetaInfoPrivate::Pointer NodeMetaInfoPrivate::create(Model *model, const TypeName &type, int major, int minor) { - if (m_nodeMetaInfoCache.contains(stringIdentifier(type, major, minor))) { - const Pointer &info = m_nodeMetaInfoCache.value(stringIdentifier(type, major, minor)); - if (info->model() == model) - return info; - else - m_nodeMetaInfoCache.clear(); - } + if (model->d->m_nodeMetaInfoCache.contains(stringIdentifier(type, major, minor))) + return model->d->m_nodeMetaInfoCache.value(stringIdentifier(type, major, minor)); Pointer newData(new NodeMetaInfoPrivate(model, type, major, minor)); if (newData->isValid()) - m_nodeMetaInfoCache.insert(stringIdentifier(type, major, minor), newData); + model->d->m_nodeMetaInfoCache.insert(stringIdentifier(type, major, minor), newData); return newData; } @@ -1671,11 +1657,6 @@ bool NodeMetaInfo::isQmlItem() const || isSubclassOf("QtQml.QtObject"); } -void NodeMetaInfo::clearCache() -{ - Internal::NodeMetaInfoPrivate::clearCache(); -} - bool NodeMetaInfo::isLayoutable() const { if (isSubclassOf("<cpp>.QDeclarativeBasePositioner")) diff --git a/src/plugins/qmldesigner/designercore/model/abstractview.cpp b/src/plugins/qmldesigner/designercore/model/abstractview.cpp index d7d512334d..6a1a8d4897 100644 --- a/src/plugins/qmldesigner/designercore/model/abstractview.cpp +++ b/src/plugins/qmldesigner/designercore/model/abstractview.cpp @@ -35,6 +35,7 @@ #ifndef QMLDESIGNER_TEST #include <qmldesignerplugin.h> #include <viewmanager.h> +#include <nodeabstractproperty.h> #endif #include <coreplugin/helpmanager.h> @@ -397,7 +398,7 @@ QList<ModelNode> AbstractView::toModelNodeList(const QList<Internal::InternalNod QList<ModelNode> toModelNodeList(const QList<Internal::InternalNode::Pointer> &nodeList, AbstractView *view) { QList<ModelNode> newNodeList; - foreach (const Internal::InternalNode::Pointer &node, nodeList) + for (const Internal::InternalNode::Pointer &node : nodeList) newNodeList.append(ModelNode(node, view->model(), view)); return newNodeList; @@ -406,7 +407,7 @@ QList<ModelNode> toModelNodeList(const QList<Internal::InternalNode::Pointer> &n QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode> &nodeList) { QList<Internal::InternalNode::Pointer> newNodeList; - foreach (const ModelNode &node, nodeList) + for (const ModelNode &node : nodeList) newNodeList.append(node.internalNode()); return newNodeList; @@ -414,15 +415,26 @@ QList<Internal::InternalNode::Pointer> toInternalNodeList(const QList<ModelNode> /*! Sets the list of nodes to the actual selected nodes specified by - \a selectedNodeList. + \a selectedNodeList if the node or its ancestors are not locked. */ void AbstractView::setSelectedModelNodes(const QList<ModelNode> &selectedNodeList) { - model()->d->setSelectedNodes(toInternalNodeList(selectedNodeList)); + QList<ModelNode> unlockedNodes; + + for (const auto &modelNode : selectedNodeList) { + if (!ModelNode::isThisOrAncestorLocked(modelNode)) + unlockedNodes.push_back(modelNode); + } + + model()->d->setSelectedNodes(toInternalNodeList(unlockedNodes)); } void AbstractView::setSelectedModelNode(const ModelNode &modelNode) { + if (ModelNode::isThisOrAncestorLocked(modelNode)) { + clearSelectedModelNodes(); + return; + } setSelectedModelNodes({modelNode}); } 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/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index f31aaeecdc..aed9ae788c 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -161,7 +161,7 @@ void ModelPrivate::notifyImportsChanged(const QList<Import> &addedImports, const resetModel = true; } - NodeMetaInfo::clearCache(); + m_nodeMetaInfoCache.clear(); if (nodeInstanceView()) nodeInstanceView()->importsChanged(addedImports, removedImports); @@ -2080,6 +2080,11 @@ QList<ModelNode> Model::selectedNodes(AbstractView *view) const return d->toModelNodeList(d->selectedNodes(), view); } +void Model::clearMetaInfoCache() +{ + d->m_nodeMetaInfoCache.clear(); +} + /*! \brief Returns the URL against which relative URLs within the model should be resolved. \return The base URL. diff --git a/src/plugins/qmldesigner/designercore/model/model_p.h b/src/plugins/qmldesigner/designercore/model/model_p.h index 5a8dced512..4874959759 100644 --- a/src/plugins/qmldesigner/designercore/model/model_p.h +++ b/src/plugins/qmldesigner/designercore/model/model_p.h @@ -85,6 +85,7 @@ class ModelPrivate : public QObject { friend class QmlDesigner::Model; friend class QmlDesigner::Internal::WriteLocker; + friend class QmlDesigner::Internal::NodeMetaInfoPrivate; public: ModelPrivate(Model *model); @@ -268,6 +269,7 @@ private: QPointer<NodeInstanceView> m_nodeInstanceView; QPointer<TextModifier> m_textModifier; QPointer<Model> m_metaInfoProxyModel; + QHash<TypeName, QSharedPointer<NodeMetaInfoPrivate>> m_nodeMetaInfoCache; bool m_writeLock; qint32 m_internalIdCounter; }; diff --git a/src/plugins/qmldesigner/designercore/model/modelnode.cpp b/src/plugins/qmldesigner/designercore/model/modelnode.cpp index 5c1ffce639..688dae2d07 100644 --- a/src/plugins/qmldesigner/designercore/model/modelnode.cpp +++ b/src/plugins/qmldesigner/designercore/model/modelnode.cpp @@ -1228,6 +1228,53 @@ void ModelNode::removeGlobalStatus() } } +bool ModelNode::locked() const +{ + if (hasLocked()) + return auxiliaryData(lockedProperty).toBool(); + + return false; +} + +bool ModelNode::hasLocked() const +{ + return hasAuxiliaryData(lockedProperty); +} + +void ModelNode::setLocked(bool value) +{ + setAuxiliaryData(lockedProperty, value); + + if (value) { + // Remove newly locked node and all its descendants from potential selection + for (ModelNode node : allSubModelNodesAndThisNode()) { + node.deselectNode(); + node.removeAuxiliaryData("timeline_expanded"); + node.removeAuxiliaryData("transition_expanded"); + } + } +} + +void ModelNode::removeLocked() +{ + if (hasLocked()) + removeAuxiliaryData(lockedProperty); +} + +bool ModelNode::isThisOrAncestorLocked(const ModelNode &node) +{ + if (!node.isValid()) + return false; + + if (node.locked()) + return true; + + if (node.isRootNode() || !node.hasParentProperty()) + return false; + + return isThisOrAncestorLocked(node.parentProperty().parentModelNode()); +} + void ModelNode::setScriptFunctions(const QStringList &scriptFunctionList) { model()->d->setScriptFunctions(internalNode(), scriptFunctionList); 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/designercore/model/rewriteaction.cpp b/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp index 106341043d..2b12f03b2b 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriteaction.cpp @@ -181,8 +181,10 @@ bool ChangePropertyRewriteAction::execute(QmlRefactoring &refactoring, ModelNode { if (m_sheduledInHierarchy) { const int nodeLocation = positionStore.nodeOffset(m_property.parentModelNode()); - if (nodeLocation < 0) + if (nodeLocation < 0) { + qWarning() << "*** ChangePropertyRewriteAction::execute ignored. Invalid node location"; return true; + } bool result = false; if (m_property.isDefaultProperty()) { diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp index f7a1254035..86b561548e 100644 --- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp +++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp @@ -72,12 +72,7 @@ RewriterView::RewriterView(DifferenceHandling differenceHandling, QObject *paren m_textToModelMerger(new Internal::TextToModelMerger(this)) { m_amendTimer.setSingleShot(true); - m_amendTimer.setInterval(400); connect(&m_amendTimer, &QTimer::timeout, this, &RewriterView::amendQmlText); - - QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); - connect(modelManager, &QmlJS::ModelManagerInterface::libraryInfoUpdated, - this, &RewriterView::handleLibraryInfoUpdate, Qt::QueuedConnection); } RewriterView::~RewriterView() = default; @@ -94,8 +89,6 @@ Internal::TextToModelMerger *RewriterView::textToModelMerger() const void RewriterView::modelAttached(Model *model) { - m_modelAttachPending = false; - if (model && model->textModifier()) setTextModifier(model->textModifier()); @@ -109,12 +102,10 @@ void RewriterView::modelAttached(Model *model) if (!(m_errors.isEmpty() && m_warnings.isEmpty())) notifyErrorsAndWarnings(m_errors); - if (hasIncompleteTypeInformation()) { - m_modelAttachPending = true; + if (hasIncompleteTypeInformation()) QTimer::singleShot(1000, this, [this, model](){ modelAttached(model); }); - } } void RewriterView::modelAboutToBeDetached(Model * /*model*/) @@ -812,13 +803,6 @@ void RewriterView::setupCanonicalHashes() const } } -void RewriterView::handleLibraryInfoUpdate() -{ - // Trigger dummy amend to reload document when library info changes - if (isAttached() && !m_modelAttachPending) - m_amendTimer.start(); -} - ModelNode RewriterView::nodeAtTextCursorPosition(int cursorPosition) const { return nodeAtTextCursorPositionHelper(rootModelNode(), cursorPosition); @@ -1021,7 +1005,7 @@ void RewriterView::qmlTextChanged() auto &viewManager = QmlDesignerPlugin::instance()->viewManager(); if (viewManager.usesRewriterView(this)) { QmlDesignerPlugin::instance()->viewManager().disableWidgets(); - m_amendTimer.start(); + m_amendTimer.start(400); } #else /*Keep test synchronous*/ diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp index 61f8d5e0c3..708d120ba5 100644 --- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp +++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp @@ -989,7 +989,7 @@ bool TextToModelMerger::load(const QString &data, DifferenceHandler &differenceH m_rewriterView->setIncompleteTypeInformation(false); // maybe the project environment (kit, ...) changed, so we need to clean old caches - NodeMetaInfo::clearCache(); + m_rewriterView->model()->clearMetaInfoCache(); try { Snapshot snapshot = m_rewriterView->textModifier()->qmljsSnapshot(); @@ -1119,8 +1119,15 @@ void TextToModelMerger::syncNode(ModelNode &modelNode, differenceHandler.typeDiffers(isRootNode, modelNode, typeName, majorVersion, minorVersion, astNode, context); - if (!isRootNode) + + if (!modelNode.isValid()) + return; + + if (!isRootNode && modelNode.majorVersion() != -1 && modelNode.minorVersion() != -1) { + qWarning() << "Preempting Node sync. Type differs" << modelNode << + modelNode.majorVersion() << modelNode.minorVersion(); return; // the difference handler will create a new node, so we're done. + } } if (isComponentType(typeName) || isImplicitComponent) 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 ea57599bd0..8dc0933903 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", ] } @@ -446,6 +462,8 @@ Project { "componentcore/modelnodecontextmenu_helper.h", "componentcore/modelnodeoperations.cpp", "componentcore/modelnodeoperations.h", + "componentcore/navigation2d.cpp", + "componentcore/navigation2d.h", "componentcore/selectioncontext.cpp", "componentcore/selectioncontext.h", "componentcore/qmldesignericonprovider.cpp", @@ -600,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", @@ -791,6 +811,11 @@ Project { "pathtool/pathtool.h", "pathtool/pathtoolview.cpp", "pathtool/pathtoolview.h", + "previewtooltip/previewimagetooltip.cpp", + "previewtooltip/previewimagetooltip.h", + "previewtooltip/previewimagetooltip.ui", + "previewtooltip/previewtooltipbackend.cpp", + "previewtooltip/previewtooltipbackend.h", "richtexteditor/hyperlinkdialog.cpp", "richtexteditor/hyperlinkdialog.h", "richtexteditor/hyperlinkdialog.ui", 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/plugins/qmlpreview/qmldebugtranslationwidget.cpp b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp index d1c7c9f289..086ef82fc9 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.cpp @@ -38,6 +38,7 @@ #include <utils/outputformatter.h> #include <utils/utilsicons.h> #include <utils/fileutils.h> +#include <utils/qtcolorbutton.h> #include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginspec.h> @@ -94,6 +95,11 @@ namespace QmlPreview { QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent, TestLanguageGetter languagesGetterMethod) : QWidget(parent) , m_testLanguagesGetter(languagesGetterMethod) + , m_warningColor(Qt::red) + //, m_foundTrColor(Qt::green) // invalid color -> init without the frame + , m_lastWarningColor(m_warningColor) + , m_lastfoundTrColor(Qt::green) + { auto mainLayout = new QVBoxLayout(this); @@ -121,12 +127,54 @@ QmlDebugTranslationWidget::QmlDebugTranslationWidget(QWidget *parent, TestLangua m_selectLanguageLayout = new QHBoxLayout; mainLayout->addLayout(m_selectLanguageLayout); + auto settingsLayout = new QHBoxLayout(); + mainLayout->addLayout(settingsLayout); + auto elideWarningCheckBox = new QCheckBox(tr("Enable elide warning")); - layout()->addWidget(elideWarningCheckBox); connect(elideWarningCheckBox, &QCheckBox::stateChanged, [this] (int state) { m_elideWarning = (state == Qt::Checked); - }); + settingsLayout->addWidget(elideWarningCheckBox); + + auto warningColorCheckbox = new QCheckBox(tr("select Warning color: ")); + settingsLayout->addWidget(warningColorCheckbox); + auto warningColorButton = new Utils::QtColorButton(); + connect(warningColorCheckbox, &QCheckBox::stateChanged, [warningColorButton, this] (int state) { + if (state == Qt::Checked) { + warningColorButton->setColor(m_lastWarningColor); + warningColorButton->setEnabled(true); + } else { + m_lastWarningColor = warningColorButton->color(); + warningColorButton->setColor({}); + warningColorButton->setEnabled(false); + } + }); + connect(warningColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { + m_warningColor = color; + }); + warningColorCheckbox->setCheckState(Qt::Checked); + settingsLayout->addWidget(warningColorButton); + + auto foundTrColorCheckbox = new QCheckBox(tr("select found 'tr' color: ")); + settingsLayout->addWidget(foundTrColorCheckbox); + auto foundTrColorButton = new Utils::QtColorButton(); + foundTrColorButton->setDisabled(true); + connect(foundTrColorCheckbox, &QCheckBox::stateChanged, [foundTrColorButton, this] (int state) { + if (state == Qt::Checked) { + foundTrColorButton->setColor(m_lastfoundTrColor); + foundTrColorButton->setEnabled(true); + } else { + m_lastfoundTrColor = foundTrColorButton->color(); + foundTrColorButton->setColor({}); + foundTrColorButton->setEnabled(false); + } + }); + connect(foundTrColorButton, &Utils::QtColorButton::colorChanged, [this](const QColor &color) { + m_foundTrColor = color; + }); + settingsLayout->addWidget(foundTrColorButton); + + settingsLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding)); auto controlLayout = new QHBoxLayout; mainLayout->addLayout(controlLayout); @@ -232,6 +280,16 @@ void QmlDebugTranslationWidget::updateStartupProjectTranslations() updateCurrentTranslations(ProjectExplorer::SessionManager::startupProject()); } +QColor QmlDebugTranslationWidget::warningColor() +{ + return m_warningColor; +} + +QColor QmlDebugTranslationWidget::foundTrColor() +{ + return m_foundTrColor; +} + void QmlDebugTranslationWidget::updateCurrentTranslations(ProjectExplorer::Project *project) { m_testLanguages.clear(); diff --git a/src/plugins/qmlpreview/qmldebugtranslationwidget.h b/src/plugins/qmlpreview/qmldebugtranslationwidget.h index 7ea0760ac0..de0c7ba362 100644 --- a/src/plugins/qmlpreview/qmldebugtranslationwidget.h +++ b/src/plugins/qmlpreview/qmldebugtranslationwidget.h @@ -63,6 +63,9 @@ public: void setCurrentFile(const Utils::FilePath &filepath); void setFiles(const Utils::FilePaths &filePathes); void updateStartupProjectTranslations(); + + QColor warningColor(); + QColor foundTrColor(); private: void updateCurrentEditor(const Core::IEditor *editor); void updateCurrentTranslations(ProjectExplorer::Project *project); @@ -98,6 +101,10 @@ private: QHBoxLayout *m_selectLanguageLayout; TestLanguageGetter m_testLanguagesGetter; + QColor m_warningColor; + QColor m_foundTrColor; + QColor m_lastWarningColor; + QColor m_lastfoundTrColor; }; } // namespace QmlPreview diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp index 0fdd28182c..516b82ac72 100644 --- a/src/plugins/texteditor/texteditor.cpp +++ b/src/plugins/texteditor/texteditor.cpp @@ -4745,7 +4745,8 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data, o.start = ts.positionAtColumn(text, m_blockSelection.firstVisualColumn()); o.length = ts.positionAtColumn(text, m_blockSelection.lastVisualColumn()) - o.start; } - if (data.textCursor.hasSelection() && data.textCursor == range.cursor) { + if (data.textCursor.hasSelection() && data.textCursor == range.cursor + && data.textCursor.anchor() == range.cursor.anchor()) { const QTextCharFormat selectionFormat = data.fontSettings.toTextCharFormat(C_SELECTION); if (selectionFormat.background().style() != Qt::NoBrush) o.format.setBackground(selectionFormat.background()); diff --git a/src/shared/proparser/qmakebuiltins.cpp b/src/shared/proparser/qmakebuiltins.cpp index c74ff4faf0..9288d9e887 100644 --- a/src/shared/proparser/qmakebuiltins.cpp +++ b/src/shared/proparser/qmakebuiltins.cpp @@ -931,8 +931,10 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinExpand( else if (!m_tmp2.compare(QLatin1String("lines"), Qt::CaseInsensitive)) lines = true; } - int exitCode; - QByteArray bytes = getCommandOutput(args.at(0).toQString(), &exitCode); + int exitCode = 0; + QByteArray bytes; + if (m_option->runSystemFunction) + bytes = getCommandOutput(args.at(0).toQString(), &exitCode); if (args.count() > 2 && !args.at(2).isEmpty()) { m_valuemapStack.top()[args.at(2).toKey()] = ProStringList(ProString(QString::number(exitCode))); @@ -1778,6 +1780,8 @@ QMakeEvaluator::VisitReturn QMakeEvaluator::evaluateBuiltinConditional( evalError(fL1S("system(exec) requires one argument.")); return ReturnFalse; } + if (!m_option->runSystemFunction) + return ReturnTrue; if (m_cumulative) // Anything else would be insanity return ReturnFalse; #ifndef QT_BOOTSTRAPPED diff --git a/src/shared/proparser/qmakeglobals.h b/src/shared/proparser/qmakeglobals.h index 96e2299214..c8efbb2252 100644 --- a/src/shared/proparser/qmakeglobals.h +++ b/src/shared/proparser/qmakeglobals.h @@ -110,6 +110,7 @@ public: QString qmakespec, xqmakespec; QString user_template, user_template_prefix; QString extra_cmds[4]; + bool runSystemFunction = false; #ifdef PROEVALUATOR_DEBUG int debugLevel; 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 { |