aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEike Ziller <eike.ziller@qt.io>2020-06-29 09:01:31 +0200
committerEike Ziller <eike.ziller@qt.io>2020-06-29 09:01:31 +0200
commit40a14070d38476724b35c2217370e3367d036cfa (patch)
tree28791c338325615b0871d7004fafd93904bfae16
parent569ad988525e29140b47d7338468bd60cac37614 (diff)
parent018e4aa19755713f1fdb628c5fc6281f35b5d6ac (diff)
Merge remote-tracking branch 'origin/4.13'
-rw-r--r--doc/qtcreator/src/android/androiddev.qdoc8
-rw-r--r--doc/qtcreator/src/cmake/creator-projects-cmake.qdoc2
-rw-r--r--doc/qtcreator/src/external-resources/external-resources.qdoc8
-rw-r--r--share/qtcreator/qml/qmlpuppet/commands/createscenecommand.cpp16
-rw-r--r--share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h24
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml2
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml2
-rw-r--r--share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml2
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp26
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h1
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp1
-rw-r--r--share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri4
-rw-r--r--src/libs/qmldebug/qmldebugcommandlinearguments.h6
-rw-r--r--src/libs/qmljs/qmljscheck.cpp1
-rw-r--r--src/libs/sqlite/sqlitebasestatement.cpp116
-rw-r--r--src/libs/sqlite/sqlitebasestatement.h12
-rw-r--r--src/libs/sqlite/sqliteexception.h92
-rw-r--r--src/plugins/android/androidsettingswidget.cpp7
-rw-r--r--src/plugins/clangformat/clangformatplugin.cpp9
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt39
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp190
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h82
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.ui65
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp370
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporter.h111
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp130
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h56
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json6
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo2
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri37
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pro17
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs59
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc5
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp161
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h86
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h78
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp119
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/componentexporter.h93
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp58
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/exportnotification.h33
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp163
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h60
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp66
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h42
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp65
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.h44
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.cpp41
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.h58
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp87
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.h43
-rw-r--r--src/plugins/qmldesigner/components/componentcore/componentcore_constants.h8
-rw-r--r--src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp21
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp17
-rw-r--r--src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h2
-rw-r--r--src/plugins/qmldesigner/components/componentcore/zoomaction.cpp22
-rw-r--r--src/plugins/qmldesigner/components/componentcore/zoomaction.h3
-rw-r--r--src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp3
-rw-r--r--src/plugins/qmldesigner/components/edit3d/edit3dview.cpp2
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp26
-rw-r--r--src/plugins/qmldesigner/components/formeditor/formeditorview.cpp39
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.cpp4
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp6
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h10
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp50
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h98
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp42
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineitem.h4
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp14
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h4
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp4
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp21
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h1
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp50
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h1
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp8
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp2
-rw-r--r--src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h4
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri35
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc4
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h37
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp172
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h89
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp431
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h146
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp237
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h73
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp803
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h145
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp177
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h66
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui74
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp342
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h92
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp348
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h97
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp414
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h103
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp211
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.h57
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.ui87
-rw-r--r--src/plugins/qmldesigner/designercore/include/qmlitemnode.h3
-rw-r--r--src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp44
-rw-r--r--src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp33
-rw-r--r--src/plugins/qmldesigner/designercore/instances/puppetcreator.h1
-rw-r--r--src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp2
-rw-r--r--src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp54
-rw-r--r--src/plugins/qmldesigner/designercore/model/rewriterview.cpp8
-rw-r--r--src/plugins/qmldesigner/designersettings.cpp4
-rw-r--r--src/plugins/qmldesigner/designersettings.h5
-rw-r--r--src/plugins/qmldesigner/qmldesigner.pro7
-rw-r--r--src/plugins/qmldesigner/qmldesigner.qbs1
-rw-r--r--src/plugins/qmldesigner/qmldesigner_dependencies.pri1
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.cpp6
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.pro1
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.qbs20
-rw-r--r--src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp44
-rw-r--r--src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h2
-rw-r--r--src/plugins/qmlpreview/qmlpreviewruncontrol.cpp8
-rw-r--r--src/plugins/qmlprojectmanager/CMakeLists.txt1
-rw-r--r--src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp128
-rw-r--r--src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h57
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectmanager.pro2
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectmanager.qbs1
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h2
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp55
-rw-r--r--tests/unit/mockup/projectexplorer/project.h2
130 files changed, 7694 insertions, 317 deletions
diff --git a/doc/qtcreator/src/android/androiddev.qdoc b/doc/qtcreator/src/android/androiddev.qdoc
index 950fe6e9aa..6af2926a08 100644
--- a/doc/qtcreator/src/android/androiddev.qdoc
+++ b/doc/qtcreator/src/android/androiddev.qdoc
@@ -57,11 +57,11 @@
To use \QC to develop Qt applications for Android, you need the following:
\list
- \li \l{https://www.oracle.com/java/technologies/javase-jdk8-downloads.html}
- {Java SE Development Kit (JDK)} version 6 up to 8.
- You can also use \l{http://openjdk.java.net/}{OpenJDK} on Linux.
+ \li \l{AdoptOpenJDK} for all platforms. You can also use \l{OpenJDK}
+ on Linux.
- \note Android SDK Tools have issues with JDK versions later than 8.
+ \note Android SDK Tools versions <= 26.x have issues with JDK versions
+ later than 8. It is recommended to use the latest Command-line SDK Tools.
\li \l{http://www.gradle.org}{Gradle} for building application packages
(APK) and app bundles (AAB) for Android devices. Gradle is delivered
diff --git a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc
index 0a76cddc40..fa055e6975 100644
--- a/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc
+++ b/doc/qtcreator/src/cmake/creator-projects-cmake.qdoc
@@ -59,7 +59,7 @@
\section1 Adding CMake Tools
- \QC requires CMake's {https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html}
+ \QC requires CMake's \l{https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html}
{file-based API}. Please make sure to use CMake version 3.14, or later.
To specify paths to CMake executables:
diff --git a/doc/qtcreator/src/external-resources/external-resources.qdoc b/doc/qtcreator/src/external-resources/external-resources.qdoc
index ba1517344f..9114cfe3ee 100644
--- a/doc/qtcreator/src/external-resources/external-resources.qdoc
+++ b/doc/qtcreator/src/external-resources/external-resources.qdoc
@@ -45,3 +45,11 @@
\externalpage https://doc.qt.io/QtForMCUs/qtul-getting-started-windows.html
\title Getting Started on Windows
*/
+/*!
+ \externalpage https://adoptopenjdk.net/
+ \title AdoptOpenJDK
+*/
+/*!
+ \externalpage http://openjdk.java.net
+ \title OpenJDK
+*/
diff --git a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.cpp b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.cpp
index 0c69dc0aa4..1ee34ff447 100644
--- a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.cpp
+++ b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.cpp
@@ -40,7 +40,8 @@ CreateSceneCommand::CreateSceneCommand(const QVector<InstanceContainer> &instanc
const QVector<AddImportContainer> &importVector,
const QVector<MockupTypeContainer> &mockupTypeVector,
const QUrl &fileUrl,
- const QHash<QString, QVariantMap> &edit3dToolStates)
+ const QHash<QString, QVariantMap> &edit3dToolStates,
+ const QString &language)
: m_instanceVector(instanceContainer),
m_reparentInstanceVector(reparentContainer),
m_idVector(idVector),
@@ -50,7 +51,8 @@ CreateSceneCommand::CreateSceneCommand(const QVector<InstanceContainer> &instanc
m_importVector(importVector),
m_mockupTypeVector(mockupTypeVector),
m_fileUrl(fileUrl),
- m_edit3dToolStates(edit3dToolStates)
+ m_edit3dToolStates(edit3dToolStates),
+ m_language(language)
{
}
@@ -104,6 +106,11 @@ QHash<QString, QVariantMap> CreateSceneCommand::edit3dToolStates() const
return m_edit3dToolStates;
}
+QString CreateSceneCommand::language() const
+{
+ return m_language;
+}
+
QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command)
{
out << command.instances();
@@ -116,6 +123,7 @@ QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command)
out << command.mockupTypes();
out << command.fileUrl();
out << command.edit3dToolStates();
+ out << command.language();
return out;
}
@@ -132,6 +140,7 @@ QDataStream &operator>>(QDataStream &in, CreateSceneCommand &command)
in >> command.m_mockupTypeVector;
in >> command.m_fileUrl;
in >> command.m_edit3dToolStates;
+ in >> command.m_language;
return in;
}
@@ -148,7 +157,8 @@ QDebug operator <<(QDebug debug, const CreateSceneCommand &command)
<< "imports: " << command.imports() << ", "
<< "mockupTypes: " << command.mockupTypes() << ", "
<< "fileUrl: " << command.fileUrl() << ", "
- << "edit3dToolStates: " << command.edit3dToolStates() << ")";
+ << "edit3dToolStates: " << command.edit3dToolStates() << ", "
+ << "language: " << command.language() << ")";
}
}
diff --git a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h
index fbfd2d2d5f..aee8fe0d47 100644
--- a/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h
+++ b/share/qtcreator/qml/qmlpuppet/commands/createscenecommand.h
@@ -45,16 +45,18 @@ class CreateSceneCommand
public:
CreateSceneCommand();
- explicit CreateSceneCommand(const QVector<InstanceContainer> &instanceContainer,
- const QVector<ReparentContainer> &reparentContainer,
- const QVector<IdContainer> &idVector,
- const QVector<PropertyValueContainer> &valueChangeVector,
- const QVector<PropertyBindingContainer> &bindingChangeVector,
- const QVector<PropertyValueContainer> &auxiliaryChangeVector,
- const QVector<AddImportContainer> &importVector,
- const QVector<MockupTypeContainer> &mockupTypeVector,
- const QUrl &fileUrl,
- const QHash<QString, QVariantMap> &edit3dToolStates);
+ explicit CreateSceneCommand(
+ const QVector<InstanceContainer> &instanceContainer,
+ const QVector<ReparentContainer> &reparentContainer,
+ const QVector<IdContainer> &idVector,
+ const QVector<PropertyValueContainer> &valueChangeVector,
+ const QVector<PropertyBindingContainer> &bindingChangeVector,
+ const QVector<PropertyValueContainer> &auxiliaryChangeVector,
+ const QVector<AddImportContainer> &importVector,
+ const QVector<MockupTypeContainer> &mockupTypeVector,
+ const QUrl &fileUrl,
+ const QHash<QString, QVariantMap> &edit3dToolStates,
+ const QString &language);
QVector<InstanceContainer> instances() const;
QVector<ReparentContainer> reparentInstances() const;
@@ -66,6 +68,7 @@ public:
QVector<MockupTypeContainer> mockupTypes() const;
QUrl fileUrl() const;
QHash<QString, QVariantMap> edit3dToolStates() const;
+ QString language() const;
private:
QVector<InstanceContainer> m_instanceVector;
@@ -78,6 +81,7 @@ private:
QVector<MockupTypeContainer> m_mockupTypeVector;
QUrl m_fileUrl;
QHash<QString, QVariantMap> m_edit3dToolStates;
+ QString m_language;
};
QDataStream &operator<<(QDataStream &out, const CreateSceneCommand &command);
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml
index 1401333627..e88b9c4c51 100644
--- a/share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/DirectionalDraggable.qml
@@ -104,6 +104,7 @@ Model {
grabsMouse: targetNode
active: rootModel.active
dragHelper: rootModel.dragHelper
+ priority: 5
onPressed: rootModel.handlePressed(mouseAreaYZ, planePos, screenPos)
onDragged: rootModel.handleDragged(mouseAreaYZ, planePos, screenPos)
@@ -121,6 +122,7 @@ Model {
grabsMouse: targetNode
active: rootModel.active
dragHelper: rootModel.dragHelper
+ priority: 5
onPressed: rootModel.handlePressed(mouseAreaXZ, planePos, screenPos)
onDragged: rootModel.handleDragged(mouseAreaXZ, planePos, screenPos)
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml
index adb6f23a48..493e6cc3c2 100644
--- a/share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/MoveGizmo.qml
@@ -161,7 +161,7 @@ Node {
color: highlightOnHover && (hovering || dragging) ? Qt.lighter(Qt.rgba(0.5, 0.5, 0.5, 1))
: Qt.rgba(0.5, 0.5, 0.5, 1)
rotation: view3D.camera.rotation
- priority: 1
+ priority: 10
targetNode: moveGizmo.targetNode
view3D: moveGizmo.view3D
diff --git a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml
index ccfc45b621..3b4918e272 100644
--- a/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml
+++ b/share/qtcreator/qml/qmlpuppet/mockfiles/ScaleGizmo.qml
@@ -185,7 +185,7 @@ Node {
height: 120
rotation: view3D.camera.rotation
grabsMouse: scaleGizmo.targetNode
- priority: 1
+ priority: 10
active: scaleGizmo.visible
dragHelper: scaleGizmo.dragHelper
diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp
index d356c415d8..1b48a9ae14 100644
--- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp
+++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.cpp
@@ -314,6 +314,7 @@ void NodeInstanceServer::stopRenderTimer()
void NodeInstanceServer::createScene(const CreateSceneCommand &command)
{
+ setTranslationLanguage(command.language());
initializeView();
Internal::QmlPrivateGate::stopUnifiedTimer();
@@ -1329,6 +1330,20 @@ void NodeInstanceServer::loadDummyContextObjectFile(const QFileInfo& qmlFileInfo
refreshBindings();
}
+void NodeInstanceServer::setTranslationLanguage(const QString &language)
+{
+ static QPointer<MultiLanguage::Translator> multilanguageTranslator;
+ if (!MultiLanguage::databaseFilePath().isEmpty()) {
+ if (!multilanguageLink) {
+ multilanguageLink = std::make_unique<MultiLanguage::Link>();
+ multilanguageTranslator = multilanguageLink->translator().release();
+ QCoreApplication::installTranslator(multilanguageTranslator);
+ }
+ if (multilanguageTranslator)
+ multilanguageTranslator->setLanguage(language);
+ }
+}
+
void NodeInstanceServer::loadDummyDataFiles(const QString& directory)
{
QDir dir(directory, "*.qml");
@@ -1400,16 +1415,7 @@ void NodeInstanceServer::view3DAction(const View3DActionCommand &command)
void NodeInstanceServer::changeLanguage(const ChangeLanguageCommand &command)
{
- static QPointer<MultiLanguage::Translator> multilanguageTranslator;
- if (!MultiLanguage::databaseFilePath().isEmpty()) {
- if (!multilanguageLink) {
- multilanguageLink = std::make_unique<MultiLanguage::Link>();
- multilanguageTranslator = multilanguageLink->translator().release();
- QCoreApplication::installTranslator(multilanguageTranslator);
- }
- if (multilanguageTranslator)
- multilanguageTranslator->setLanguage(command.language);
- }
+ setTranslationLanguage(command.language);
QEvent ev(QEvent::LanguageChange);
QCoreApplication::sendEvent(QCoreApplication::instance(), &ev);
engine()->retranslate();
diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h
index 2d6544eda0..b0dc39a2a2 100644
--- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h
+++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/nodeinstanceserver.h
@@ -241,6 +241,7 @@ protected:
virtual void initializeView() = 0;
virtual void setupScene(const CreateSceneCommand &command) = 0;
+ void setTranslationLanguage(const QString &language);
void loadDummyDataFiles(const QString& directory);
void loadDummyDataContext(const QString& directory);
void loadDummyDataFile(const QFileInfo& fileInfo);
diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp
index a177e55c85..852ef16bad 100644
--- a/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp
+++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/instances/qt5previewnodeinstanceserver.cpp
@@ -46,6 +46,7 @@ Qt5PreviewNodeInstanceServer::Qt5PreviewNodeInstanceServer(NodeInstanceClientInt
void Qt5PreviewNodeInstanceServer::createScene(const CreateSceneCommand &command)
{
+ setTranslationLanguage(command.language());
initializeView();
setupScene(command);
startRenderTimer();
diff --git a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri
index a2b3219327..f772fd8a55 100644
--- a/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri
+++ b/share/qtcreator/qml/qmlpuppet/qml2puppet/qml2puppet.pri
@@ -5,9 +5,11 @@ CONFIG += c++11
DEFINES -= QT_CREATOR
-# This .pri file contains classes to enable a special multilanguage translator debug service
+# This .pri file contains classes to enable a special multilanguage translator
MULTILANGUAGE_SUPPORT_PRI=$$(MULTILANGUAGE_SUPPORT_PRI)
!isEmpty(MULTILANGUAGE_SUPPORT_PRI) {
+ exists($$(MULTILANGUAGE_SUPPORT_PRI)): message(including \"$$(MULTILANGUAGE_SUPPORT_PRI)\")
+ else: error("MULTILANGUAGE_SUPPORT_PRI: \"$$(MULTILANGUAGE_SUPPORT_PRI)\" does not exist.")
include($$(MULTILANGUAGE_SUPPORT_PRI))
DEFINES += MULTILANGUAGE_TRANSLATIONPROVIDER
}
diff --git a/src/libs/qmldebug/qmldebugcommandlinearguments.h b/src/libs/qmldebug/qmldebugcommandlinearguments.h
index 0a22d0f4c9..f69fec2f9b 100644
--- a/src/libs/qmldebug/qmldebugcommandlinearguments.h
+++ b/src/libs/qmldebug/qmldebugcommandlinearguments.h
@@ -47,11 +47,11 @@ inline QString qmlDebugServices(QmlDebugServicesPreset preset)
case NoQmlDebugServices:
return QString();
case QmlDebuggerServices:
- return QStringLiteral("DebugMessages,QmlDebugger,V8Debugger,QmlInspector");
+ return QStringLiteral("DebugMessages,QmlDebugger,V8Debugger,QmlInspector,DebugTranslation");
case QmlProfilerServices:
- return QStringLiteral("CanvasFrameRate,EngineControl,DebugMessages");
+ return QStringLiteral("CanvasFrameRate,EngineControl,DebugMessages,DebugTranslation");
case QmlNativeDebuggerServices:
- return QStringLiteral("NativeQmlDebugger");
+ return QStringLiteral("NativeQmlDebugger,DebugTranslation");
case QmlPreviewServices:
return QStringLiteral("QmlPreview,DebugTranslation");
default:
diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp
index 6527718c57..f58b414487 100644
--- a/src/libs/qmljs/qmljscheck.cpp
+++ b/src/libs/qmljs/qmljscheck.cpp
@@ -621,7 +621,6 @@ class UnsupportedTypesByQmlUi : public QStringList
public:
UnsupportedTypesByQmlUi() : QStringList({"ShaderEffect",
"Component",
- "Transition",
"Drawer"})
{
append(UnsupportedTypesByVisualDesigner());
diff --git a/src/libs/sqlite/sqlitebasestatement.cpp b/src/libs/sqlite/sqlitebasestatement.cpp
index ce22ece526..dc4dd465e7 100644
--- a/src/libs/sqlite/sqlitebasestatement.cpp
+++ b/src/libs/sqlite/sqlitebasestatement.cpp
@@ -251,10 +251,46 @@ sqlite3 *BaseStatement::sqliteDatabaseHandle() const
void BaseStatement::checkForStepError(int resultCode) const
{
switch (resultCode) {
- case SQLITE_BUSY: throwStatementIsBusy("SqliteStatement::stepStatement: database engine was unable to acquire the database locks!");
- case SQLITE_ERROR : throwStatementHasError("SqliteStatement::stepStatement: run-time error (such as a constraint violation) has occurred!");
- case SQLITE_MISUSE: throwStatementIsMisused("SqliteStatement::stepStatement: was called inappropriately!");
- case SQLITE_CONSTRAINT: throwConstraintPreventsModification("SqliteStatement::stepStatement: contraint prevent insert or update!");
+ case SQLITE_BUSY:
+ throwStatementIsBusy("SqliteStatement::stepStatement: database engine was unable to "
+ "acquire the database locks!");
+ case SQLITE_ERROR:
+ throwStatementHasError("SqliteStatement::stepStatement: run-time error (such as a "
+ "constraint violation) has occurred!");
+ case SQLITE_MISUSE:
+ throwStatementIsMisused("SqliteStatement::stepStatement: was called inappropriately!");
+ case SQLITE_CONSTRAINT:
+ throwConstraintPreventsModification(
+ "SqliteStatement::stepStatement: contraint prevent insert or update!");
+ case SQLITE_TOOBIG:
+ throwTooBig("SqliteStatement::stepStatement: Some is to bigger than SQLITE_MAX_LENGTH.");
+ case SQLITE_SCHEMA:
+ throwSchemaChangeError("SqliteStatement::stepStatement: Schema changed but the statement "
+ "cannot be recompiled.");
+ case SQLITE_READONLY:
+ throwCannotWriteToReadOnlyConnection(
+ "SqliteStatement::stepStatement: Cannot write to read only connection");
+ case SQLITE_PROTOCOL:
+ throwProtocolError(
+ "SqliteStatement::stepStatement: Something strang with the file locking happened.");
+ case SQLITE_NOMEM:
+ throw std::bad_alloc();
+ case SQLITE_NOLFS:
+ throwDatabaseExceedsMaximumFileSize(
+ "SqliteStatement::stepStatement: Database exceeds maximum file size.");
+ case SQLITE_MISMATCH:
+ throwDataTypeMismatch(
+ "SqliteStatement::stepStatement: Most probably you used not an integer for a rowid.");
+ case SQLITE_LOCKED:
+ throwConnectionIsLocked("SqliteStatement::stepStatement: Database connection is locked.");
+ case SQLITE_IOERR:
+ throwInputOutputError("SqliteStatement::stepStatement: An IO error happened.");
+ case SQLITE_INTERRUPT:
+ throwExecutionInterrupted("SqliteStatement::stepStatement: Execution was interrupted.");
+ case SQLITE_CORRUPT:
+ throwDatabaseIsCorrupt("SqliteStatement::stepStatement: Database is corrupt.");
+ case SQLITE_CANTOPEN:
+ throwCannotOpen("SqliteStatement::stepStatement: Cannot open database or temporary file.");
}
throwUnknowError("SqliteStatement::stepStatement: unknown error has happened");
@@ -263,10 +299,17 @@ void BaseStatement::checkForStepError(int resultCode) const
void BaseStatement::checkForResetError(int resultCode) const
{
switch (resultCode) {
- case SQLITE_BUSY: throwStatementIsBusy("SqliteStatement::stepStatement: database engine was unable to acquire the database locks!");
- case SQLITE_ERROR : throwStatementHasError("SqliteStatement::stepStatement: run-time error (such as a constraint violation) has occurred!");
- case SQLITE_MISUSE: throwStatementIsMisused("SqliteStatement::stepStatement: was called inappropriately!");
- case SQLITE_CONSTRAINT: throwConstraintPreventsModification("SqliteStatement::stepStatement: contraint prevent insert or update!");
+ case SQLITE_BUSY:
+ throwStatementIsBusy("SqliteStatement::stepStatement: database engine was unable to "
+ "acquire the database locks!");
+ case SQLITE_ERROR:
+ throwStatementHasError("SqliteStatement::stepStatement: run-time error (such as a "
+ "constraint violation) has occurred!");
+ case SQLITE_MISUSE:
+ throwStatementIsMisused("SqliteStatement::stepStatement: was called inappropriately!");
+ case SQLITE_CONSTRAINT:
+ throwConstraintPreventsModification(
+ "SqliteStatement::stepStatement: contraint prevent insert or update!");
}
throwUnknowError("SqliteStatement::reset: unknown error has happened");
@@ -278,7 +321,8 @@ void BaseStatement::checkForPrepareError(int resultCode) const
case SQLITE_BUSY: throwStatementIsBusy("SqliteStatement::prepareStatement: database engine was unable to acquire the database locks!");
case SQLITE_ERROR : throwStatementHasError("SqliteStatement::prepareStatement: run-time error (such as a constraint violation) has occurred!");
case SQLITE_MISUSE: throwStatementIsMisused("SqliteStatement::prepareStatement: was called inappropriately!");
- case SQLITE_IOERR: throwIoError("SqliteStatement::prepareStatement: IO error happened!");
+ case SQLITE_IOERR:
+ throwInputOutputError("SqliteStatement::prepareStatement: IO error happened!");
}
throwUnknowError("SqliteStatement::prepareStatement: unknown error has happened");
@@ -346,9 +390,9 @@ void BaseStatement::throwStatementIsMisused(const char *whatHasHappened) const
throw StatementIsMisused(whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle()));
}
-void BaseStatement::throwIoError(const char *whatHasHappened) const
+void BaseStatement::throwInputOutputError(const char *whatHasHappened) const
{
- throw IoError(whatHasHappened);
+ throw InputOutputError(whatHasHappened);
}
void BaseStatement::throwConstraintPreventsModification(const char *whatHasHappened) const
@@ -384,6 +428,56 @@ void BaseStatement::throwBingingTooBig(const char *whatHasHappened) const
throw BindingTooBig(whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle()));
}
+void BaseStatement::throwTooBig(const char *whatHasHappened) const
+{
+ throw TooBig{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwSchemaChangeError(const char *whatHasHappened) const
+{
+ throw SchemeChangeError{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwCannotWriteToReadOnlyConnection(const char *whatHasHappened) const
+{
+ throw CannotWriteToReadOnlyConnection{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwProtocolError(const char *whatHasHappened) const
+{
+ throw ProtocolError{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwDatabaseExceedsMaximumFileSize(const char *whatHasHappened) const
+{
+ throw DatabaseExceedsMaximumFileSize{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwDataTypeMismatch(const char *whatHasHappened) const
+{
+ throw DataTypeMismatch{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwConnectionIsLocked(const char *whatHasHappened) const
+{
+ throw ConnectionIsLocked{whatHasHappened, sqlite3_errmsg(sqliteDatabaseHandle())};
+}
+
+void BaseStatement::throwExecutionInterrupted(const char *whatHasHappened) const
+{
+ throw ExecutionInterrupted{whatHasHappened};
+}
+
+void BaseStatement::throwDatabaseIsCorrupt(const char *whatHasHappened) const
+{
+ throw DatabaseIsCorrupt{whatHasHappened};
+}
+
+void BaseStatement::throwCannotOpen(const char *whatHasHappened) const
+{
+ throw CannotOpen{whatHasHappened};
+}
+
QString BaseStatement::columnName(int column) const
{
return QString::fromUtf8(sqlite3_column_name(m_compiledStatement.get(), column));
diff --git a/src/libs/sqlite/sqlitebasestatement.h b/src/libs/sqlite/sqlitebasestatement.h
index 2480a4674a..bae41665d5 100644
--- a/src/libs/sqlite/sqlitebasestatement.h
+++ b/src/libs/sqlite/sqlitebasestatement.h
@@ -109,7 +109,7 @@ public:
[[noreturn]] void throwStatementIsBusy(const char *whatHasHappened) const;
[[noreturn]] void throwStatementHasError(const char *whatHasHappened) const;
[[noreturn]] void throwStatementIsMisused(const char *whatHasHappened) const;
- [[noreturn]] void throwIoError(const char *whatHasHappened) const;
+ [[noreturn]] void throwInputOutputError(const char *whatHasHappened) const;
[[noreturn]] void throwConstraintPreventsModification(const char *whatHasHappened) const;
[[noreturn]] void throwNoValuesToFetch(const char *whatHasHappened) const;
[[noreturn]] void throwInvalidColumnFetched(const char *whatHasHappened) const;
@@ -117,6 +117,16 @@ public:
[[noreturn]] void throwWrongBingingName(const char *whatHasHappened) const;
[[noreturn]] void throwUnknowError(const char *whatHasHappened) const;
[[noreturn]] void throwBingingTooBig(const char *whatHasHappened) const;
+ [[noreturn]] void throwTooBig(const char *whatHasHappened) const;
+ [[noreturn]] void throwSchemaChangeError(const char *whatHasHappened) const;
+ [[noreturn]] void throwCannotWriteToReadOnlyConnection(const char *whatHasHappened) const;
+ [[noreturn]] void throwProtocolError(const char *whatHasHappened) const;
+ [[noreturn]] void throwDatabaseExceedsMaximumFileSize(const char *whatHasHappened) const;
+ [[noreturn]] void throwDataTypeMismatch(const char *whatHasHappened) const;
+ [[noreturn]] void throwConnectionIsLocked(const char *whatHasHappened) const;
+ [[noreturn]] void throwExecutionInterrupted(const char *whatHasHappened) const;
+ [[noreturn]] void throwDatabaseIsCorrupt(const char *whatHasHappened) const;
+ [[noreturn]] void throwCannotOpen(const char *whatHasHappened) const;
QString columnName(int column) const;
diff --git a/src/libs/sqlite/sqliteexception.h b/src/libs/sqlite/sqliteexception.h
index bd4245934c..a46d30d851 100644
--- a/src/libs/sqlite/sqliteexception.h
+++ b/src/libs/sqlite/sqliteexception.h
@@ -91,10 +91,10 @@ public:
}
};
-class IoError : public Exception
+class InputOutputError : public Exception
{
public:
- IoError(const char *whatErrorHasHappen)
+ InputOutputError(const char *whatErrorHasHappen)
: Exception(whatErrorHasHappen)
{
}
@@ -267,6 +267,14 @@ public:
}
};
+class TooBig : public Exception
+{
+public:
+ TooBig(const char *whatErrorHasHappen, Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
class CannotConvert : public Exception
{
public:
@@ -299,4 +307,84 @@ public:
{}
};
+class SchemeChangeError : public Exception
+{
+public:
+ SchemeChangeError(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class CannotWriteToReadOnlyConnection : public Exception
+{
+public:
+ CannotWriteToReadOnlyConnection(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class ProtocolError : public Exception
+{
+public:
+ ProtocolError(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class DatabaseExceedsMaximumFileSize : public Exception
+{
+public:
+ DatabaseExceedsMaximumFileSize(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class DataTypeMismatch : public Exception
+{
+public:
+ DataTypeMismatch(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class ConnectionIsLocked : public Exception
+{
+public:
+ ConnectionIsLocked(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class ExecutionInterrupted : public Exception
+{
+public:
+ ExecutionInterrupted(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class DatabaseIsCorrupt : public Exception
+{
+public:
+ DatabaseIsCorrupt(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
+
+class CannotOpen : public Exception
+{
+public:
+ CannotOpen(const char *whatErrorHasHappen,
+ Utils::SmallString &&errorMessage = Utils::SmallString())
+ : Exception(whatErrorHasHappen, std::move(errorMessage))
+ {}
+};
} // namespace Sqlite
diff --git a/src/plugins/android/androidsettingswidget.cpp b/src/plugins/android/androidsettingswidget.cpp
index 404d3e1952..b5aa045589 100644
--- a/src/plugins/android/androidsettingswidget.cpp
+++ b/src/plugins/android/androidsettingswidget.cpp
@@ -454,8 +454,6 @@ AndroidSettingsWidget::AndroidSettingsWidget()
m_ui.AVDTableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
m_ui.AVDTableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
- m_ui.downloadOpenJDKToolButton->setVisible(!HostOsInfo::isLinuxHost());
-
const QIcon downloadIcon = Icons::ONLINE.icon();
m_ui.downloadSDKToolButton->setIcon(downloadIcon);
m_ui.downloadNDKToolButton->setIcon(downloadIcon);
@@ -696,7 +694,10 @@ void AndroidSettingsWidget::openNDKDownloadUrl()
void AndroidSettingsWidget::openOpenJDKDownloadUrl()
{
- QDesktopServices::openUrl(QUrl::fromUserInput("https://www.oracle.com/java/technologies/javase-jdk8-downloads.html"));
+ if (HostOsInfo::isLinuxHost())
+ QDesktopServices::openUrl(QUrl::fromUserInput("https://openjdk.java.net/install/"));
+ else
+ QDesktopServices::openUrl(QUrl::fromUserInput("https://adoptopenjdk.net/"));
}
void AndroidSettingsWidget::downloadOpenSslRepo(const bool silent)
diff --git a/src/plugins/clangformat/clangformatplugin.cpp b/src/plugins/clangformat/clangformatplugin.cpp
index 2ebef54584..9b76a0862f 100644
--- a/src/plugins/clangformat/clangformatplugin.cpp
+++ b/src/plugins/clangformat/clangformatplugin.cpp
@@ -153,12 +153,15 @@ bool ClangFormatPlugin::initialize(const QStringList &arguments, QString *errorS
}
return true;
#else
-#ifndef Q_CC_MSVC
+#ifdef _MSC_VER
+#pragma message( \
+ "ClangFormat: building dummy plugin due to unmodified Clang, see README.md for more info")
+#else
#warning ClangFormat: building dummy plugin due to unmodified Clang, see README.md for more info
#endif
- *errorString = QStringLiteral("Disabling ClangFormat plugin as it has not been built against a modified Clang's libFormat."
+ *errorString = "Disabling ClangFormat plugin as it has not been built against a modified Clang's libFormat."
"For more information see the Qt Creator README at "
- "https://code.qt.io/cgit/qt-creator/qt-creator.git/tree/README.md");
+ "https://code.qt.io/cgit/qt-creator/qt-creator.git/tree/README.md";
return false;
#endif
}
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 24c345d1e4..fba3472817 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -38,6 +38,29 @@ if (APPLE)
set(QmlDesignerPluginInstallPrefix "${IDE_PLUGIN_PATH}/QmlDesigner")
endif()
+
+add_qtc_plugin(assetexporterplugin
+ CONDITION TARGET QmlDesigner
+ DEPENDS Core ProjectExplorer QmlDesigner Utils Qt5::Qml
+ PUBLIC_INCLUDES assetexporterplugin
+ SOURCES
+ assetexporterplugin/assetexportdialog.h assetexporterplugin/assetexportdialog.cpp assetexporterplugin/assetexportdialog.ui
+ assetexporterplugin/assetexporter.h assetexporterplugin/assetexporter.cpp
+ assetexporterplugin/assetexporterplugin.h assetexporterplugin/assetexporterplugin.cpp
+ assetexporterplugin/assetexporterview.h assetexporterplugin/assetexporterview.cpp
+ assetexporterplugin/assetexportpluginconstants.h
+ assetexporterplugin/componentexporter.h assetexporterplugin/componentexporter.cpp
+ assetexporterplugin/exportnotification.h assetexporterplugin/exportnotification.cpp
+ assetexporterplugin/filepathmodel.h assetexporterplugin/filepathmodel.cpp
+ assetexporterplugin/parsers/assetnodeparser.h assetexporterplugin/parsers/assetnodeparser.cpp
+ assetexporterplugin/parsers/modelitemnodeparser.h assetexporterplugin/parsers/modelitemnodeparser.cpp
+ assetexporterplugin/parsers/modelnodeparser.h assetexporterplugin/parsers/modelnodeparser.cpp
+ assetexporterplugin/parsers/textnodeparser.h assetexporterplugin/parsers/textnodeparser.cpp
+ assetexporterplugin/assetexporterplugin.qrc
+ PLUGIN_PATH ${QmlDesignerPluginInstallPrefix}
+ SKIP_DEBUG_CMAKE_FILE_CHECK
+)
+
add_qtc_plugin(componentsplugin
CONDITION TARGET QmlDesigner
DEPENDS Core QmlDesigner Utils Qt5::Qml
@@ -647,6 +670,22 @@ extend_qtc_plugin(QmlDesigner
)
extend_qtc_plugin(QmlDesigner
+ SOURCES_PREFIX components/transitioneditor
+ SOURCES
+ transitioneditorview.cpp transitioneditorview.h
+ transitioneditorwidget.cpp transitioneditorwidget.h
+ transitioneditortoolbar.cpp transitioneditortoolbar.h
+ transitioneditorgraphicsscene.cpp transitioneditorgraphicsscene.h
+ transitioneditorgraphicslayout.cpp transitioneditorgraphicslayout.h
+ transitioneditorsectionitem.cpp transitioneditorsectionitem.h
+ transitioneditorpropertyitem.cpp transitioneditorpropertyitem.h
+ transitioneditorsettingsdialog.cpp transitioneditorsettingsdialog.h
+ transitioneditorsettingsdialog.ui
+ transitionform.cpp transitionform.h
+ transitioneditor.qrc
+)
+
+extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/curveeditor
SOURCES
curveeditor.qrc
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
new file mode 100644
index 0000000000..53ec46697e
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
@@ -0,0 +1,190 @@
+/****************************************************************************
+**
+** 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 "assetexportdialog.h"
+
+#include "ui_assetexportdialog.h"
+#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 <QPushButton>
+#include <QListView>
+#include <QPlainTextEdit>
+#include <QDialogButtonBox>
+#include <QMessageBox>
+#include <QScrollBar>
+
+#include <algorithm>
+#include <cmath>
+
+namespace {
+static void addFormattedMessage(Utils::OutputFormatter *formatter, const QString &str,
+ Utils::OutputFormat format) {
+ if (!formatter)
+ return;
+
+ QPlainTextEdit *edit = formatter->plainTextEdit();
+ QScrollBar *scroll = edit->verticalScrollBar();
+ bool isAtBottom = scroll && scroll->value() == scroll->maximum();
+
+ QString msg = str + "\n";
+ formatter->appendMessage(msg, format);
+
+ if (isAtBottom)
+ scroll->setValue(scroll->maximum());
+}
+}
+
+using namespace ProjectExplorer;
+
+namespace QmlDesigner {
+AssetExportDialog::AssetExportDialog(const Utils::FilePath &exportPath,
+ AssetExporter &assetExporter, FilePathModel &model,
+ QWidget *parent) :
+ QDialog(parent),
+ m_assetExporter(assetExporter),
+ m_filePathModel(model),
+ m_ui(new Ui::AssetExportDialog),
+ m_filesView(new QListView),
+ m_exportLogs(new QPlainTextEdit),
+ m_outputFormatter(new Utils::OutputFormatter())
+{
+ m_ui->setupUi(this);
+
+ m_ui->exportPath->setFileName(exportPath);
+ m_ui->exportPath->setPromptDialogTitle(tr("Choose Export Path"));
+ m_ui->exportPath->lineEdit()->setReadOnly(true);
+ m_ui->exportPath->addButton(tr("Open"), this, [this]() {
+ Core::FileUtils::showInGraphicalShell(Core::ICore::mainWindow(), m_ui->exportPath->path());
+ });
+
+ m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(false);
+
+ m_ui->stackedWidget->addWidget(m_filesView);
+ m_filesView->setModel(&m_filePathModel);
+
+ m_exportLogs->setReadOnly(true);
+ m_outputFormatter->setPlainTextEdit(m_exportLogs);
+ m_ui->stackedWidget->addWidget(m_exportLogs);
+ switchView(false);
+
+ connect(m_ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, [this]() {
+ m_assetExporter.cancel();
+ });
+
+ m_exportBtn = m_ui->buttonBox->addButton(tr("Export"), QDialogButtonBox::AcceptRole);
+ m_exportBtn->setEnabled(false);
+ connect(m_exportBtn, &QPushButton::clicked, this, &AssetExportDialog::onExport);
+ connect(&m_filePathModel, &FilePathModel::modelReset, this, [this]() {
+ m_ui->exportProgress->setRange(0, 1000);
+ m_ui->exportProgress->setValue(0);
+ m_exportBtn->setEnabled(true);
+ });
+
+ connect(m_ui->buttonBox->button(QDialogButtonBox::Close), &QPushButton::clicked, [this]() {
+ close();
+ });
+ m_ui->buttonBox->button(QDialogButtonBox::Close)->setVisible(false);
+
+ connect(&m_assetExporter, &AssetExporter::stateChanged,
+ this, &AssetExportDialog::onExportStateChanged);
+ connect(&m_assetExporter, &AssetExporter::exportProgressChanged,
+ this, &AssetExportDialog::updateExportProgress);
+
+ connect(TaskHub::instance(), &TaskHub::taskAdded, this, &AssetExportDialog::onTaskAdded);
+
+ m_ui->exportProgress->setRange(0,0);
+}
+
+AssetExportDialog::~AssetExportDialog()
+{
+ m_assetExporter.cancel();
+}
+
+void AssetExportDialog::onExport()
+{
+ switchView(true);
+
+ updateExportProgress(0.0);
+ TaskHub::clearTasks(Constants::TASK_CATEGORY_ASSET_EXPORT);
+ m_exportLogs->clear();
+
+ m_assetExporter.exportQml(m_filePathModel.files(), m_ui->exportPath->fileName());
+}
+
+void AssetExportDialog::onExportStateChanged(AssetExporter::ParsingState newState)
+{
+ switch (newState) {
+ case AssetExporter::ParsingState::ExportingDone:
+ m_exportBtn->setVisible(false);
+ m_ui->buttonBox->button(QDialogButtonBox::Close)->setVisible(true);
+ break;
+ default:
+ break;
+ }
+
+ m_exportBtn->setEnabled(newState == AssetExporter::ParsingState::ExportingDone);
+ m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(m_assetExporter.isBusy());
+}
+
+void AssetExportDialog::updateExportProgress(double value)
+{
+ value = std::max(0.0, std::min(1.0, value));
+ m_ui->exportProgress->setValue(std::round(value * 1000));
+}
+
+void AssetExportDialog::switchView(bool showExportView)
+{
+ if (showExportView)
+ m_ui->stackedWidget->setCurrentWidget(m_exportLogs);
+ else
+ m_ui->stackedWidget->setCurrentWidget(m_filesView);
+}
+
+void AssetExportDialog::onTaskAdded(const ProjectExplorer::Task &task)
+{
+ Utils::OutputFormat format = Utils::NormalMessageFormat;
+ if (task.category == Constants::TASK_CATEGORY_ASSET_EXPORT) {
+ switch (task.type) {
+ case ProjectExplorer::Task::Error:
+ format = Utils::StdErrFormat;
+ break;
+ case ProjectExplorer::Task::Warning:
+ format = Utils::StdOutFormat;
+ break;
+ default:
+ format = Utils::NormalMessageFormat;
+ }
+ addFormattedMessage(m_outputFormatter, task.description(), format);
+ }
+}
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h
new file mode 100644
index 0000000000..7bf68b6a74
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.h
@@ -0,0 +1,82 @@
+/****************************************************************************
+**
+** 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 "assetexporter.h"
+
+#include <QDialog>
+#include <QStringListModel>
+
+#include "utils/fileutils.h"
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+class QPushButton;
+class QListView;
+class QPlainTextEdit;
+QT_END_NAMESPACE
+
+namespace Ui {
+class AssetExportDialog;
+}
+
+namespace Utils {
+class OutputFormatter;
+}
+
+namespace ProjectExplorer {
+class Task;
+}
+
+namespace QmlDesigner {
+class FilePathModel;
+
+class AssetExportDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AssetExportDialog(const Utils::FilePath &exportPath, AssetExporter &assetExporter,
+ FilePathModel& model, QWidget *parent = nullptr);
+ ~AssetExportDialog();
+
+private:
+ void onExport();
+ void onExportStateChanged(AssetExporter::ParsingState newState);
+ void updateExportProgress(double value);
+ void switchView(bool showExportView);
+ void onTaskAdded(const ProjectExplorer::Task &task);
+
+private:
+ AssetExporter &m_assetExporter;
+ FilePathModel &m_filePathModel;
+ std::unique_ptr<Ui::AssetExportDialog> m_ui;
+ QPushButton *m_exportBtn = nullptr;
+ QListView *m_filesView = nullptr;
+ QPlainTextEdit *m_exportLogs = nullptr;
+ Utils::OutputFormatter *m_outputFormatter = nullptr;
+};
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.ui b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.ui
new file mode 100644
index 0000000000..38c2152098
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.ui
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>AssetExportDialog</class>
+ <widget class="QDialog" name="AssetExportDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>768</width>
+ <height>480</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Export QML</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="Utils::PathChooser" name="exportPath" native="true"/>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Export path:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0" colspan="2">
+ <widget class="QStackedWidget" name="stackedWidget"/>
+ </item>
+ <item row="2" column="0" colspan="2">
+ <widget class="QProgressBar" name="exportProgress">
+ <property name="maximum">
+ <number>1000</number>
+ </property>
+ <property name="value">
+ <number>0</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" colspan="2">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>Utils::PathChooser</class>
+ <extends>QWidget</extends>
+ <header location="global">utils/pathchooser.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp
new file mode 100644
index 0000000000..26e2d2c455
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp
@@ -0,0 +1,370 @@
+/****************************************************************************
+**
+** 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 "assetexporter.h"
+#include "componentexporter.h"
+#include "exportnotification.h"
+#include "assetexportpluginconstants.h"
+
+#include "rewriterview.h"
+#include "qmlitemnode.h"
+#include "qmlobjectnode.h"
+#include "utils/qtcassert.h"
+#include "utils/runextensions.h"
+#include "variantproperty.h"
+
+#include <QCryptographicHash>
+#include <QDir>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QLoggingCategory>
+#include <QWaitCondition>
+
+#include <random>
+#include <queue>
+
+using namespace ProjectExplorer;
+using namespace std;
+namespace {
+bool makeParentPath(const Utils::FilePath &path)
+{
+ QDir d;
+ return d.mkpath(path.toFileInfo().absolutePath());
+}
+
+QByteArray generateHash(const QString &token) {
+ static uint counter = 0;
+ std::mt19937 gen(std::random_device().operator()());
+ std::uniform_int_distribution<> distribution(1, 99999);
+ QByteArray data = QString("%1%2%3").arg(token).arg(++counter).arg(distribution(gen)).toLatin1();
+ return QCryptographicHash::hash(data, QCryptographicHash::Md5).toHex();
+}
+
+Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.assetExporter", QtInfoMsg)
+Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.assetExporter", QtWarningMsg)
+Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.assetExporter", QtCriticalMsg)
+}
+
+namespace QmlDesigner {
+
+class AssetDumper
+{
+public:
+ AssetDumper();
+ ~AssetDumper();
+
+ void dumpAsset(const QPixmap &p, const Utils::FilePath &path);
+
+ /* Keeps on dumping until all assets are dumped, then quits */
+ void quitDumper();
+
+ /* Aborts dumping */
+ void abortDumper();
+
+private:
+ void addAsset(const QPixmap &p, const Utils::FilePath &path);
+ void doDumping(QFutureInterface<void> &fi);
+ void savePixmap(const QPixmap &p, Utils::FilePath &path) const;
+
+ QFuture<void> m_dumpFuture;
+ QMutex m_queueMutex;
+ QWaitCondition m_queueCondition;
+ std::queue<std::pair<QPixmap, Utils::FilePath>> m_assets;
+ std::atomic<bool> m_quitDumper;
+};
+
+
+
+AssetExporter::AssetExporter(AssetExporterView *view, ProjectExplorer::Project *project, QObject *parent) :
+ QObject(parent),
+ m_currentState(*this),
+ m_project(project),
+ m_view(view)
+{
+ connect(m_view, &AssetExporterView::loadingFinished, this, &AssetExporter::onQmlFileLoaded);
+ connect(m_view, &AssetExporterView::loadingError, this, &AssetExporter::notifyLoadError);
+}
+
+AssetExporter::~AssetExporter()
+{
+ cancel();
+}
+
+void AssetExporter::exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
+ bool exportAssets)
+{
+ ExportNotification::addInfo(tr("Exporting metadata at %1. Export assets: ")
+ .arg(exportPath.toUserOutput())
+ .arg(exportAssets? tr("Yes") : tr("No")));
+ // TODO Asset export
+ notifyProgress(0.0);
+ Q_UNUSED(exportAssets);
+ m_exportFiles = qmlFiles;
+ m_components = QJsonArray();
+ m_exportPath = exportPath;
+ m_currentState.change(ParsingState::Parsing);
+ triggerLoadNextFile();
+ m_assetDumper = make_unique<AssetDumper>();
+}
+
+void AssetExporter::cancel()
+{
+ // TODO Cancel export
+ m_assetDumper.reset();
+}
+
+bool AssetExporter::isBusy() const
+{
+ return m_currentState == AssetExporter::ParsingState::Parsing ||
+ m_currentState == AssetExporter::ParsingState::ExportingAssets ||
+ m_currentState == AssetExporter::ParsingState::WritingJson;
+}
+
+Utils::FilePath AssetExporter::exportAsset(const QmlObjectNode &node)
+{
+ // TODO: Use this hash as UUID and add to the node.
+ QByteArray hash = addNodeUUID(node.modelNode());
+ Utils::FilePath assetPath = m_exportPath.pathAppended(QString("assets/%1.png")
+ .arg(QString::fromLatin1(hash)));
+ m_assetDumper->dumpAsset(node.toQmlItemNode().instanceRenderPixmap(), assetPath);
+ return assetPath;
+}
+
+void AssetExporter::exportComponent(const ModelNode &rootNode)
+{
+ qCDebug(loggerInfo) << "Exporting component" << rootNode.id();
+ Component exporter(*this, rootNode);
+ exporter.exportComponent();
+ m_components.append(exporter.json());
+}
+
+void AssetExporter::notifyLoadError(AssetExporterView::LoadState state)
+{
+ QString errorStr = tr("Unknown error.");
+ switch (state) {
+ case AssetExporterView::LoadState::Exausted:
+ errorStr = tr("Loading file is taking too long.");
+ break;
+ case AssetExporterView::LoadState::QmlErrorState:
+ errorStr = tr("Cannot parse. QML file has errors.");
+ break;
+ default:
+ return;
+ }
+ qCDebug(loggerError) << "QML load error:" << errorStr;
+ ExportNotification::addError(tr("Loading QML failed. %1").arg(errorStr));
+}
+
+void AssetExporter::notifyProgress(double value) const
+{
+ emit exportProgressChanged(value);
+}
+
+void AssetExporter::onQmlFileLoaded()
+{
+ QTC_ASSERT(m_view && m_view->model(), qCDebug(loggerError) << "Null model"; return);
+ qCDebug(loggerInfo) << "Qml file load done" << m_view->model()->fileUrl();
+ exportComponent(m_view->rootModelNode());
+ QString error;
+ if (!m_view->saveQmlFile(&error)) {
+ ExportNotification::addError(tr("Error saving QML file. %1")
+ .arg(error.isEmpty()? tr("Unknown") : error));
+ }
+ triggerLoadNextFile();
+}
+
+QByteArray AssetExporter::addNodeUUID(ModelNode node)
+{
+ QByteArray uuid = node.auxiliaryData(Constants::UuidTag).toByteArray();
+ qDebug() << node.id() << "UUID" << uuid;
+ if (uuid.isEmpty()) {
+ // Assign a new hash.
+ do {
+ uuid = generateHash(node.id());
+ } while (m_usedHashes.contains(uuid));
+ m_usedHashes.insert(uuid);
+ node.setAuxiliaryData(Constants::UuidAuxTag, QString::fromLatin1(uuid));
+ node.model()->rewriterView()->writeAuxiliaryData();
+ }
+ return uuid;
+}
+
+void AssetExporter::triggerLoadNextFile()
+{
+ QTimer::singleShot(0, this, &AssetExporter::loadNextFile);
+}
+
+void AssetExporter::loadNextFile()
+{
+ if (m_exportFiles.isEmpty()) {
+ notifyProgress(0.8);
+ m_currentState.change(ParsingState::ParsingFinished);
+ writeMetadata();
+ return;
+ }
+
+ // Load the next pending file.
+ const Utils::FilePath file = m_exportFiles.takeFirst();
+ ExportNotification::addInfo(tr("Exporting file %1.").arg(file.toUserOutput()));
+ qCDebug(loggerInfo) << "Loading next file" << file;
+ m_view->loadQmlFile(file);
+}
+
+void AssetExporter::writeMetadata() const
+{
+ Utils::FilePath metadataPath = m_exportPath.pathAppended(m_exportPath.fileName() + ".metadata");
+ ExportNotification::addInfo(tr("Writing metadata to file %1.").
+ arg(metadataPath.toUserOutput()));
+ makeParentPath(metadataPath);
+ m_currentState.change(ParsingState::WritingJson);
+ QJsonObject jsonRoot; // TODO: Write plugin info to root
+ jsonRoot.insert("artboards", m_components);
+ QJsonDocument doc(jsonRoot);
+ if (doc.isNull() || doc.isEmpty()) {
+ ExportNotification::addError(tr("Empty JSON document."));
+ } else {
+ Utils::FileSaver saver(metadataPath.toString(), QIODevice::Text);
+ saver.write(doc.toJson(QJsonDocument::Indented));
+ if (!saver.finalize()) {
+ ExportNotification::addError(tr("Writing metadata failed. %1").
+ arg(saver.errorString()));
+ }
+ }
+ notifyProgress(1.0);
+ ExportNotification::addInfo(tr("Export finished."));
+ m_assetDumper->quitDumper();
+ m_currentState.change(ParsingState::ExportingDone);
+}
+
+AssetExporter::State::State(AssetExporter &exporter) :
+ m_assetExporter(exporter)
+{
+
+}
+
+void AssetExporter::State::change(const ParsingState &state)
+{
+ qCDebug(loggerInfo()) << "Assetimporter State change: Old: " << m_state << "New: " << state;
+ if (m_state != state) {
+ m_state = state;
+ m_assetExporter.stateChanged(m_state);
+ }
+}
+
+QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s)
+{
+ os << static_cast<std::underlying_type<QmlDesigner::AssetExporter::ParsingState>::type>(s);
+ return os;
+}
+
+AssetDumper::AssetDumper():
+ m_quitDumper(false)
+{
+ m_dumpFuture = Utils::runAsync(&AssetDumper::doDumping, this);
+}
+
+AssetDumper::~AssetDumper()
+{
+ abortDumper();
+}
+
+void AssetDumper::dumpAsset(const QPixmap &p, const Utils::FilePath &path)
+{
+ addAsset(p, path);
+}
+
+void AssetDumper::quitDumper()
+{
+ m_quitDumper = true;
+ m_queueCondition.wakeAll();
+ if (!m_dumpFuture.isFinished())
+ m_dumpFuture.waitForFinished();
+}
+
+void AssetDumper::abortDumper()
+{
+ if (!m_dumpFuture.isFinished()) {
+ m_dumpFuture.cancel();
+ m_queueCondition.wakeAll();
+ m_dumpFuture.waitForFinished();
+ }
+}
+
+void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path)
+{
+ QMutexLocker locker(&m_queueMutex);
+ qDebug() << "Save Asset:" << path;
+ m_assets.push({p, path});
+}
+
+void AssetDumper::doDumping(QFutureInterface<void> &fi)
+{
+ auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) {
+ QMutexLocker locker(&m_queueMutex);
+ if (m_assets.empty())
+ return false;
+ *asset = m_assets.front();
+ m_assets.pop();
+ return true;
+ };
+
+ forever {
+ std::pair<QPixmap, Utils::FilePath> asset;
+ if (haveAsset(&asset)) {
+ if (fi.isCanceled())
+ break;
+ savePixmap(asset.first, asset.second);
+ } else {
+ if (m_quitDumper)
+ break;
+ QMutexLocker locker(&m_queueMutex);
+ m_queueCondition.wait(&m_queueMutex);
+ }
+
+ if (fi.isCanceled())
+ break;
+ }
+ fi.reportFinished();
+}
+
+void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const
+{
+ if (p.isNull()) {
+ qCDebug(loggerWarn) << "Dumping null pixmap" << path;
+ return;
+ }
+
+ if (!makeParentPath(path)) {
+ ExportNotification::addError(AssetExporter::tr("Error creating asset directory. %1")
+ .arg(path.fileName()));
+ return;
+ }
+
+ if (!p.save(path.toString())) {
+ ExportNotification::addError(AssetExporter::tr("Error saving asset. %1")
+ .arg(path.fileName()));
+ }
+}
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h
new file mode 100644
index 0000000000..2aa238fb32
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+**
+** 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 "assetexporterview.h"
+#include "utils/fileutils.h"
+
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QSet>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+class QJsonArray;
+QT_END_NAMESPACE
+
+namespace ProjectExplorer {
+class Project;
+}
+
+namespace QmlDesigner {
+class AssetDumper;
+
+class AssetExporter : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum class ParsingState {
+ Idle = 0,
+ Parsing,
+ ParsingFinished,
+ ExportingAssets,
+ ExportingAssetsFinished,
+ WritingJson,
+ ExportingDone
+ };
+
+ AssetExporter(AssetExporterView *view, ProjectExplorer::Project *project,
+ QObject *parent = nullptr);
+ ~AssetExporter();
+
+ void exportQml(const Utils::FilePaths &qmlFiles, const Utils::FilePath &exportPath,
+ bool exportAssets = false);
+
+ void cancel();
+ bool isBusy() const;
+
+ Utils::FilePath exportAsset(const QmlObjectNode& node);
+
+signals:
+ void stateChanged(ParsingState);
+ void exportProgressChanged(double) const;
+
+private:
+ ParsingState currentState() const { return m_currentState.m_state; }
+ void exportComponent(const ModelNode &rootNode);
+ void writeMetadata() const;
+ void notifyLoadError(AssetExporterView::LoadState state);
+ void notifyProgress(double value) const;
+ void triggerLoadNextFile();
+ void loadNextFile();
+
+ void onQmlFileLoaded();
+
+ QByteArray addNodeUUID(ModelNode node);
+
+private:
+ mutable class State {
+ public:
+ State(AssetExporter&);
+ void change(const ParsingState &state);
+ operator ParsingState() const { return m_state; }
+ AssetExporter &m_assetExporter;
+ ParsingState m_state = ParsingState::Idle;
+ } m_currentState;
+ ProjectExplorer::Project *m_project = nullptr;
+ AssetExporterView *m_view = nullptr;
+ Utils::FilePaths m_exportFiles;
+ Utils::FilePath m_exportPath;
+ QJsonArray m_components;
+ QSet<QByteArray> m_usedHashes;
+ std::unique_ptr<AssetDumper> m_assetDumper;
+};
+QDebug operator<< (QDebug os, const QmlDesigner::AssetExporter::ParsingState& s);
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp
new file mode 100644
index 0000000000..e45e48ca0d
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp
@@ -0,0 +1,130 @@
+/****************************************************************************
+**
+** 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 "assetexporterplugin.h"
+
+#include "assetexportpluginconstants.h"
+#include "assetexportdialog.h"
+#include "assetexporter.h"
+#include "assetexporterview.h"
+#include "filepathmodel.h"
+#include "componentexporter.h"
+
+#include "parsers/modelitemnodeparser.h"
+#include "parsers/textnodeparser.h"
+#include "parsers/assetnodeparser.h"
+
+#include "coreplugin/actionmanager/actionmanager.h"
+#include "coreplugin/actionmanager/actioncontainer.h"
+#include "coreplugin/documentmanager.h"
+#include "qmldesigner/qmldesignerplugin.h"
+#include "projectexplorer/projectexplorerconstants.h"
+#include "projectexplorer/session.h"
+#include "projectexplorer/project.h"
+#include "projectexplorer/session.h"
+#include "projectexplorer/taskhub.h"
+
+#include "extensionsystem/pluginmanager.h"
+#include "extensionsystem/pluginspec.h"
+
+#include "utils/algorithm.h"
+
+#include <QCoreApplication>
+#include <QAction>
+
+#include <QLoggingCategory>
+
+namespace QmlDesigner {
+
+AssetExporterPlugin::AssetExporterPlugin() :
+ m_view(new AssetExporterView)
+{
+ ProjectExplorer::TaskHub::addCategory( Constants::TASK_CATEGORY_ASSET_EXPORT,
+ tr("Asset Export"), false);
+
+ auto *designerPlugin = QmlDesigner::QmlDesignerPlugin::instance();
+ auto &viewManager = designerPlugin->viewManager();
+ viewManager.registerViewTakingOwnership(m_view);
+
+ // Add parsers templates for factory instantiation.
+ Component::addNodeParser<ItemNodeParser>();
+ Component::addNodeParser<TextNodeParser>();
+ Component::addNodeParser<AssetNodeParser>();
+
+ // Instantiate actions created by the plugin.
+ addActions();
+
+ connect(ProjectExplorer::SessionManager::instance(),
+ &ProjectExplorer::SessionManager::startupProjectChanged,
+ this, &AssetExporterPlugin::updateActions);
+
+ updateActions();
+}
+
+QString AssetExporterPlugin::pluginName() const
+{
+ return QLatin1String("AssetExporterPlugin");
+}
+
+void AssetExporterPlugin::onExport()
+{
+ auto startupProject = ProjectExplorer::SessionManager::startupProject();
+ if (!startupProject)
+ return;
+
+ FilePathModel model(startupProject);
+ QString exportDirName = startupProject->displayName() + "_export";
+ auto exportDir = startupProject->projectFilePath().parentDir().pathAppended(exportDirName);
+ AssetExporter assetExporter(m_view, startupProject);
+ AssetExportDialog assetExporterDialog(exportDir, assetExporter, model);
+ assetExporterDialog.exec();
+}
+
+void AssetExporterPlugin::addActions()
+{
+ auto exportAction = new QAction(tr("Export QML"));
+ exportAction->setToolTip(tr("Export QML code of the current project."));
+ connect(exportAction, &QAction::triggered, this, &AssetExporterPlugin::onExport);
+ Core::Command *cmd = Core::ActionManager::registerAction(exportAction, Constants::EXPORT_QML);
+
+ // Add action to build menu
+ Core::ActionContainer *buildMenu =
+ Core::ActionManager::actionContainer(ProjectExplorer::Constants::M_BUILDPROJECT);
+ buildMenu->addAction(cmd, ProjectExplorer::Constants::G_BUILD_RUN);
+}
+
+void AssetExporterPlugin::updateActions()
+{
+ auto project = ProjectExplorer::SessionManager::startupProject();
+ QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action();
+ exportAction->setEnabled(project && !project->needsConfiguration());
+}
+
+QString AssetExporterPlugin::metaInfo() const
+{
+ return QLatin1String(":/assetexporterplugin/assetexporterplugin.metainfo");
+}
+
+} //QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h
new file mode 100644
index 0000000000..0615cdb217
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.h
@@ -0,0 +1,56 @@
+/****************************************************************************
+**
+** 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 <iwidgetplugin.h>
+
+
+namespace QmlDesigner {
+class AssetExporter;
+class AssetExporterView;
+class AssetExporterPlugin : public QObject, QmlDesigner::IWidgetPlugin
+{
+ Q_OBJECT
+
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QmlDesignerPlugin" FILE "assetexporterplugin.json")
+
+ Q_DISABLE_COPY(AssetExporterPlugin)
+ Q_INTERFACES(QmlDesigner::IWidgetPlugin)
+
+public:
+ AssetExporterPlugin();
+
+ QString metaInfo() const;
+ QString pluginName() const;
+
+private:
+ void onExport();
+ void addActions();
+ void updateActions();
+
+ AssetExporterView *m_view = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json
new file mode 100644
index 0000000000..a925eaca8e
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.json
@@ -0,0 +1,6 @@
+{
+ "Vendor" : "The Qt Company Ltd",
+ "Category" : "Qt Quick",
+ "Description" : "Plugin for exporting assets and QML from QmlDesigner",
+ "Url" : "http://www.qt.io"
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo
new file mode 100644
index 0000000000..5bfe70cffd
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.metainfo
@@ -0,0 +1,2 @@
+MetaInfo {
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri
new file mode 100644
index 0000000000..713ab1184f
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pri
@@ -0,0 +1,37 @@
+QT *= qml quick core widgets
+
+VPATH += $$PWD
+
+RESOURCES += assetexporterplugin.qrc
+
+INCLUDEPATH += ./
+
+HEADERS += \
+ assetexportdialog.h \
+ assetexporter.h \
+ assetexporterplugin.h \
+ assetexporterview.h \
+ assetexportpluginconstants.h \
+ componentexporter.h \
+ exportnotification.h \
+ filepathmodel.h \
+ parsers/assetnodeparser.h \
+ parsers/modelitemnodeparser.h \
+ parsers/modelnodeparser.h \
+ parsers/textnodeparser.h
+
+SOURCES += \
+ assetexportdialog.cpp \
+ assetexporter.cpp \
+ assetexporterplugin.cpp \
+ assetexporterview.cpp \
+ componentexporter.cpp \
+ exportnotification.cpp \
+ filepathmodel.cpp \
+ parsers/assetnodeparser.cpp \
+ parsers/modelitemnodeparser.cpp \
+ parsers/modelnodeparser.cpp \
+ parsers/textnodeparser.cpp
+
+FORMS += \
+ assetexportdialog.ui
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pro b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pro
new file mode 100644
index 0000000000..2612b06e0e
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.pro
@@ -0,0 +1,17 @@
+include (../../../../qtcreator.pri)
+include (../plugindestdir.pri)
+include (../designercore/iwidgetplugin.pri)
+include (../qmldesigner_dependencies.pri)
+include (assetexporterplugin.pri)
+
+LIBS += -L$$IDE_PLUGIN_PATH
+LIBS += -l$$qtLibraryName(QmlDesigner)
+LIBS += -l$$qtLibraryName(ExtensionSystem)
+LIBS += -l$$qtLibraryName(Core)
+LIBS += -l$$qtLibraryName(ProjectExplorer)
+LIBS += -l$$qtLibraryName(Utils)
+
+TARGET = assetexporterplugin
+TEMPLATE = lib
+CONFIG += plugin
+
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs
new file mode 100644
index 0000000000..e847525324
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qbs
@@ -0,0 +1,59 @@
+import qbs
+
+QtcProduct {
+ name: "assetexporterplugin"
+ type: ["dynamiclibrary"]
+ installDir: qtc.ide_plugin_path + '/' + installDirName
+ property string installDirName: qbs.targetOS.contains("macos") ? "QmlDesigner" : "qmldesigner"
+
+ Depends { name: "Core" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "QmlDesigner" }
+ Depends { name: "Utils" }
+
+ cpp.includePaths: base.concat([
+ "./",
+ "../designercore/include",
+ "../../../../share/qtcreator/qml/qmlpuppet/interfaces",
+ "../../../../share/qtcreator/qml/qmlpuppet/types"
+ ])
+
+ Properties {
+ condition: qbs.targetOS.contains("unix")
+ cpp.internalVersion: ""
+ }
+
+ Group {
+ name: "plugin metadata"
+ files: ["assetexporterplugin.json"]
+ fileTags: ["qt_plugin_metadata"]
+ }
+
+ files: [
+ "assetexportdialog.cpp",
+ "assetexportdialog.h",
+ "assetexportdialog.ui",
+ "assetexporter.cpp",
+ "assetexporter.h",
+ "assetexporterplugin.cpp",
+ "assetexporterplugin.h",
+ "assetexporterplugin.qrc",
+ "assetexporterview.cpp",
+ "assetexporterview.h",
+ "assetexportpluginconstants.h",
+ "componentexporter.cpp",
+ "componentexporter.h",
+ "exportnotification.cpp",
+ "exportnotification.h",
+ "filepathmodel.cpp",
+ "filepathmodel.h",
+ "parsers/assetnodeparser.cpp",
+ "parsers/assetnodeparser.h",
+ "parsers/modelitemnodeparser.cpp",
+ "parsers/modelitemnodeparser.h",
+ "parsers/modelnodeparser.cpp",
+ "parsers/modelnodeparser.h",
+ "parsers/textnodeparser.cpp",
+ "parsers/textnodeparser.h"
+ ]
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc
new file mode 100644
index 0000000000..8db1e0adaf
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/assetexporterplugin">
+ <file>assetexporterplugin.metainfo</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp
new file mode 100644
index 0000000000..fbb4173249
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.cpp
@@ -0,0 +1,161 @@
+/****************************************************************************
+**
+** 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 "assetexporterview.h"
+
+#include "qmlitemnode.h"
+#include "rewriterview.h"
+
+#include "coreplugin/editormanager/editormanager.h"
+#include "coreplugin/editormanager/ieditor.h"
+#include "coreplugin/modemanager.h"
+#include "coreplugin/coreconstants.h"
+
+#include <QLoggingCategory>
+
+namespace {
+Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.view", QtInfoMsg)
+Q_LOGGING_CATEGORY(loggerWarn, "qtc.designer.assetExportPlugin.view", QtWarningMsg)
+Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.view", QtCriticalMsg)
+
+static const int RetryIntervalMs = 500;
+static const int MinRetry = 2;
+}
+
+namespace QmlDesigner {
+
+AssetExporterView::AssetExporterView(QObject *parent) : AbstractView(parent),
+ m_timer(this)
+{
+ m_timer.setInterval(RetryIntervalMs);
+ // We periodically check if file is loaded.
+ connect(&m_timer, &QTimer::timeout, this, &AssetExporterView::handleTimerTimeout);
+}
+
+
+bool AssetExporterView::loadQmlFile(const Utils::FilePath &path, uint timeoutSecs)
+{
+ qCDebug(loggerInfo) << "Load file" << path;
+ if (loadingState() == LoadState::Busy)
+ return false;
+
+ setState(LoadState::Busy);
+ m_retryCount = std::max(MinRetry, static_cast<int>((timeoutSecs * 1000) / RetryIntervalMs));
+ m_currentEditor = Core::EditorManager::openEditor(path.toString(), Core::Id(),
+ Core::EditorManager::DoNotMakeVisible);
+ Core::ModeManager::activateMode(Core::Constants::MODE_DESIGN);
+ Core::ModeManager::setFocusToCurrentMode();
+ m_timer.start();
+ return true;
+}
+
+bool AssetExporterView::saveQmlFile(QString *error) const
+{
+ if (!m_currentEditor) {
+ qCDebug(loggerWarn) << "Saving QML file failed. No editor.";
+ return false;
+ }
+ return m_currentEditor->document()->save(error);
+}
+
+void AssetExporterView::modelAttached(Model *model)
+{
+ if (model->rewriterView() && model->rewriterView()->inErrorState())
+ setState(LoadState::QmlErrorState);
+
+ AbstractView::modelAttached(model);
+}
+
+void AssetExporterView::
+instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash)
+{
+ if (inErrorState() || loadingState() == LoadState::Loaded)
+ return; // Already reached error or connected state.
+
+ // We expect correct dimensions are available if the rootnode's
+ // information change message is received.
+ const auto nodes = informationChangeHash.keys();
+ bool hasRootNode = std::any_of(nodes.begin(), nodes.end(), [](const ModelNode &n) {
+ return n.isRootNode();
+ });
+ if (hasRootNode)
+ handleMaybeDone();
+}
+
+void AssetExporterView::instancesPreviewImageChanged(const QVector<ModelNode> &nodeList)
+{
+ Q_UNUSED(nodeList);
+ emit previewChanged();
+}
+
+bool AssetExporterView::inErrorState() const
+{
+ return m_state == LoadState::Exausted || m_state == LoadState::QmlErrorState;
+}
+
+bool AssetExporterView::isLoaded() const
+{
+ return isAttached() && QmlItemNode(rootModelNode()).isValid();
+}
+
+void AssetExporterView::setState(AssetExporterView::LoadState state)
+{
+ if (state != m_state) {
+ m_state = state;
+ qCDebug(loggerInfo) << "Loading state changed" << m_state;
+ if (inErrorState() || m_state == LoadState::Loaded) {
+ m_timer.stop();
+ // TODO: Send the loaded signal with a delay. The assumption that model attached and a
+ // valid root object is enough to declare a QML file is ready is incorrect. A ideal
+ // solution would be that the puppet notifies file ready signal.
+ if (m_state == LoadState::Loaded)
+ QTimer::singleShot(2000, this, &AssetExporterView::loadingFinished);
+ else
+ emit loadingError(m_state);
+ }
+ }
+}
+
+void AssetExporterView::handleMaybeDone()
+{
+ if (isLoaded())
+ setState(LoadState::Loaded);
+}
+
+void AssetExporterView::handleTimerTimeout()
+{
+ if (!inErrorState() && loadingState() != LoadState::Loaded)
+ handleMaybeDone();
+
+ if (--m_retryCount < 0)
+ setState(LoadState::Exausted);
+}
+
+}
+
+QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s)
+{
+ os << static_cast<std::underlying_type<QmlDesigner::AssetExporterView::LoadState>::type>(s);
+ return os;
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h
new file mode 100644
index 0000000000..46c2c77071
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterview.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** 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 "abstractview.h"
+
+#include "utils/fileutils.h"
+
+#include <QObject>
+#include <QTimer>
+
+#include <memory>
+
+namespace Core {
+class IEditor;
+}
+namespace QmlDesigner {
+
+
+class AssetExporterView : public AbstractView
+{
+ Q_OBJECT
+public:
+ enum class LoadState {
+ Idle = 1,
+ Busy,
+ Exausted,
+ QmlErrorState,
+ Loaded
+ };
+
+ AssetExporterView(QObject *parent = nullptr);
+
+ bool loadQmlFile(const Utils::FilePath &path, uint timeoutSecs = 10);
+ bool saveQmlFile(QString *error) const;
+
+ void modelAttached(Model *model) override;
+ void instanceInformationsChanged(const QMultiHash<ModelNode, InformationName> &informationChangeHash) override;
+ void instancesPreviewImageChanged(const QVector<ModelNode> &nodeList) override;
+
+ LoadState loadingState() const { return m_state; }
+ bool inErrorState() const;
+
+signals:
+ void loadingFinished();
+ void loadingError(LoadState);
+ void previewChanged();
+
+private:
+ bool isLoaded() const;
+ void setState(LoadState state);
+ void handleMaybeDone();
+ void handleTimerTimeout();
+
+ Core::IEditor *m_currentEditor = nullptr;
+ QTimer m_timer;
+ int m_retryCount = 0;
+ LoadState m_state = LoadState::Idle;
+ bool m_waitForPuppet = false;
+};
+
+}
+
+QDebug operator<<(QDebug os, const QmlDesigner::AssetExporterView::LoadState &s);
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h
new file mode 100644
index 0000000000..1937c7126e
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportpluginconstants.h
@@ -0,0 +1,78 @@
+/****************************************************************************
+**
+** 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 {
+namespace Constants {
+
+const char EXPORT_QML[] = "Designer.ExportPlugin.ExportQml";
+
+const char TASK_CATEGORY_ASSET_EXPORT[] = "AssetExporter.Export";
+const char UuidAuxTag[] = "uuid";
+
+//***************************************************************************
+// Metadata tags
+//***************************************************************************
+// Plugin info tags
+const char PluginInfoTag[] = "pluginInfo";
+const char MetadataVersionTag[] = "metadataVersion";
+
+const char DocumentInfoTag[] = "documentInfo";
+const char DocumentNameTag[] = "name";
+
+// Layer data tags
+const char ArtboardListTag[] = "artboards";
+
+const char XPosTag[] = "x";
+const char YPosTag[] = "y";
+const char WidthTag[] = "width";
+const char HeightTag[] = "height";
+
+
+const char QmlIdTag[] = "qmlId";
+const char ExportTypeTag[] = "exportType";
+const char QmlPropertiesTag[] = "qmlProperties";
+const char ImportsTag[] = "extraImports";
+const char UuidTag[] = "uuid";
+const char ClipTag[] = "clip";
+const char AssetDataTag[] = "assetData";
+const char AssetPathTag[] = "assetPath";
+const char AssetBoundsTag[] = "assetBounds";
+const char OpacityTag[] = "opacity";
+
+const char TextDetailsTag[] = "textDetails";
+const char FontFamilyTag[] = "fontFamily";
+const char FontSizeTag[] = "fontSize";
+const char FontStyleTag[] = "fontStyle";
+const char LetterSpacingTag[] = "kerning";
+const char TextColorTag[] = "textColor";
+const char TextContentTag[] = "contents";
+const char IsMultilineTag[] = "multiline";
+const char LineHeightTag[] = "lineHeight";
+const char HAlignTag[] = "horizontalAlignment";
+const char VAlignTag[] = "verticalAlignment";
+
+}
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp
new file mode 100644
index 0000000000..819fa3d328
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.cpp
@@ -0,0 +1,119 @@
+/****************************************************************************
+**
+** 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 "componentexporter.h"
+#include "parsers/modelnodeparser.h"
+
+#include "model.h"
+#include "nodeabstractproperty.h"
+#include "rewriterview.h"
+
+#include "utils/qtcassert.h"
+
+#include <QJsonArray>
+#include <QJsonObject>
+#include <QLoggingCategory>
+
+namespace {
+Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.modelExporter", QtInfoMsg)
+
+static void populateLineage(const QmlDesigner::ModelNode &node, QByteArrayList &lineage)
+{
+ if (!node.isValid() || node.type().isEmpty())
+ return;
+ lineage.append(node.type());
+ if (node.hasParentProperty())
+ populateLineage(node.parentProperty().parentModelNode(), lineage);
+}
+
+}
+
+namespace QmlDesigner {
+
+std::vector<std::unique_ptr<Internal::NodeParserCreatorBase>> Component::m_readers;
+Component::Component(AssetExporter &exporter, const ModelNode &rootNode):
+ m_exporter(exporter),
+ m_rootNode(rootNode)
+{
+
+}
+
+QJsonObject Component::json() const
+{
+ return m_json;
+}
+
+AssetExporter &Component::exporter()
+{
+ return m_exporter;
+}
+
+void Component::exportComponent()
+{
+ QTC_ASSERT(m_rootNode.isValid(), return);
+ m_json = nodeToJson(m_rootNode);
+}
+
+ModelNodeParser *Component::createNodeParser(const ModelNode &node) const
+{
+ QByteArrayList lineage;
+ populateLineage(node, lineage);
+ std::unique_ptr<ModelNodeParser> reader;
+ for (auto &parserCreator: m_readers) {
+ std::unique_ptr<ModelNodeParser> r(parserCreator->instance(lineage, node));
+ if (r->isExportable()) {
+ if (reader) {
+ if (reader->priority() < r->priority())
+ reader = std::move(r);
+ } else {
+ reader = std::move(r);
+ }
+ }
+ }
+
+ if (!reader)
+ qCDebug(loggerInfo()) << "No parser for node" << node;
+
+ return reader.release();
+}
+
+QJsonObject Component::nodeToJson(const ModelNode &node)
+{
+ QJsonObject jsonObject;
+ std::unique_ptr<ModelNodeParser> parser(createNodeParser(node));
+ if (parser)
+ jsonObject = parser->json(*this);
+
+ QJsonArray children;
+ for (const ModelNode &childnode : node.directSubModelNodes())
+ children.append(nodeToJson(childnode));
+
+ if (!children.isEmpty())
+ jsonObject.insert("children", children);
+
+ return jsonObject;
+}
+
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h
new file mode 100644
index 0000000000..3668f372bf
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/componentexporter.h
@@ -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.
+**
+****************************************************************************/
+#pragma once
+
+#include <QJsonObject>
+#include <QByteArrayList>
+
+#include <memory>
+
+#include "utils/qtcassert.h"
+
+QT_BEGIN_NAMESPACE
+class QJsonArray;
+QT_END_NAMESPACE
+
+namespace QmlDesigner {
+class AssetExporter;
+class ModelNode;
+class Component;
+class ModelNodeParser;
+
+namespace Internal {
+class NodeParserCreatorBase
+{
+public:
+ virtual ~NodeParserCreatorBase() {}
+protected:
+ virtual ModelNodeParser *instance(const QByteArrayList &, const ModelNode &) const = 0;
+ friend class QmlDesigner::Component;
+};
+
+template<class T>
+class NodeParserCreator : public NodeParserCreatorBase
+{
+public:
+ NodeParserCreator() = default;
+ ~NodeParserCreator() = default;
+
+protected:
+ ModelNodeParser *instance(const QByteArrayList &lineage, const ModelNode &node) const {
+ return new T(lineage, node);
+ }
+};
+} //Internal
+
+class Component
+{
+public:
+ Component(AssetExporter& exporter, const ModelNode &rootNode);
+
+ void exportComponent();
+ QJsonObject json() const;
+
+ AssetExporter &exporter();
+
+ template<typename T> static void addNodeParser()
+ {
+ QTC_ASSERT((std::is_base_of<ModelNodeParser, T>::value), return);
+ m_readers.push_back(std::make_unique<Internal::NodeParserCreator<T>>());
+ }
+private:
+ ModelNodeParser* createNodeParser(const ModelNode &node) const;
+ QJsonObject nodeToJson(const ModelNode &node);
+
+private:
+ AssetExporter& m_exporter;
+ const ModelNode &m_rootNode;
+ QJsonObject m_json;
+ static std::vector<std::unique_ptr<Internal::NodeParserCreatorBase>> m_readers;
+};
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp
new file mode 100644
index 0000000000..d4259c3fe2
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.cpp
@@ -0,0 +1,58 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
+**
+** This file is part of the Qt Asset Importer module.
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+******************************************************************************/
+#include "exportnotification.h"
+#include "assetexportpluginconstants.h"
+
+#include "projectexplorer/taskhub.h"
+
+#include <QLoggingCategory>
+
+namespace {
+Q_LOGGING_CATEGORY(loggerDebug, "qtc.designer.assetExportPlugin.exportNotification", QtDebugMsg)
+}
+
+using namespace ProjectExplorer;
+namespace {
+static void addTask(Task::TaskType type, const QString &desc)
+{
+ qCDebug(loggerDebug) << desc;
+ Task task(type, desc, {}, -1, QmlDesigner::Constants::TASK_CATEGORY_ASSET_EXPORT);
+ TaskHub::addTask(task);
+}
+}
+
+namespace QmlDesigner {
+
+void ExportNotification::addError(const QString &errMsg)
+{
+ addTask(Task::Error, errMsg);
+}
+
+void ExportNotification::addWarning(const QString &warningMsg)
+{
+ addTask(Task::Warning, warningMsg);
+}
+
+void ExportNotification::addInfo(const QString &infoMsg)
+{
+ addTask(Task::Unknown, infoMsg);
+}
+} // QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h
new file mode 100644
index 0000000000..23fab1b8fa
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/exportnotification.h
@@ -0,0 +1,33 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at http://www.qt.io/contact-us
+**
+** This file is part of the Qt Asset Importer module.
+**
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://www.qt.io/contact-us
+**
+******************************************************************************/
+#pragma once
+
+#include <QString>
+
+namespace QmlDesigner {
+class ExportNotification
+{
+public:
+ static void addError(const QString &errMsg);
+ static void addWarning(const QString &warningMsg);
+ static void addInfo(const QString &infoMsg);
+};
+} // QmlDesigner
diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp
new file mode 100644
index 0000000000..36c175414b
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp
@@ -0,0 +1,163 @@
+/****************************************************************************
+**
+** 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 "filepathmodel.h"
+
+#include "exportnotification.h"
+
+#include "projectexplorer/project.h"
+#include "projectexplorer/projectnodes.h"
+#include "utils/runextensions.h"
+
+#include <QLoggingCategory>
+#include <QTimer>
+
+using namespace ProjectExplorer;
+
+namespace {
+Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg)
+Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg)
+
+void findQmlFiles(QFutureInterface<Utils::FilePath> &f, const Project *project)
+{
+ if (!project && !f.isCanceled())
+ f.reportFinished({});
+
+ int index = 0;
+ Utils::FilePaths qmlFiles = project->files([&f, &index](const Node* node) ->bool {
+ if (f.isCanceled())
+ return false;
+ Utils::FilePath path = node->filePath();
+ bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
+ if (isComponent && node->filePath().endsWith(".ui.qml"))
+ f.reportResult(path, index++);
+ return true;
+ });
+ f.reportFinished();
+}
+}
+
+namespace QmlDesigner {
+
+FilePathModel::FilePathModel(ProjectExplorer::Project *project, QObject *parent)
+ : QAbstractListModel(parent),
+ m_project(project)
+{
+ QTimer::singleShot(0, this, &FilePathModel::processProject);
+}
+
+FilePathModel::~FilePathModel()
+{
+ if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
+ !m_preprocessWatcher->isFinished()) {
+ ExportNotification::addInfo(tr("Canceling QML files preparation."));
+ m_preprocessWatcher->cancel();
+ m_preprocessWatcher->waitForFinished();
+ qCDebug(loggerInfo) << "Canceling QML files preparation done.";
+ }
+}
+
+Qt::ItemFlags FilePathModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags itemFlags = QAbstractListModel::flags(index);
+ if (index.isValid())
+ itemFlags |= Qt::ItemIsUserCheckable;
+ return itemFlags;
+}
+
+int FilePathModel::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid())
+ return m_files.count();
+ return 0;
+}
+
+QVariant FilePathModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return {};
+
+ switch (role) {
+ case Qt::DisplayRole:
+ return m_files[index.row()].toUserOutput();
+ case Qt::CheckStateRole:
+ return m_skipped.count(m_files[index.row()]) ? Qt::Unchecked : Qt::Checked;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+bool FilePathModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid() || role != Qt::CheckStateRole)
+ return false;
+
+ const Utils::FilePath path = m_files[index.row()];
+ if (value == Qt::Checked)
+ m_skipped.erase(path);
+ else
+ m_skipped.insert(path);
+
+ emit dataChanged(index, index);
+ return true;
+}
+
+Utils::FilePaths FilePathModel::files() const
+{
+ Utils::FilePaths selectedPaths;
+ std::copy_if(m_files.begin(), m_files.end(), std::back_inserter(selectedPaths),
+ [this](const Utils::FilePath &path) {
+ return !m_skipped.count(path);
+ });
+ return selectedPaths;
+}
+
+void FilePathModel::processProject()
+{
+ if (m_preprocessWatcher && !m_preprocessWatcher->isCanceled() &&
+ !m_preprocessWatcher->isFinished()) {
+ qCDebug(loggerError) << "Previous model load not finished.";
+ return;
+ }
+
+ beginResetModel();
+ m_preprocessWatcher.reset(new QFutureWatcher<Utils::FilePath>(this));
+ connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::resultReadyAt, this,
+ [this](int resultIndex) {
+ beginInsertRows(index(0, 0) , m_files.count(), m_files.count());
+ m_files.append(m_preprocessWatcher->resultAt(resultIndex));
+ endInsertRows();
+ });
+
+ connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
+ this, &FilePathModel::endResetModel);
+
+ QFuture<Utils::FilePath> f = Utils::runAsync(&findQmlFiles, m_project);
+ m_preprocessWatcher->setFuture(f);
+}
+
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h
new file mode 100644
index 0000000000..91a800c036
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.h
@@ -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.
+**
+****************************************************************************/
+#pragma once
+#include <QAbstractListModel>
+#include <QFutureWatcher>
+
+#include "utils/fileutils.h"
+
+#include <memory>
+#include <unordered_set>
+
+namespace ProjectExplorer {
+class Project;
+}
+
+namespace QmlDesigner {
+class FilePathModel : public QAbstractListModel
+{
+public:
+ FilePathModel(ProjectExplorer::Project *project, QObject *parent = nullptr);
+ ~FilePathModel() override;
+
+ Qt::ItemFlags flags(const QModelIndex &index) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+
+ Utils::FilePaths files() const;
+private:
+ void processProject();
+
+ ProjectExplorer::Project *m_project = nullptr;
+ std::unique_ptr<QFutureWatcher<Utils::FilePath>> m_preprocessWatcher;
+ std::unordered_set<Utils::FilePath> m_skipped;
+ Utils::FilePaths m_files;
+};
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp
new file mode 100644
index 0000000000..159eccec46
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.cpp
@@ -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.
+**
+****************************************************************************/
+
+#include "assetnodeparser.h"
+#include "assetexportpluginconstants.h"
+#include "assetexporter.h"
+
+#include "qmlitemnode.h"
+#include "componentexporter.h"
+
+#include "utils/fileutils.h"
+
+#include <QPixmap>
+
+namespace QmlDesigner {
+using namespace Constants;
+AssetNodeParser::AssetNodeParser(const QByteArrayList &lineage, const ModelNode &node) :
+ ItemNodeParser(lineage, node)
+{
+
+}
+
+bool AssetNodeParser::isExportable() const
+{
+ auto hasType = [this](const QByteArray &type) {
+ return lineage().contains(type);
+ };
+ return hasType("QtQuick.Image") || hasType("QtQuick.Rectangle");
+}
+
+QJsonObject AssetNodeParser::json(Component &component) const
+{
+ QJsonObject jsonObject = ItemNodeParser::json(component);
+
+ QPixmap asset = objectNode().toQmlItemNode().instanceRenderPixmap();
+ Utils::FilePath assetPath = component.exporter().exportAsset(objectNode());
+
+ QJsonObject assetData;
+ assetData.insert(AssetPathTag, assetPath.toString());
+ jsonObject.insert(AssetDataTag, assetData);
+ return jsonObject;
+}
+}
+
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h
new file mode 100644
index 0000000000..be764b17ec
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/assetnodeparser.h
@@ -0,0 +1,42 @@
+/****************************************************************************
+**
+** 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 "modelitemnodeparser.h"
+
+namespace QmlDesigner {
+class Component;
+
+class AssetNodeParser : public ItemNodeParser
+{
+public:
+ AssetNodeParser(const QByteArrayList &lineage, const ModelNode &node);
+ ~AssetNodeParser() override = default;
+
+ bool isExportable() const override;
+ int priority() const override { return 200; }
+ QJsonObject json(Component &component) const override;
+};
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp
new file mode 100644
index 0000000000..355983f221
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.cpp
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** 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 "modelitemnodeparser.h"
+#include "assetexportpluginconstants.h"
+
+#include "qmlitemnode.h"
+
+namespace QmlDesigner {
+using namespace Constants;
+ItemNodeParser::ItemNodeParser(const QByteArrayList &lineage,
+ const ModelNode &node) :
+ ModelNodeParser(lineage, node)
+{
+
+}
+
+bool QmlDesigner::ItemNodeParser::isExportable() const
+{
+ return lineage().contains("QtQuick.Item");
+}
+
+QJsonObject QmlDesigner::ItemNodeParser::json(QmlDesigner::Component &component) const
+{
+ Q_UNUSED(component);
+ const QmlObjectNode &qmlObjectNode = objectNode();
+ QJsonObject jsonObject;
+ jsonObject.insert(QmlIdTag, qmlObjectNode.id());
+ QmlItemNode itemNode = qmlObjectNode.toQmlItemNode();
+
+ // Position relative to parent
+ QPointF pos = itemNode.instancePosition();
+ jsonObject.insert(XPosTag, pos.x());
+ jsonObject.insert(YPosTag, pos.y());
+
+ // size
+ QSizeF size = itemNode.instanceSize();
+ jsonObject.insert(WidthTag, size.width());
+ jsonObject.insert(HeightTag, size.height());
+
+ return jsonObject;
+}
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.h b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.h
new file mode 100644
index 0000000000..503fb4c2e9
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelitemnodeparser.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 "modelnodeparser.h"
+
+namespace QmlDesigner {
+class ModelNode;
+class Component;
+
+class ItemNodeParser : public ModelNodeParser
+{
+public:
+ ItemNodeParser(const QByteArrayList &lineage, const ModelNode &node);
+
+ ~ItemNodeParser() override = default;
+
+ int priority() const override { return 100; }
+ bool isExportable() const override;
+ QJsonObject json(Component &component) const override;
+};
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.cpp
new file mode 100644
index 0000000000..31787b83cc
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.cpp
@@ -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.
+**
+****************************************************************************/
+#include "modelnodeparser.h"
+
+namespace QmlDesigner {
+ModelNodeParser::ModelNodeParser(const QByteArrayList &lineage, const ModelNode &node) :
+ m_node(node),
+ m_objectNode(node),
+ m_lineage(lineage)
+{
+
+}
+
+QVariant ModelNodeParser::propertyValue(const PropertyName &name) const
+{
+ return m_objectNode.instanceValue(name);
+}
+
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.h b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.h
new file mode 100644
index 0000000000..4ca17746e8
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/modelnodeparser.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 "qmlobjectnode.h"
+
+#include <QJsonObject>
+#include <QByteArrayList>
+
+namespace QmlDesigner {
+class Component;
+class ModelNode;
+
+class ModelNodeParser
+{
+public:
+ ModelNodeParser(const QByteArrayList &lineage, const ModelNode &node);
+
+ virtual ~ModelNodeParser() = default;
+
+ virtual int priority() const = 0;
+ virtual bool isExportable() const = 0;
+ virtual QJsonObject json(Component& component) const = 0;
+
+ const QByteArrayList& lineage() const { return m_lineage; }
+ const QmlObjectNode& objectNode() const { return m_objectNode; }
+ QVariant propertyValue(const PropertyName &name) const;
+
+protected:
+ const ModelNode &m_node;
+
+private:
+ QmlObjectNode m_objectNode;
+ QByteArrayList m_lineage;
+};
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp
new file mode 100644
index 0000000000..12b73c4506
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.cpp
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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 "textnodeparser.h"
+#include "assetexportpluginconstants.h"
+
+#include <QColor>
+#include <QHash>
+
+namespace {
+const QHash<QString, QString> AlignMapping{
+ {"AlignRight", "RIGHT"},
+ {"AlignHCenter", "CENTER"},
+ {"AlignJustify", "JUSTIFIED"},
+ {"AlignLeft", "LEFT"},
+ {"AlignTop", "TOP"},
+ {"AlignVCenter", "CENTER"},
+ {"AlignBottom", "BOTTOM"}
+};
+
+QString toJsonAlignEnum(QString value) {
+ if (value.isEmpty() || !AlignMapping.contains(value))
+ return "";
+ return AlignMapping[value];
+}
+}
+
+
+namespace QmlDesigner {
+using namespace Constants;
+TextNodeParser::TextNodeParser(const QByteArrayList &lineage, const ModelNode &node) :
+ ItemNodeParser(lineage, node)
+{
+
+}
+
+bool TextNodeParser::isExportable() const
+{
+ return lineage().contains("QtQuick.Text");
+}
+
+QJsonObject TextNodeParser::json(Component &component) const
+{
+ Q_UNUSED(component);
+ QJsonObject jsonObject = ItemNodeParser::json(component);
+
+ QJsonObject textDetails;
+ textDetails.insert(TextContentTag, propertyValue("text").toString());
+ textDetails.insert(FontFamilyTag, propertyValue("font.family").toString());
+ textDetails.insert(FontStyleTag, propertyValue("font.styleName").toString());
+ textDetails.insert(FontSizeTag, propertyValue("font.pixelSize").toInt());
+ textDetails.insert(LetterSpacingTag, propertyValue("font.letterSpacing").toFloat());
+
+ QColor fontColor(propertyValue("font.color").toString());
+ textDetails.insert(TextColorTag, fontColor.name(QColor::HexArgb));
+
+ textDetails.insert(HAlignTag, toJsonAlignEnum(propertyValue("horizontalAlignment").toString()));
+ textDetails.insert(VAlignTag, toJsonAlignEnum(propertyValue("verticalAlignment").toString()));
+
+ textDetails.insert(IsMultilineTag, propertyValue("wrapMode").toString().compare("NoWrap") != 0);
+
+ jsonObject.insert(TextDetailsTag, textDetails);
+ return jsonObject;
+}
+}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.h b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.h
new file mode 100644
index 0000000000..c05d5c8f88
--- /dev/null
+++ b/src/plugins/qmldesigner/assetexporterplugin/parsers/textnodeparser.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 "modelitemnodeparser.h"
+
+namespace QmlDesigner {
+class Component;
+
+class TextNodeParser : public ItemNodeParser
+{
+public:
+ TextNodeParser(const QByteArrayList &lineage, const ModelNode &node);
+ ~TextNodeParser() override = default;
+
+ bool isExportable() const override;
+ int priority() const override { return 200; }
+ QJsonObject json(Component &component) const override;
+};
+
+}
diff --git a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
index 792666ec36..2fa19a5c24 100644
--- a/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
+++ b/src/plugins/qmldesigner/components/componentcore/componentcore_constants.h
@@ -83,6 +83,8 @@ const char decreaseIndexOfStackedContainerCommandId[] = "DecreaseIndexOfStackedC
const char flowAssignEffectCommandId[] = "AssignFlowEffect";
const char flowAssignCustomEffectCommandId[] = "AssignFlowCustomEffect";
const char addToGroupItemCommandId[] = "AddToGroupItem";
+const char fitRootToScreenCommandId[] = "FitRootToScreen";
+const char fitSelectionToScreenCommandId[] = "FitSelectionToScreen";
const char selectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Selection");
const char flowConnectionCategoryDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Connect");
@@ -155,6 +157,9 @@ const char layoutFillHeightDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContext
const char flowAssignEffectDisplayName[] = "Assign FlowEffect ";
const char flowAssignCustomEffectDisplayName[] = "Assign Custom FlowEffect ";
+const char fitRootToScreenDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit root to screen");
+const char fitSelectionToScreenDisplayName[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit selection to screen");
+
const char raiseToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Raise selected item.");
const char lowerToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Lower selected item.");
@@ -173,6 +178,9 @@ const char decreaseIndexOfStackedContainerToolTip[] = QT_TRANSLATE_NOOP("QmlDesi
const char addItemToStackedContainerToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add item to stacked container.");
const char addFlowActionToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Add flow action.");
+const char fitRootToScreenToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit the root element inside the available space.");
+const char fitSelectionToScreenToolTip[] = QT_TRANSLATE_NOOP("QmlDesignerContextMenu", "Fit the selected elements inside the available space.");
+
const int priorityFirst = 280;
const int prioritySelectionCategory = 220;
const int priorityQmlPreviewCategory = 200;
diff --git a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
index 2dbf8b00b4..479f40203c 100644
--- a/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/designeractionmanager.cpp
@@ -821,6 +821,27 @@ void DesignerActionManager::createDefaultDesignerActions()
&resetSize,
&selectionNotEmptyAndHasWidthOrHeightProperty));
+ addDesignerAction(new ModelNodeAction(
+ fitRootToScreenCommandId,
+ fitRootToScreenDisplayName,
+ Utils::Icon({{":/utils/images/fittoview.png", Utils::Theme::IconsBaseColor}}).icon(),
+ fitRootToScreenToolTip,
+ genericToolBarCategory,
+ QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_0),
+ 182,
+ &fitRootToScreen));
+
+ addDesignerAction(new ModelNodeAction(
+ fitSelectionToScreenCommandId,
+ fitSelectionToScreenDisplayName,
+ Utils::Icon({{":/utils/images/fittoview.png", Utils::Theme::IconsBaseColor}}).icon(),
+ fitSelectionToScreenToolTip,
+ genericToolBarCategory,
+ QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_I),
+ 183,
+ &fitSelectionToScreen,
+ &selectionNotEmpty));
+
addDesignerAction(new SeperatorDesignerAction(editCategory, 170));
addDesignerAction(new VisiblityModelNodeAction(
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
index 98148b7bd3..da77f26af7 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.cpp
@@ -329,6 +329,23 @@ void resetPosition(const SelectionContext &selectionState)
});
}
+void fitRootToScreen(const SelectionContext &selectionState)
+{
+ if (!selectionState.view())
+ return;
+
+ selectionState.view()->emitCustomNotification(QStringLiteral("fit root to screen"));
+}
+
+void fitSelectionToScreen(const SelectionContext &selectionState)
+{
+ if (!selectionState.view())
+ return;
+
+ selectionState.view()->emitCustomNotification(QStringLiteral("fit selection to screen"),
+ selectionState.selectedModelNodes());
+}
+
void goIntoComponentOperation(const SelectionContext &selectionState)
{
goIntoComponent(selectionState.currentSingleSelectedNode());
diff --git a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
index afd8416bf9..0a8e094a7e 100644
--- a/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
+++ b/src/plugins/qmldesigner/components/componentcore/modelnodeoperations.h
@@ -49,6 +49,8 @@ void setFillWidth(const SelectionContext &selectionState);
void setFillHeight(const SelectionContext &selectionState);
void resetSize(const SelectionContext &selectionState);
void resetPosition(const SelectionContext &selectionState);
+void fitRootToScreen(const SelectionContext &selectionState);
+void fitSelectionToScreen(const SelectionContext &selectionState);
void goIntoComponentOperation(const SelectionContext &selectionState);
void setId(const SelectionContext &selectionState);
void resetZ(const SelectionContext &selectionState);
diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
index 95ed102f82..4f1d1c3da9 100644
--- a/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.cpp
@@ -30,7 +30,7 @@
namespace QmlDesigner {
-const int defaultZoomIndex = 11;
+const int defaultZoomIndex = 13;
ZoomAction::ZoomAction(QObject *parent)
: QWidgetAction(parent),
@@ -69,14 +69,19 @@ void ZoomAction::setZoomLevel(float zoomLevel)
if (qFuzzyCompare(m_zoomLevel, zoomLevel))
return;
+ forceZoomLevel(zoomLevel);
+}
+
+void ZoomAction::forceZoomLevel(float zoomLevel)
+{
m_zoomLevel = qBound(0.01f, zoomLevel, 16.0f);
emit zoomLevelChanged(m_zoomLevel);
}
//initial m_zoomLevel and m_currentComboBoxIndex
-const QVector<float> s_zoomFactors = {0.01f, 0.02f, 0.05f, 0.0625f, 0.125f, 0.25f,
- 0.33f, 0.5f, 0.66f, 0.75f, 0.9f, 1.0f, 1.25f,
- 1.5f, 1.75f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f, 10.0f, 16.0f };
+const QVector<float> s_zoomFactors = {0.01f, 0.02f, 0.05f, 0.0625f, 0.1f, 0.125f, 0.2f, 0.25f,
+ 0.33f, 0.5f, 0.66f, 0.75f, 0.9f, 1.0f, 1.1f, 1.25f, 1.33f,
+ 1.5f, 1.66f, 1.75f, 2.0f, 3.0f, 4.0f, 6.0f, 8.0f, 10.0f, 16.0f };
int getZoomIndex(float zoom)
{
@@ -87,6 +92,15 @@ int getZoomIndex(float zoom)
return -1;
}
+float ZoomAction::getClosestZoomLevel(float zoomLevel)
+{
+ int i = 0;
+ while (i < s_zoomFactors.size() && s_zoomFactors[i] < zoomLevel)
+ ++i;
+
+ return s_zoomFactors[qBound(0, i - 1, s_zoomFactors.size() - 1)];
+}
+
QWidget *ZoomAction::createWidget(QWidget *parent)
{
auto comboBox = new QComboBox(parent);
diff --git a/src/plugins/qmldesigner/components/componentcore/zoomaction.h b/src/plugins/qmldesigner/components/componentcore/zoomaction.h
index 1b178343e8..bd46f915d9 100644
--- a/src/plugins/qmldesigner/components/componentcore/zoomaction.h
+++ b/src/plugins/qmldesigner/components/componentcore/zoomaction.h
@@ -48,6 +48,9 @@ public:
void zoomOut();
void resetZoomLevel();
void setZoomLevel(float zoomLevel);
+ void forceZoomLevel(float zoomLevel);
+
+ static float getClosestZoomLevel(float zoomLevel);
protected:
QWidget *createWidget(QWidget *parent) override;
diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp
index bad37ed443..f9710f1e8d 100644
--- a/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp
+++ b/src/plugins/qmldesigner/components/connectioneditor/connectionmodel.cpp
@@ -269,6 +269,9 @@ void ConnectionModel::addConnection()
if (QmlItemNode(selectedNode).isFlowActionArea())
source = selectedNode.validId() + ".trigger()";
+ if (QmlVisualNode(selectedNode).isFlowTransition())
+ source = selectedNode.validId() + ".trigger()";
+
if (!connectionView()->selectedModelNodes().constFirst().id().isEmpty())
newNode.bindingProperty("target").setExpression(selectedNode.id());
else
diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
index 9e882be2fd..fda18bdf45 100644
--- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp
@@ -112,7 +112,7 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState)
if (sceneState.contains(sceneKey)) {
qint32 newActiveScene = sceneState[sceneKey].value<qint32>();
edit3DWidget()->canvas()->updateActiveScene(newActiveScene);
- rootModelNode().setAuxiliaryData("3d-active-scene", newActiveScene);
+ rootModelNode().setAuxiliaryData("active3dScene", newActiveScene);
}
if (sceneState.contains(selectKey))
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
index 6bb138f8af..6a4acacb97 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditoritem.cpp
@@ -129,7 +129,7 @@ void FormEditorItem::setup()
QRectF FormEditorItem::boundingRect() const
{
- return m_boundingRect.adjusted(-2, -2, 2, 2);
+ return m_boundingRect;
}
QPainterPath FormEditorItem::shape() const
@@ -150,7 +150,7 @@ void FormEditorItem::updateGeometry()
prepareGeometryChange();
m_selectionBoundingRect = qmlItemNode().instanceBoundingRect().adjusted(0, 0, 1., 1.);
m_paintedBoundingRect = qmlItemNode().instancePaintedBoundingRect();
- m_boundingRect = m_paintedBoundingRect.united(m_selectionBoundingRect);
+ m_boundingRect = qmlItemNode().instanceBoundingRect();
setTransform(qmlItemNode().instanceTransformWithContentTransform());
// the property for zValue is called z in QGraphicsObject
if (qmlItemNode().instanceValue("z").isValid() && !qmlItemNode().isRootModelNode())
@@ -739,7 +739,7 @@ static bool isValid(const QList<QmlItemNode> &list)
if (!item.isValid())
return false;
- return true;
+ return !list.isEmpty();
}
static bool isModelNodeValid(const QList<QmlItemNode> &list)
@@ -748,7 +748,7 @@ static bool isModelNodeValid(const QList<QmlItemNode> &list)
if (!item.modelNode().isValid())
return false;
- return true;
+ return !list.isEmpty();
}
class ResolveConnection
@@ -797,17 +797,17 @@ public:
if (f.isValid()) {
for (const QmlFlowActionAreaNode &area : f.flowActionAreas()) {
ModelNode target = area.targetTransition();
- if (target == node.modelNode()) {
+ if (target == node.modelNode())
areaNode = area;
- } else {
- const ModelNode decisionNode = area.decisionNodeForTransition(node.modelNode());
- if (decisionNode.isValid()) {
- from.clear();
- from.append(decisionNode);
- areaNode = ModelNode();
- }
- }
}
+
+ const ModelNode decisionNode = QmlFlowItemNode::decisionNodeForTransition(node.modelNode());
+ if (decisionNode.isValid()) {
+ from.clear();
+ from.append(decisionNode);
+ areaNode = ModelNode();
+ }
+
if (f.modelNode().hasAuxiliaryData("joinConnection"))
joinConnection = f.modelNode().auxiliaryData("joinConnection").toBool();
} else {
diff --git a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp
index c7c34329c4..f64b45f351 100644
--- a/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp
+++ b/src/plugins/qmldesigner/components/formeditor/formeditorview.cpp
@@ -442,12 +442,49 @@ void FormEditorView::documentMessagesChanged(const QList<DocumentMessage> &error
m_formEditorWidget->hideErrorMessageBox();
}
-void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &/*nodeList*/, const QList<QVariant> &/*data*/)
+void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &/*data*/)
{
if (identifier == QLatin1String("puppet crashed"))
m_dragTool->clearMoveDelay();
if (identifier == QLatin1String("reset QmlPuppet"))
temporaryBlockView();
+ if (identifier == QLatin1String("fit root to screen")) {
+ if (QmlItemNode(rootModelNode()).isFlowView()) {
+ QRectF boundingRect;
+ for (QGraphicsItem *item : scene()->items()) {
+ if (auto formEditorItem = FormEditorItem::fromQGraphicsItem(item)) {
+ if (!formEditorItem->qmlItemNode().modelNode().isRootNode()
+ && !formEditorItem->sceneBoundingRect().isNull())
+ boundingRect = boundingRect.united(formEditorItem->sceneBoundingRect());
+ }
+ }
+ m_formEditorWidget->graphicsView()->fitInView(boundingRect,
+ Qt::KeepAspectRatio);
+ } else {
+ m_formEditorWidget->graphicsView()->fitInView(m_formEditorWidget->rootItemRect(),
+ Qt::KeepAspectRatio);
+ }
+
+ const qreal scaleFactor = m_formEditorWidget->graphicsView()->viewportTransform().m11();
+ float zoomLevel = ZoomAction::getClosestZoomLevel(scaleFactor);
+ m_formEditorWidget->zoomAction()->forceZoomLevel(zoomLevel);
+ }
+ if (identifier == QLatin1String("fit selection to screen")) {
+ if (nodeList.isEmpty())
+ return;
+
+ QRectF boundingRect;
+ for (const ModelNode &node : nodeList) {
+ if (FormEditorItem *item = scene()->itemForQmlItemNode(node))
+ boundingRect = boundingRect.united(item->sceneBoundingRect());
+ }
+
+ m_formEditorWidget->graphicsView()->fitInView(boundingRect,
+ Qt::KeepAspectRatio);
+ const qreal scaleFactor = m_formEditorWidget->graphicsView()->viewportTransform().m11();
+ float zoomLevel = ZoomAction::getClosestZoomLevel(scaleFactor);
+ m_formEditorWidget->zoomAction()->forceZoomLevel(zoomLevel);
+ }
}
AbstractFormEditorTool *FormEditorView::currentTool() const
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp
index 9acede8f79..8523827823 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.cpp
+++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp
@@ -469,7 +469,7 @@ void DesignDocument::paste()
[](const ModelNode &node) { return !node.isSubclassOf("QtQuick3D.Node"); })
== selectedNodes.end();
if (all3DNodes) {
- int activeSceneId = rootModelNode().auxiliaryData("3d-active-scene").toInt();
+ int activeSceneId = rootModelNode().auxiliaryData("active3dScene").toInt();
if (activeSceneId != -1) {
NodeListProperty sceneNodeProperty
= QmlVisualNode::findSceneNodeProperty(rootModelNode().view(), activeSceneId);
@@ -515,7 +515,7 @@ void DesignDocument::paste()
} else {
// if selection is empty and this is a 3D Node, paste it under the active scene
if (pastedNode.isSubclassOf("QtQuick3D.Node")) {
- int activeSceneId = rootModelNode().auxiliaryData("3d-active-scene").toInt();
+ int activeSceneId = rootModelNode().auxiliaryData("active3dScene").toInt();
if (activeSceneId != -1) {
NodeListProperty sceneNodeProperty
= QmlVisualNode::findSceneNodeProperty(rootModelNode().view(), activeSceneId);
diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
index f4c612aad8..594bbed8d5 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp
@@ -469,8 +469,8 @@ void PropertyEditorView::setupQmlBackend()
if (m_selectedNode.isValid()) {
qmlObjectNode = QmlObjectNode(m_selectedNode);
Q_ASSERT(qmlObjectNode.isValid());
+ currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this);
}
- currentQmlBackend->setup(qmlObjectNode, currentStateName, qmlSpecificsFile, this);
currentQmlBackend->context()->setContextProperty("finishedNotify", QVariant(false));
if (specificQmlData.isEmpty())
currentQmlBackend->contextObject()->setSpecificQmlData(specificQmlData);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp
index 485bb8dbb0..f72b7e570c 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.cpp
@@ -31,12 +31,12 @@
namespace QmlDesigner {
-TimelineAbstractTool::TimelineAbstractTool(TimelineGraphicsScene *scene)
+TimelineAbstractTool::TimelineAbstractTool(AbstractScrollGraphicsScene *scene)
: m_scene(scene)
, m_delegate(nullptr)
{}
-TimelineAbstractTool::TimelineAbstractTool(TimelineGraphicsScene *scene,
+TimelineAbstractTool::TimelineAbstractTool(AbstractScrollGraphicsScene *scene,
TimelineToolDelegate *delegate)
: m_scene(scene)
, m_delegate(delegate)
@@ -44,7 +44,7 @@ TimelineAbstractTool::TimelineAbstractTool(TimelineGraphicsScene *scene,
TimelineAbstractTool::~TimelineAbstractTool() = default;
-TimelineGraphicsScene *TimelineAbstractTool::scene() const
+AbstractScrollGraphicsScene *TimelineAbstractTool::scene() const
{
return m_scene;
}
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h
index 0411a8d166..7d62ceaffc 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineabstracttool.h
@@ -36,14 +36,14 @@ namespace QmlDesigner {
enum class ToolType { Move, Select };
class TimelineMovableAbstractItem;
-class TimelineGraphicsScene;
+class AbstractScrollGraphicsScene;
class TimelineToolDelegate;
class TimelineAbstractTool
{
public:
- explicit TimelineAbstractTool(TimelineGraphicsScene *scene);
- explicit TimelineAbstractTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+ explicit TimelineAbstractTool(AbstractScrollGraphicsScene *scene);
+ explicit TimelineAbstractTool(AbstractScrollGraphicsScene *scene, TimelineToolDelegate *delegate);
virtual ~TimelineAbstractTool();
virtual void mousePressEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) = 0;
@@ -56,7 +56,7 @@ public:
virtual void keyPressEvent(QKeyEvent *keyEvent) = 0;
virtual void keyReleaseEvent(QKeyEvent *keyEvent) = 0;
- TimelineGraphicsScene *scene() const;
+ AbstractScrollGraphicsScene *scene() const;
TimelineToolDelegate *delegate() const;
@@ -65,7 +65,7 @@ public:
TimelineMovableAbstractItem *currentItem() const;
private:
- TimelineGraphicsScene *m_scene;
+ AbstractScrollGraphicsScene *m_scene;
TimelineToolDelegate *m_delegate;
};
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp
index 326fa7d44b..3b87f22c51 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.cpp
@@ -91,7 +91,7 @@ QList<QmlTimelineKeyframeGroup> allTimelineFrames(const QmlTimeline &timeline)
}
TimelineGraphicsScene::TimelineGraphicsScene(TimelineWidget *parent)
- : QGraphicsScene(parent)
+ : AbstractScrollGraphicsScene(parent)
, m_parent(parent)
, m_layout(new TimelineGraphicsLayout(this))
, m_currentFrameIndicator(new TimelineFrameHandle)
@@ -378,17 +378,17 @@ void TimelineGraphicsScene::commitCurrentFrame(qreal frame)
}
}
-QList<TimelineKeyframeItem *> TimelineGraphicsScene::selectedKeyframes() const
+QList<TimelineKeyframeItem *> AbstractScrollGraphicsScene::selectedKeyframes() const
{
return m_selectedKeyframes;
}
-bool TimelineGraphicsScene::hasSelection() const
+bool AbstractScrollGraphicsScene::hasSelection() const
{
return !m_selectedKeyframes.empty();
}
-bool TimelineGraphicsScene::isCurrent(TimelineKeyframeItem *keyframe) const
+bool AbstractScrollGraphicsScene::isCurrent(TimelineKeyframeItem *keyframe) const
{
if (m_selectedKeyframes.empty())
return false;
@@ -396,12 +396,12 @@ bool TimelineGraphicsScene::isCurrent(TimelineKeyframeItem *keyframe) const
return m_selectedKeyframes.back() == keyframe;
}
-bool TimelineGraphicsScene::isKeyframeSelected(TimelineKeyframeItem *keyframe) const
+bool AbstractScrollGraphicsScene::isKeyframeSelected(TimelineKeyframeItem *keyframe) const
{
return m_selectedKeyframes.contains(keyframe);
}
-bool TimelineGraphicsScene::multipleKeyframesSelected() const
+bool AbstractScrollGraphicsScene::multipleKeyframesSelected() const
{
return m_selectedKeyframes.count() > 1;
}
@@ -456,19 +456,19 @@ void TimelineGraphicsScene::invalidateRecordButtonsStatus()
TimelinePropertyItem::updateRecordButtonStatus(item);
}
-int TimelineGraphicsScene::scrollOffset() const
+int AbstractScrollGraphicsScene::scrollOffset() const
{
return m_scrollOffset;
}
-void TimelineGraphicsScene::setScrollOffset(int offset)
+void AbstractScrollGraphicsScene::setScrollOffset(int offset)
{
m_scrollOffset = offset;
emitScrollOffsetChanged();
update();
}
-QGraphicsView *TimelineGraphicsScene::graphicsView() const
+QGraphicsView *AbstractScrollGraphicsScene::graphicsView() const
{
for (auto *v : views())
if (v->objectName() == "SceneView")
@@ -477,7 +477,7 @@ QGraphicsView *TimelineGraphicsScene::graphicsView() const
return nullptr;
}
-QGraphicsView *TimelineGraphicsScene::rulerView() const
+QGraphicsView *AbstractScrollGraphicsScene::rulerView() const
{
for (auto *v : views())
if (v->objectName() == "RulerView")
@@ -491,7 +491,7 @@ QmlTimeline TimelineGraphicsScene::currentTimeline() const
return QmlTimeline(timelineModelNode());
}
-QRectF TimelineGraphicsScene::selectionBounds() const
+QRectF AbstractScrollGraphicsScene::selectionBounds() const
{
QRectF bbox;
@@ -501,7 +501,7 @@ QRectF TimelineGraphicsScene::selectionBounds() const
return bbox;
}
-void TimelineGraphicsScene::selectKeyframes(const SelectionMode &mode,
+void AbstractScrollGraphicsScene::selectKeyframes(const SelectionMode &mode,
const QList<TimelineKeyframeItem *> &items)
{
if (mode == SelectionMode::Remove || mode == SelectionMode::Toggle) {
@@ -536,13 +536,14 @@ void TimelineGraphicsScene::selectKeyframes(const SelectionMode &mode,
emit selectionChanged();
}
-void TimelineGraphicsScene::clearSelection()
+void AbstractScrollGraphicsScene::clearSelection()
{
for (auto *keyframe : m_selectedKeyframes)
if (keyframe)
keyframe->setHighlighted(false);
m_selectedKeyframes.clear();
+ emit selectionChanged();
}
QList<QGraphicsItem *> TimelineGraphicsScene::itemsAt(const QPointF &pos)
@@ -682,7 +683,7 @@ ModelNode TimelineGraphicsScene::timelineModelNode() const
void TimelineGraphicsScene::handleKeyframeDeletion()
{
QList<ModelNode> nodesToBeDeleted;
- for (auto keyframe : m_selectedKeyframes) {
+ for (auto keyframe : selectedKeyframes()) {
nodesToBeDeleted.append(keyframe->frameNode());
}
deleteKeyframes(nodesToBeDeleted);
@@ -711,7 +712,7 @@ void TimelineGraphicsScene::pasteKeyframesToTarget(const ModelNode &targetNode)
void TimelineGraphicsScene::copySelectedKeyframes()
{
TimelineActions::copyKeyframes(
- Utils::transform(m_selectedKeyframes, &TimelineKeyframeItem::frameNode));
+ Utils::transform(selectedKeyframes(), &TimelineKeyframeItem::frameNode));
}
void TimelineGraphicsScene::pasteSelectedKeyframes()
@@ -756,7 +757,20 @@ void TimelineGraphicsScene::activateLayout()
m_layout->activate();
}
-void TimelineGraphicsScene::emitScrollOffsetChanged()
+AbstractView *TimelineGraphicsScene::abstractView() const
+{
+ return timelineView();
+}
+
+int AbstractScrollGraphicsScene::getScrollOffset(QGraphicsScene *scene)
+{
+ auto scrollScene = qobject_cast<AbstractScrollGraphicsScene*>(scene);
+ if (scrollScene)
+ return scrollScene->scrollOffset();
+ return 0;
+}
+
+void AbstractScrollGraphicsScene::emitScrollOffsetChanged()
{
for (QGraphicsItem *item : items())
TimelineMovableAbstractItem::emitScrollOffsetChanged(item);
@@ -783,4 +797,8 @@ bool TimelineGraphicsScene::event(QEvent *event)
}
}
+QmlDesigner::AbstractScrollGraphicsScene::AbstractScrollGraphicsScene(QWidget *parent)
+ : QGraphicsScene(parent)
+{}
+
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h
index 4e33ce362e..7413cb1dbb 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinegraphicsscene.h
@@ -36,6 +36,7 @@
QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QWidget)
namespace QmlDesigner {
@@ -51,15 +52,66 @@ class TimelinePlaceholder;
class TimelineGraphicsLayout;
class TimelineToolBar;
-class TimelineGraphicsScene : public QGraphicsScene
+class AbstractScrollGraphicsScene : public QGraphicsScene
{
Q_OBJECT
+public:
+ AbstractScrollGraphicsScene(QWidget *parent);
+ ;
+
+ int scrollOffset() const;
+ void setScrollOffset(int offset);
+ static int getScrollOffset(QGraphicsScene *scene);
+
+ QRectF selectionBounds() const;
+
+ void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items);
+ virtual void clearSelection();
+ QList<TimelineKeyframeItem *> selectedKeyframes() const;
+ bool hasSelection() const;
+ bool isCurrent(TimelineKeyframeItem *keyframe) const;
+ bool isKeyframeSelected(TimelineKeyframeItem *keyframe) const;
+ bool multipleKeyframesSelected() const;
+
+ virtual qreal rulerScaling() const = 0;
+ virtual int rulerWidth() const = 0;
+ virtual qreal rulerDuration() const = 0;
+
+ virtual AbstractView *abstractView() const = 0;
+
+ virtual void setCurrentFrame(int) {}
+
+ virtual qreal startFrame() const = 0;
+ virtual qreal endFrame() const = 0;
+
+ virtual void invalidateScrollbar() = 0;
+
+ virtual qreal snap(qreal frame, bool snapToPlayhead = true)
+ {
+ Q_UNUSED(snapToPlayhead);
+ return frame;
+ }
+
+ QGraphicsView *graphicsView() const;
+ QGraphicsView *rulerView() const;
+
signals:
+ void statusBarMessageChanged(const QString &message);
void selectionChanged();
-
void scroll(const TimelineUtils::Side &side);
+private:
+ void emitScrollOffsetChanged();
+
+ int m_scrollOffset = 0;
+ QList<TimelineKeyframeItem *> m_selectedKeyframes;
+};
+
+class TimelineGraphicsScene : public AbstractScrollGraphicsScene
+{
+ Q_OBJECT
+
public:
explicit TimelineGraphicsScene(TimelineWidget *parent);
@@ -74,7 +126,7 @@ public:
void invalidateLayout();
qreal setCurrenFrame(const QmlTimeline &timeline, qreal frame);
- void setCurrentFrame(int frame);
+ void setCurrentFrame(int frame) override;
void setStartFrame(int frame);
void setEndFrame(int frame);
@@ -82,11 +134,12 @@ public:
TimelineWidget *timelineWidget() const;
TimelineToolBar *toolBar() const;
- qreal rulerScaling() const;
- int rulerWidth() const;
- qreal rulerDuration() const;
- qreal startFrame() const;
- qreal endFrame() const;
+ qreal rulerScaling() const override;
+ int rulerWidth() const override;
+ qreal rulerDuration() const override;
+
+ qreal startFrame() const override;
+ qreal endFrame() const override;
void updateKeyframePositionsCache();
@@ -97,39 +150,22 @@ public:
QVector<qreal> keyframePositions() const;
QVector<qreal> keyframePositions(const QmlTimelineKeyframeGroup &frames) const;
- qreal snap(qreal frame, bool snapToPlayhead = true);
+ qreal snap(qreal frame, bool snapToPlayhead = true) override;
void setRulerScaling(int scaling);
void commitCurrentFrame(qreal frame);
- QList<TimelineKeyframeItem *> selectedKeyframes() const;
-
- bool hasSelection() const;
- bool isCurrent(TimelineKeyframeItem *keyframe) const;
- bool isKeyframeSelected(TimelineKeyframeItem *keyframe) const;
- bool multipleKeyframesSelected() const;
-
void invalidateSectionForTarget(const ModelNode &modelNode);
void invalidateKeyframesForTarget(const ModelNode &modelNode);
void invalidateScene();
- void invalidateScrollbar();
+ void invalidateScrollbar() override;
void invalidateCurrentValues();
void invalidateRecordButtonsStatus();
- int scrollOffset() const;
- void setScrollOffset(int offset);
- QGraphicsView *graphicsView() const;
- QGraphicsView *rulerView() const;
-
QmlTimeline currentTimeline() const;
- QRectF selectionBounds() const;
-
- void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items);
- void clearSelection();
-
void handleKeyframeDeletion();
void deleteAllKeyframesForTarget(const ModelNode &targetNode);
void insertAllKeyframesForTarget(const ModelNode &targetNode);
@@ -143,8 +179,7 @@ public:
void activateLayout();
-signals:
- void statusBarMessageChanged(const QString &message);
+ AbstractView *abstractView() const override;
protected:
bool event(QEvent *event) override;
@@ -163,7 +198,6 @@ private:
void invalidateSections();
ModelNode timelineModelNode() const;
- void emitScrollOffsetChanged();
void emitStatusBarPlayheadFrameChanged(int frame);
QList<QGraphicsItem *> itemsAt(const QPointF &pos);
@@ -178,12 +212,8 @@ private:
TimelineToolDelegate m_tools;
- QList<TimelineKeyframeItem *> m_selectedKeyframes;
-
// sorted, unique cache of keyframes positions, used for snapping
QVector<qreal> m_keyframePositionsCache;
-
- int m_scrollOffset = 0;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp
index 3b7f8a2fe0..6d7fb55262 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.cpp
@@ -52,8 +52,7 @@ TimelineItem::TimelineItem(TimelineItem *parent)
TimelineGraphicsScene *TimelineItem::timelineScene() const
{
- return static_cast<TimelineGraphicsScene *>(scene());
- ;
+ return qobject_cast<TimelineGraphicsScene *>(scene());
}
TimelineFrameHandle::TimelineFrameHandle(TimelineItem *parent)
@@ -93,7 +92,7 @@ void TimelineFrameHandle::setPosition(qreal frame)
void TimelineFrameHandle::setPositionInteractive(const QPointF &position)
{
- const double width = timelineScene()->width();
+ const double width = abstractScrollGraphicsScene()->width();
if (position.x() > width) {
callSetClampedXPosition(width - (rect().width() / 2) - 1);
@@ -104,7 +103,7 @@ void TimelineFrameHandle::setPositionInteractive(const QPointF &position)
} else {
callSetClampedXPosition(position.x() - rect().width() / 2);
const qreal frame = std::round(mapFromSceneToFrame(rect().center().x()));
- timelineScene()->commitCurrentFrame(frame);
+ timelineGraphicsScene()->commitCurrentFrame(frame);
}
}
@@ -128,6 +127,11 @@ TimelineFrameHandle *TimelineFrameHandle::asTimelineFrameHandle()
return this;
}
+TimelineGraphicsScene *TimelineFrameHandle::timelineGraphicsScene() const
+{
+ return qobject_cast<TimelineGraphicsScene* >(abstractScrollGraphicsScene());
+}
+
void TimelineFrameHandle::scrollOffsetChanged()
{
setPosition(position());
@@ -182,7 +186,7 @@ void TimelineFrameHandle::paint(QPainter *painter,
QPointF TimelineFrameHandle::mapFromGlobal(const QPoint &pos) const
{
- for (auto *view : timelineScene()->views()) {
+ for (auto *view : abstractScrollGraphicsScene()->views()) {
if (view->objectName() == "SceneView") {
auto graphicsViewCoords = view->mapFromGlobal(pos);
auto sceneCoords = view->mapToScene(graphicsViewCoords);
@@ -195,7 +199,7 @@ QPointF TimelineFrameHandle::mapFromGlobal(const QPoint &pos) const
int TimelineFrameHandle::computeScrollSpeed() const
{
const double mouse = mapFromGlobal(QCursor::pos()).x();
- const double width = timelineScene()->width();
+ const double width = abstractScrollGraphicsScene()->width();
const double acc = mouse > width ? mouse - width
: double(TimelineConstants::sectionWidth) - mouse;
@@ -216,7 +220,7 @@ void TimelineFrameHandle::callSetClampedXPosition(double x)
const int minimumWidth = TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
- rect().width() / 2;
const int maximumWidth = minimumWidth
- + timelineScene()->rulerDuration() * timelineScene()->rulerScaling()
+ + abstractScrollGraphicsScene()->rulerDuration() * abstractScrollGraphicsScene()->rulerScaling()
- scrollOffset();
setClampedXPosition(x, minimumWidth, maximumWidth);
@@ -225,7 +229,7 @@ void TimelineFrameHandle::callSetClampedXPosition(double x)
// Auto scroll when dragging playhead out of bounds.
void TimelineFrameHandle::scrollOutOfBounds()
{
- const double width = timelineScene()->width();
+ const double width = abstractScrollGraphicsScene()->width();
const double mouse = mapFromGlobal(QCursor::pos()).x();
if (mouse > width)
@@ -236,14 +240,14 @@ void TimelineFrameHandle::scrollOutOfBounds()
void TimelineFrameHandle::scrollOutOfBoundsMax()
{
- const double width = timelineScene()->width();
+ const double width = abstractScrollGraphicsScene()->width();
if (QApplication::mouseButtons() == Qt::LeftButton) {
- const double frameWidth = timelineScene()->rulerScaling();
+ const double frameWidth = abstractScrollGraphicsScene()->rulerScaling();
const double upperThreshold = width - frameWidth;
if (rect().center().x() > upperThreshold) {
- timelineScene()->setScrollOffset(computeScrollSpeed());
- timelineScene()->invalidateScrollbar();
+ abstractScrollGraphicsScene()->setScrollOffset(computeScrollSpeed());
+ abstractScrollGraphicsScene()->invalidateScrollbar();
}
callSetClampedXPosition(width - (rect().width() / 2) - 1);
@@ -253,8 +257,8 @@ void TimelineFrameHandle::scrollOutOfBoundsMax()
callSetClampedXPosition(width - (rect().width() / 2) - 1);
const int frame = std::floor(mapFromSceneToFrame(rect().center().x()));
- const int ef = timelineScene()->endFrame();
- timelineScene()->commitCurrentFrame(frame <= ef ? frame : ef);
+ const int ef = abstractScrollGraphicsScene()->endFrame();
+ timelineGraphicsScene()->commitCurrentFrame(frame <= ef ? frame : ef);
}
}
@@ -264,11 +268,11 @@ void TimelineFrameHandle::scrollOutOfBoundsMin()
auto offset = computeScrollSpeed();
if (offset >= 0)
- timelineScene()->setScrollOffset(offset);
+ abstractScrollGraphicsScene()->setScrollOffset(offset);
else
- timelineScene()->setScrollOffset(0);
+ abstractScrollGraphicsScene()->setScrollOffset(0);
- timelineScene()->invalidateScrollbar();
+ abstractScrollGraphicsScene()->invalidateScrollbar();
callSetClampedXPosition(TimelineConstants::sectionWidth);
m_timer.start();
@@ -278,7 +282,7 @@ void TimelineFrameHandle::scrollOutOfBoundsMin()
int frame = mapFromSceneToFrame(rect().center().x());
- const int sframe = timelineScene()->startFrame();
+ const int sframe = abstractScrollGraphicsScene()->startFrame();
if (frame != sframe) {
const qreal framePos = mapFromFrameToScene(frame);
@@ -287,7 +291,7 @@ void TimelineFrameHandle::scrollOutOfBoundsMin()
frame++;
}
- timelineScene()->commitCurrentFrame(frame >= sframe ? frame : sframe);
+ timelineGraphicsScene()->commitCurrentFrame(frame >= sframe ? frame : sframe);
}
}
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h
index 9d8bd9493e..22832f0d0d 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineitem.h
@@ -35,6 +35,8 @@ QT_FORWARD_DECLARE_CLASS(QPainterPath)
namespace QmlDesigner {
+class TimelineGraphicsScene;
+
class TimelineItem : public QGraphicsWidget
{
Q_OBJECT
@@ -59,6 +61,8 @@ public:
TimelineFrameHandle *asTimelineFrameHandle() override;
+ TimelineGraphicsScene *timelineGraphicsScene() const;
+
protected:
void scrollOffsetChanged() override;
QPainterPath shape() const override;
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp
index 1d700cf063..12345a404e 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.cpp
@@ -53,7 +53,7 @@ void TimelineMovableAbstractItem::itemDoubleClicked()
int TimelineMovableAbstractItem::scrollOffset() const
{
- return timelineScene()->scrollOffset();
+ return abstractScrollGraphicsScene()->scrollOffset();
}
int TimelineMovableAbstractItem::xPosScrollOffset(int x) const
@@ -63,7 +63,7 @@ int TimelineMovableAbstractItem::xPosScrollOffset(int x) const
qreal TimelineMovableAbstractItem::mapFromFrameToScene(qreal x) const
{
- return TimelineConstants::sectionWidth + (x - timelineScene()->startFrame()) * rulerScaling()
+ return TimelineConstants::sectionWidth + (x - abstractScrollGraphicsScene()->startFrame()) * rulerScaling()
- scrollOffset() + TimelineConstants::timelineLeftOffset;
}
@@ -71,8 +71,8 @@ qreal TimelineMovableAbstractItem::mapFromSceneToFrame(qreal x) const
{
return xPosScrollOffset(x - TimelineConstants::sectionWidth
- TimelineConstants::timelineLeftOffset)
- / timelineScene()->rulerScaling()
- + timelineScene()->startFrame();
+ / abstractScrollGraphicsScene()->rulerScaling()
+ + abstractScrollGraphicsScene()->startFrame();
}
void TimelineMovableAbstractItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
@@ -133,7 +133,7 @@ TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem(QGraph
qreal TimelineMovableAbstractItem::rulerScaling() const
{
- return static_cast<TimelineGraphicsScene *>(scene())->rulerScaling();
+ return qobject_cast<AbstractScrollGraphicsScene *>(scene())->rulerScaling();
}
int TimelineMovableAbstractItem::type() const
@@ -141,9 +141,9 @@ int TimelineMovableAbstractItem::type() const
return Type;
}
-TimelineGraphicsScene *TimelineMovableAbstractItem::timelineScene() const
+AbstractScrollGraphicsScene *TimelineMovableAbstractItem::abstractScrollGraphicsScene() const
{
- return static_cast<TimelineGraphicsScene *>(scene());
+ return qobject_cast<AbstractScrollGraphicsScene *>(scene());
}
TimelineKeyframeItem *TimelineMovableAbstractItem::asTimelineKeyframeItem()
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h
index cf71397225..199a78ad99 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovableabstractitem.h
@@ -32,7 +32,7 @@
namespace QmlDesigner {
-class TimelineGraphicsScene;
+class AbstractScrollGraphicsScene;
class TimelineKeyframeItem;
class TimelineFrameHandle;
@@ -75,7 +75,7 @@ protected:
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
void setClampedXPosition(qreal x, qreal min, qreal max);
- TimelineGraphicsScene *timelineScene() const;
+ AbstractScrollGraphicsScene *abstractScrollGraphicsScene() const;
};
} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp
index b62b64de9d..5dc52bbc9b 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.cpp
@@ -61,7 +61,7 @@ QPointF mapToItem(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *e
return event->scenePos();
}
-TimelineMoveTool::TimelineMoveTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate)
+TimelineMoveTool::TimelineMoveTool(AbstractScrollGraphicsScene *scene, TimelineToolDelegate *delegate)
: TimelineAbstractTool(scene, delegate)
{}
@@ -155,7 +155,7 @@ void TimelineMoveTool::mouseReleaseEvent(TimelineMovableAbstractItem *item,
}
}
- scene()->timelineView()->executeInTransaction("TimelineMoveTool::mouseReleaseEvent",
+ scene()->abstractView()->executeInTransaction("TimelineMoveTool::mouseReleaseEvent",
[this, current]() {
current->commitPosition(mapToItem(current, current->rect().center()));
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h
index c5c9702c44..144c123b0e 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinemovetool.h
@@ -41,7 +41,7 @@ class TimelineMoveTool : public TimelineAbstractTool
Q_DECLARE_TR_FUNCTIONS(TimelineMoveTool)
public:
- explicit TimelineMoveTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+ explicit TimelineMoveTool(AbstractScrollGraphicsScene *scene, TimelineToolDelegate *delegate);
void mousePressEvent(TimelineMovableAbstractItem *item,
QGraphicsSceneMouseEvent *event) override;
void mouseMoveEvent(TimelineMovableAbstractItem *item, QGraphicsSceneMouseEvent *event) override;
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
index 1159619dce..9aef430dea 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.cpp
@@ -505,7 +505,7 @@ TimelineKeyframeItem::TimelineKeyframeItem(TimelinePropertyItem *parent, const M
TimelineKeyframeItem::~TimelineKeyframeItem()
{
- timelineScene()->selectKeyframes(SelectionMode::Remove, {this});
+ abstractScrollGraphicsScene()->selectKeyframes(SelectionMode::Remove, {this});
}
void TimelineKeyframeItem::updateFrame()
@@ -555,8 +555,8 @@ void TimelineKeyframeItem::commitPosition(const QPointF &point)
void TimelineKeyframeItem::itemDoubleClicked()
{
- std::pair<qreal, qreal> timelineRange = {timelineScene()->currentTimeline().startKeyframe(),
- timelineScene()->currentTimeline().endKeyframe()};
+ std::pair<qreal, qreal> timelineRange = {timelineGraphicsScene()->currentTimeline().startKeyframe(),
+ timelineGraphicsScene()->currentTimeline().endKeyframe()};
editValue(m_frame, timelineRange, propertyItem()->propertyName());
}
@@ -565,6 +565,11 @@ TimelineKeyframeItem *TimelineKeyframeItem::asTimelineKeyframeItem()
return this;
}
+TimelineGraphicsScene *TimelineKeyframeItem::timelineGraphicsScene() const
+{
+ return qobject_cast<TimelineGraphicsScene *>(abstractScrollGraphicsScene());
+}
+
void TimelineKeyframeItem::blockUpdates()
{
s_blockUpdates = true;
@@ -643,21 +648,21 @@ void TimelineKeyframeItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *even
QMenu mainMenu;
QAction *removeAction = mainMenu.addAction(tr("Delete Keyframe"));
QObject::connect(removeAction, &QAction::triggered, [this]() {
- timelineScene()->handleKeyframeDeletion();
+ timelineGraphicsScene()->handleKeyframeDeletion();
});
QAction *editEasingAction = mainMenu.addAction(tr("Edit Easing Curve..."));
QObject::connect(editEasingAction, &QAction::triggered, [this]() {
- const QList<ModelNode> keys = Utils::transform(timelineScene()->selectedKeyframes(),
+ const QList<ModelNode> keys = Utils::transform(abstractScrollGraphicsScene()->selectedKeyframes(),
&TimelineKeyframeItem::m_frame);
- setEasingCurve(timelineScene(), keys);
+ setEasingCurve(timelineGraphicsScene(), keys);
});
QAction *editValueAction = mainMenu.addAction(tr("Edit Keyframe..."));
QObject::connect(editValueAction, &QAction::triggered, [this]() {
- std::pair<qreal, qreal> timelineRange = {timelineScene()->currentTimeline().startKeyframe(),
- timelineScene()->currentTimeline().endKeyframe()};
+ std::pair<qreal, qreal> timelineRange = {timelineGraphicsScene()->currentTimeline().startKeyframe(),
+ timelineGraphicsScene()->currentTimeline().endKeyframe()};
editValue(m_frame, timelineRange, propertyItem()->propertyName());
});
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h
index 5d6666f4ed..280b3bee54 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinepropertyitem.h
@@ -67,6 +67,7 @@ public:
void itemDoubleClicked() override;
TimelineKeyframeItem *asTimelineKeyframeItem() override;
+ TimelineGraphicsScene *timelineGraphicsScene() const;
protected:
bool hasManualBezier() const;
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp
index 7e8bfb6f50..c77d466585 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.cpp
@@ -1,4 +1,4 @@
-/****************************************************************************
+/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
@@ -215,15 +215,7 @@ QVector<qreal> TimelineSectionItem::keyframePositions() const
return out;
}
-QTransform rotatationTransform(qreal degrees)
-{
- QTransform transform;
- transform.rotate(degrees);
-
- return transform;
-}
-
-QPixmap rotateby90(const QPixmap &pixmap)
+static QPixmap rotateby90(const QPixmap &pixmap)
{
QImage sourceImage = pixmap.toImage();
QImage destImage(pixmap.height(), pixmap.width(), sourceImage.format());
@@ -550,6 +542,13 @@ void TimelineRulerSectionItem::invalidateRulerSize(const QmlTimeline &timeline)
m_end = timeline.endKeyframe();
}
+void TimelineRulerSectionItem::invalidateRulerSize(const qreal length)
+{
+ m_duration = length;
+ m_start = 0;
+ m_end = length;
+}
+
void TimelineRulerSectionItem::setRulerScaleFactor(int scaling)
{
qreal blend = qreal(scaling) / 100.0;
@@ -627,10 +626,12 @@ void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphi
static const QColor highlightColor = Theme::instance()->Theme::qmlDesignerButtonColor();
static const QColor handleColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ const int scrollOffset = TimelineGraphicsScene::getScrollOffset(scene());
+
painter->save();
painter->save();
painter->setRenderHint(QPainter::Antialiasing);
- painter->translate(-timelineScene()->scrollOffset(), 0);
+ painter->translate(-scrollOffset, 0);
painter->fillRect(TimelineConstants::sectionWidth,
0,
size().width() - TimelineConstants::sectionWidth,
@@ -666,11 +667,13 @@ void TimelineRulerSectionItem::paint(QPainter *painter, const QStyleOptionGraphi
const int height = size().height() - 1;
+
+
drawLine(painter,
- TimelineConstants::sectionWidth + timelineScene()->scrollOffset()
+ TimelineConstants::sectionWidth + scrollOffset
- TimelineConstants::timelineLeftOffset,
height,
- size().width() + timelineScene()->scrollOffset(),
+ size().width() + scrollOffset,
height);
QFont font = painter->font();
@@ -720,9 +723,12 @@ void TimelineRulerSectionItem::paintTicks(QPainter *painter)
m_frameTick = qreal(deltaLine);
+ int scrollOffset = TimelineGraphicsScene::getScrollOffset(scene());
+
int height = size().height();
- const int totalWidth = (size().width() + timelineScene()->scrollOffset()) / m_scaling;
- for (int i = timelineScene()->scrollOffset() / m_scaling; i < totalWidth; ++i) {
+ const int totalWidth = (size().width() + scrollOffset) / m_scaling;
+
+ for (int i = scrollOffset / m_scaling; i < totalWidth; ++i) {
if ((i % deltaText) == 0) {
drawCenteredText(painter,
TimelineConstants::sectionWidth + i * m_scaling,
@@ -794,11 +800,11 @@ void TimelineBarItem::itemMoved(const QPointF &start, const QPointF &end)
qreal min = qreal(TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
- scrollOffset());
- qreal max = qreal(timelineScene()->rulerWidth() - TimelineConstants::sectionWidth
+ qreal max = qreal(abstractScrollGraphicsScene()->rulerWidth() - TimelineConstants::sectionWidth
+ rect().width());
- const qreal minFrameX = mapFromFrameToScene(timelineScene()->startFrame());
- const qreal maxFrameX = mapFromFrameToScene(timelineScene()->endFrame());
+ const qreal minFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->startFrame() - 1);
+ const qreal maxFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->endFrame()+ 1000);
if (min < minFrameX)
min = minFrameX;
@@ -811,7 +817,7 @@ void TimelineBarItem::itemMoved(const QPointF &start, const QPointF &end)
else
dragHandle(rect(), end, min, max);
- timelineScene()->statusBarMessageChanged(
+ abstractScrollGraphicsScene()->statusBarMessageChanged(
tr("Range from %1 to %2")
.arg(qRound(mapFromSceneToFrame(rect().x())))
.arg(qRound(mapFromSceneToFrame(rect().width() + rect().x()))));
@@ -975,7 +981,7 @@ void TimelineBarItem::dragCenter(QRectF rect, const QPointF &pos, qreal min, qre
if (validateBounds(pos.x() - rect.topLeft().x())) {
qreal targetX = pos.x() - m_pivot;
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
- qreal snappedTargetFrame = timelineScene()->snap(mapFromSceneToFrame(targetX));
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
targetX = mapFromFrameToScene(snappedTargetFrame);
}
rect.moveLeft(targetX);
@@ -999,7 +1005,7 @@ void TimelineBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qre
if (validateBounds(pos.x() - left.topLeft().x())) {
qreal targetX = pos.x() - m_pivot;
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
- qreal snappedTargetFrame = timelineScene()->snap(mapFromSceneToFrame(targetX));
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
targetX = mapFromFrameToScene(snappedTargetFrame);
}
rect.setLeft(targetX);
@@ -1015,7 +1021,7 @@ void TimelineBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qre
if (validateBounds(pos.x() - right.topRight().x())) {
qreal targetX = pos.x() - m_pivot;
if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
- qreal snappedTargetFrame = timelineScene()->snap(mapFromSceneToFrame(targetX));
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
targetX = mapFromFrameToScene(snappedTargetFrame);
}
rect.setRight(targetX);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h
index 0cd8817a41..e5403bcb74 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinesectionitem.h
@@ -151,6 +151,7 @@ public:
static TimelineRulerSectionItem *create(QGraphicsScene *parentScene, TimelineItem *parent);
void invalidateRulerSize(const QmlTimeline &timeline);
+ void invalidateRulerSize(const qreal length);
void setRulerScaleFactor(int scaling);
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp
index a33f844b2e..81d4bab8b3 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.cpp
@@ -38,7 +38,7 @@
namespace QmlDesigner {
-TimelineSelectionTool::TimelineSelectionTool(TimelineGraphicsScene *scene,
+TimelineSelectionTool::TimelineSelectionTool(AbstractScrollGraphicsScene *scene,
TimelineToolDelegate *delegate)
: TimelineAbstractTool(scene, delegate)
, m_selectionRect(new QGraphicsRectItem)
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h
index 3485e087ec..f635ea0530 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelineselectiontool.h
@@ -45,7 +45,7 @@ enum class SelectionMode { New, Add, Remove, Toggle };
class TimelineSelectionTool : public TimelineAbstractTool
{
public:
- explicit TimelineSelectionTool(TimelineGraphicsScene *scene, TimelineToolDelegate *delegate);
+ explicit TimelineSelectionTool(AbstractScrollGraphicsScene *scene, TimelineToolDelegate *delegate);
~TimelineSelectionTool() override;
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp
index dec61de19f..39874f51b0 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetoolbar.cpp
@@ -56,19 +56,19 @@
namespace QmlDesigner {
-bool isSpacer(QObject *object)
+static bool isSpacer(QObject *object)
{
return object->property("spacer_widget").toBool();
}
-QWidget *createSpacer()
+static QWidget *createSpacer()
{
QWidget *spacer = new QWidget();
spacer->setProperty("spacer_widget", true);
return spacer;
}
-int controlWidth(QToolBar *bar, QObject *control)
+static int controlWidth(QToolBar *bar, QObject *control)
{
QWidget *widget = nullptr;
@@ -84,7 +84,7 @@ int controlWidth(QToolBar *bar, QObject *control)
return 0;
}
-QAction *createAction(const Core::Id &id,
+static QAction *createAction(const Core::Id &id,
const QIcon &icon,
const QString &name,
const QKeySequence &shortcut)
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp
index cbf1177912..f1b28e8b9f 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.cpp
@@ -38,7 +38,7 @@
namespace QmlDesigner {
-TimelineToolDelegate::TimelineToolDelegate(TimelineGraphicsScene *scene)
+TimelineToolDelegate::TimelineToolDelegate(AbstractScrollGraphicsScene *scene)
: m_scene(scene)
, m_start()
, m_moveTool(new TimelineMoveTool(scene, this))
diff --git a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h
index f945c1a61b..0a328386c0 100644
--- a/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h
+++ b/src/plugins/qmldesigner/components/timelineeditor/timelinetooldelegate.h
@@ -38,7 +38,7 @@ class TimelineGraphicsScene;
class TimelineToolDelegate
{
public:
- TimelineToolDelegate(TimelineGraphicsScene* scene);
+ TimelineToolDelegate(AbstractScrollGraphicsScene* scene);
QPointF startPoint() const;
@@ -65,7 +65,7 @@ private:
private:
static const int dragDistance = 20;
- TimelineGraphicsScene* m_scene;
+ AbstractScrollGraphicsScene* m_scene;
QPointF m_start;
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri
new file mode 100644
index 0000000000..8f9a9dec9e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri
@@ -0,0 +1,35 @@
+QT *= qml quick core
+
+VPATH += $$PWD
+
+INCLUDEPATH += $$PWD
+
+SOURCES += \
+ transitioneditorview.cpp \
+ transitioneditorwidget.cpp \
+ transitioneditortoolbar.cpp \
+ transitioneditorgraphicsscene.cpp \
+ transitioneditorgraphicslayout.cpp \
+ transitioneditorsectionitem.cpp \
+ transitioneditorpropertyitem.cpp \
+ transitioneditorsettingsdialog.cpp \
+ transitionform.cpp
+
+HEADERS += \
+ transitioneditorconstants \
+ transitioneditorview.h \
+ transitioneditorwidget.h \
+ transitioneditortoolbar.h \
+ transitioneditorgraphicsscene.h \
+ transitioneditorgraphicslayout.h \
+ transitioneditorsectionitem.h \
+ transitioneditorpropertyitem.h \
+ transitioneditorsettingsdialog.h \
+ transitionform.h
+
+RESOURCES += \
+ transitioneditor.qrc
+
+FORMS += \
+ transitioneditorsettingsdialog.ui \
+ transitionform.ui
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc
new file mode 100644
index 0000000000..a2a962a6b8
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc
@@ -0,0 +1,4 @@
+<RCC>
+ <qresource prefix="/transitioneditor">
+ </qresource>
+</RCC>
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h
new file mode 100644
index 0000000000..c06249eacb
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** 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 <QGraphicsItem>
+
+namespace QmlDesigner {
+namespace TransitionEditorConstants {
+
+const int transitionEditorSectionItemUserType = QGraphicsItem::UserType + 6;
+const int transitionEditorPropertyItemUserType = QGraphicsItem::UserType + 7;
+
+} // namespace TransitionEditorConstants
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp
new file mode 100644
index 0000000000..02e1258dfd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** 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 "transitioneditorgraphicslayout.h"
+
+#include "timelinegraphicsscene.h"
+#include "timelineplaceholder.h"
+#include "timelinesectionitem.h"
+#include "timelineview.h"
+#include "transitioneditorsectionitem.h"
+
+#include <QGraphicsLinearLayout>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+TransitionEditorGraphicsLayout::TransitionEditorGraphicsLayout(QGraphicsScene *scene,
+ TimelineItem *parent)
+ : TimelineItem(parent)
+ , m_layout(new QGraphicsLinearLayout)
+ , m_rulerItem(TimelineRulerSectionItem::create(scene, this))
+ , m_placeholder1(TimelinePlaceholder::create(scene, this))
+ , m_placeholder2(TimelinePlaceholder::create(scene, this))
+{
+ m_layout->setOrientation(Qt::Vertical);
+ m_layout->setSpacing(0);
+ m_layout->setContentsMargins(0, 0, 0, 0);
+
+ m_layout->addItem(m_rulerItem);
+ m_layout->addItem(m_placeholder1);
+ m_layout->addItem(m_placeholder2);
+
+ setLayout(m_layout);
+
+ setPos(QPointF(0, 0));
+
+ connect(m_rulerItem,
+ &TimelineRulerSectionItem::rulerClicked,
+ this,
+ &TransitionEditorGraphicsLayout::rulerClicked);
+}
+
+TransitionEditorGraphicsLayout::~TransitionEditorGraphicsLayout() = default;
+
+double TransitionEditorGraphicsLayout::rulerWidth() const
+{
+ return m_rulerItem->preferredWidth();
+}
+
+double TransitionEditorGraphicsLayout::rulerScaling() const
+{
+ return m_rulerItem->rulerScaling();
+}
+
+double TransitionEditorGraphicsLayout::rulerDuration() const
+{
+ return m_rulerItem->rulerDuration();
+}
+
+double TransitionEditorGraphicsLayout::endFrame() const
+{
+ return m_rulerItem->endFrame();
+}
+
+void TransitionEditorGraphicsLayout::setWidth(int width)
+{
+ m_rulerItem->setSizeHints(width);
+ m_placeholder1->setMinimumWidth(width);
+ m_placeholder2->setMinimumWidth(width);
+ setPreferredWidth(width);
+ setMaximumWidth(width);
+}
+
+void TransitionEditorGraphicsLayout::setTransition(const ModelNode &transition)
+{
+ m_layout->removeItem(m_rulerItem);
+ m_layout->removeItem(m_placeholder1);
+ m_layout->removeItem(m_placeholder2);
+
+ m_rulerItem->setParentItem(nullptr);
+ m_placeholder1->setParentItem(nullptr);
+ m_placeholder2->setParentItem(nullptr);
+
+ qDeleteAll(this->childItems());
+
+ m_rulerItem->setParentItem(this);
+
+ qreal duration = 2000;
+ if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration"))
+ duration = transition.auxiliaryData("transitionDuration").toDouble();
+
+ setDuration(duration);
+ m_layout->addItem(m_rulerItem);
+
+ m_placeholder1->setParentItem(this);
+ m_layout->addItem(m_placeholder1);
+
+ m_layout->invalidate();
+
+ if (transition.isValid() && !transition.directSubModelNodes().isEmpty()) {
+ for (const ModelNode &parallel : transition.directSubModelNodes()) {
+ auto item = TransitionEditorSectionItem::create(parallel, this);
+ m_layout->addItem(item);
+ }
+ }
+
+ m_placeholder2->setParentItem(this);
+ m_layout->addItem(m_placeholder2);
+
+ if (auto *scene = timelineScene())
+ if (auto *view = scene->timelineView())
+ if (!transition.isValid() && view->isAttached())
+ emit scaleFactorChanged(0);
+}
+
+void TransitionEditorGraphicsLayout::setDuration(qreal duration)
+{
+ m_rulerItem->invalidateRulerSize(duration);
+}
+
+void TransitionEditorGraphicsLayout::setRulerScaleFactor(int factor)
+{
+ m_rulerItem->setRulerScaleFactor(factor);
+}
+
+void TransitionEditorGraphicsLayout::invalidate()
+{
+ m_layout->invalidate();
+}
+
+int TransitionEditorGraphicsLayout::maximumScrollValue() const
+{
+ const qreal w = this->geometry().width() - qreal(TimelineConstants::sectionWidth);
+ const qreal duration = m_rulerItem->rulerDuration() + m_rulerItem->rulerDuration() * 0.1;
+ const qreal maxr = m_rulerItem->rulerScaling() * duration - w;
+ return std::round(qMax(maxr, 0.0));
+}
+
+void TransitionEditorGraphicsLayout::activate()
+{
+ m_layout->activate();
+}
+
+TimelineRulerSectionItem *TransitionEditorGraphicsLayout::ruler() const
+{
+ return m_rulerItem;
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h
new file mode 100644
index 0000000000..9362abffdf
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** 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 "timelineitem.h"
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+
+namespace QmlDesigner {
+
+class TimelineItem;
+class TimelineRulerSectionItem;
+class TimelinePlaceholder;
+
+class ModelNode;
+
+class TransitionEditorGraphicsLayout : public TimelineItem
+{
+ Q_OBJECT
+
+signals:
+ void rulerClicked(const QPointF &pos);
+
+ void scaleFactorChanged(int factor);
+
+public:
+ TransitionEditorGraphicsLayout(QGraphicsScene *scene, TimelineItem *parent = nullptr);
+
+ ~TransitionEditorGraphicsLayout() override;
+
+public:
+ double rulerWidth() const;
+
+ double rulerScaling() const;
+
+ double rulerDuration() const;
+
+ double endFrame() const;
+
+ void setWidth(int width);
+
+ void setTransition(const ModelNode &transition);
+
+ void setDuration(qreal duration);
+
+ void setRulerScaleFactor(int factor);
+
+ void invalidate();
+
+ int maximumScrollValue() const;
+
+ void activate();
+
+ TimelineRulerSectionItem *ruler() const;
+
+private:
+ QGraphicsLinearLayout *m_layout = nullptr;
+
+ TimelineRulerSectionItem *m_rulerItem = nullptr;
+
+ TimelinePlaceholder *m_placeholder1 = nullptr;
+
+ TimelinePlaceholder *m_placeholder2 = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp
new file mode 100644
index 0000000000..036fe173f5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp
@@ -0,0 +1,431 @@
+/****************************************************************************
+**
+** 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 "transitioneditorgraphicsscene.h"
+
+#include "transitioneditorgraphicslayout.h"
+#include "transitioneditorpropertyitem.h"
+#include "transitioneditorsectionitem.h"
+#include "transitioneditortoolbar.h"
+#include "transitioneditorview.h"
+#include "transitioneditorwidget.h"
+
+#include "timelineactions.h"
+#include "timelineitem.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinemovetool.h"
+#include "timelineplaceholder.h"
+#include "timelinepropertyitem.h"
+#include "timelinesectionitem.h"
+
+#include <designdocumentview.h>
+#include <exception.h>
+#include <rewritertransaction.h>
+#include <rewriterview.h>
+#include <viewmanager.h>
+#include <qmldesignerplugin.h>
+#include <qmlobjectnode.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <bindingproperty.h>
+
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <utils/hostosinfo.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QGraphicsLinearLayout>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QKeyEvent>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+static int deleteKey()
+{
+ if (Utils::HostOsInfo::isMacHost())
+ return Qt::Key_Backspace;
+
+ return Qt::Key_Delete;
+}
+
+TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWidget *parent)
+ : AbstractScrollGraphicsScene(parent)
+ , m_parent(parent)
+ , m_layout(new TransitionEditorGraphicsLayout(this))
+ , m_tools(this)
+{
+ addItem(m_layout);
+
+ setSceneRect(m_layout->geometry());
+
+ connect(m_layout, &QGraphicsWidget::geometryChanged, this, [this]() {
+ auto rect = m_layout->geometry();
+
+ setSceneRect(rect);
+
+ if (auto *gview = graphicsView())
+ gview->setSceneRect(rect.adjusted(0, TimelineConstants::rulerHeight, 0, 0));
+
+ if (auto *rview = rulerView())
+ rview->setSceneRect(rect);
+ });
+
+ auto changeScale = [this](int factor) {
+ transitionEditorWidget()->changeScaleFactor(factor);
+ setRulerScaling(qreal(factor));
+ };
+ connect(m_layout, &TransitionEditorGraphicsLayout::scaleFactorChanged, changeScale);
+}
+
+TransitionEditorGraphicsScene::~TransitionEditorGraphicsScene()
+{
+ QSignalBlocker block(this);
+ qDeleteAll(items());
+}
+
+void TransitionEditorGraphicsScene::invalidateScrollbar()
+{
+ double max = m_layout->maximumScrollValue();
+ transitionEditorWidget()->setupScrollbar(0, max, scrollOffset());
+ if (scrollOffset() > max)
+ setScrollOffset(max);
+}
+
+void TransitionEditorGraphicsScene::onShow()
+{
+ emit m_layout->scaleFactorChanged(0);
+}
+
+void TransitionEditorGraphicsScene::setTransition(const ModelNode &transition)
+{
+ clearSelection();
+ m_layout->setTransition(transition);
+}
+
+void TransitionEditorGraphicsScene::clearTransition()
+{
+ m_transition = {};
+ m_layout->setTransition({});
+}
+
+void TransitionEditorGraphicsScene::setWidth(int width)
+{
+ m_layout->setWidth(width);
+ invalidateScrollbar();
+}
+
+void TransitionEditorGraphicsScene::invalidateLayout()
+{
+ m_layout->invalidate();
+}
+
+void TransitionEditorGraphicsScene::setDuration(int duration)
+{
+ if (m_transition.isValid())
+ m_transition.setAuxiliaryData("transitionDuration", duration);
+ m_layout->setDuration(duration);
+ qreal scaling = m_layout->rulerScaling();
+ setRulerScaling(scaling);
+}
+
+qreal TransitionEditorGraphicsScene::rulerScaling() const
+{
+ return m_layout->rulerScaling();
+}
+
+int TransitionEditorGraphicsScene::rulerWidth() const
+{
+ return m_layout->rulerWidth();
+}
+
+qreal TransitionEditorGraphicsScene::rulerDuration() const
+{
+ return m_layout->rulerDuration();
+}
+
+qreal TransitionEditorGraphicsScene::endFrame() const
+{
+ return m_layout->endFrame();
+}
+
+qreal TransitionEditorGraphicsScene::startFrame() const
+{
+ return 0;
+}
+
+qreal TransitionEditorGraphicsScene::mapToScene(qreal x) const
+{
+ return TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ + (x - startFrame()) * rulerScaling() - scrollOffset();
+}
+
+qreal TransitionEditorGraphicsScene::mapFromScene(qreal x) const
+{
+ auto xPosOffset = (x - TimelineConstants::sectionWidth - TimelineConstants::timelineLeftOffset)
+ + scrollOffset();
+
+ return xPosOffset / rulerScaling() + startFrame();
+}
+
+void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor)
+{
+ m_layout->setRulerScaleFactor(scaleFactor);
+
+ setScrollOffset(0);
+ invalidateSections();
+ invalidateScrollbar();
+ update();
+}
+
+void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode &target)
+{
+ if (!target.isValid())
+ return;
+
+ bool found = false;
+
+ const QList<QGraphicsItem *> items = m_layout->childItems();
+ for (auto child : items)
+ TimelineSectionItem::updateDataForTarget(child, target, &found);
+
+ if (!found)
+ invalidateScene();
+
+ clearSelection();
+ invalidateLayout();
+}
+
+void TransitionEditorGraphicsScene::invalidateScene()
+{
+ invalidateScrollbar();
+}
+
+void TransitionEditorGraphicsScene::invalidateCurrentValues()
+{
+ const QList<QGraphicsItem *> constItems = items();
+ for (auto item : constItems)
+ TimelinePropertyItem::updateTextEdit(item);
+}
+
+QGraphicsView *TransitionEditorGraphicsScene::graphicsView() const
+{
+ const QList<QGraphicsView *> constViews = views();
+ for (auto *v : constViews)
+ if (v->objectName() == "SceneView")
+ return v;
+
+ return nullptr;
+}
+
+QGraphicsView *TransitionEditorGraphicsScene::rulerView() const
+{
+ const QList<QGraphicsView *> constViews = views();
+ for (auto *v : constViews)
+ if (v->objectName() == "RulerView")
+ return v;
+
+ return nullptr;
+}
+
+QRectF TransitionEditorGraphicsScene::selectionBounds() const
+{
+ QRectF bbox;
+
+ return bbox;
+}
+
+void TransitionEditorGraphicsScene::clearSelection()
+{
+ if (m_selectedProperty)
+ m_selectedProperty->update();
+
+ m_selectedProperty = nullptr;
+ AbstractScrollGraphicsScene::clearSelection();
+}
+
+QList<QGraphicsItem *> TransitionEditorGraphicsScene::itemsAt(const QPointF &pos)
+{
+ QTransform transform;
+
+ if (auto *gview = graphicsView())
+ transform = gview->transform();
+
+ return items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform);
+}
+
+void TransitionEditorGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+
+ m_tools.mousePressEvent(topItem, event);
+ QGraphicsScene::mousePressEvent(event);
+}
+
+void TransitionEditorGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseMoveEvent(topItem, event);
+ QGraphicsScene::mouseMoveEvent(event);
+}
+
+void TransitionEditorGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ /* The tool has handle the event last. */
+ QGraphicsScene::mouseReleaseEvent(event);
+ m_tools.mouseReleaseEvent(topItem, event);
+}
+
+void TransitionEditorGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseDoubleClickEvent(topItem, event);
+ QGraphicsScene::mouseDoubleClickEvent(event);
+}
+
+void TransitionEditorGraphicsScene::keyPressEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyPressEvent(keyEvent);
+ return;
+ }
+
+ if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) {
+ QGraphicsScene::keyPressEvent(keyEvent);
+ } else {
+ switch (keyEvent->key()) {
+ case Qt::Key_Left:
+ emit scroll(TimelineUtils::Side::Left);
+ keyEvent->accept();
+ break;
+
+ case Qt::Key_Right:
+ emit scroll(TimelineUtils::Side::Right);
+ keyEvent->accept();
+ break;
+
+ default:
+ QGraphicsScene::keyPressEvent(keyEvent);
+ break;
+ }
+ }
+}
+
+void TransitionEditorGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+ return;
+ }
+
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+}
+
+void TransitionEditorGraphicsScene::invalidateSections()
+{
+ const QList<QGraphicsItem *> children = m_layout->childItems();
+ for (auto child : children)
+ TransitionEditorSectionItem::updateData(child);
+
+ clearSelection();
+ invalidateLayout();
+}
+
+TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const
+{
+ return m_parent->transitionEditorView();
+}
+
+TransitionEditorWidget *TransitionEditorGraphicsScene::transitionEditorWidget() const
+{
+ return m_parent;
+}
+
+TransitionEditorToolBar *TransitionEditorGraphicsScene::toolBar() const
+{
+ return transitionEditorWidget()->toolBar();
+}
+
+void TransitionEditorGraphicsScene::activateLayout()
+{
+ m_layout->activate();
+}
+
+AbstractView *TransitionEditorGraphicsScene::abstractView() const
+{
+ return transitionEditorView();
+}
+
+bool TransitionEditorGraphicsScene::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ShortcutOverride:
+ if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) {
+ QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event));
+ event->accept();
+ return true;
+ }
+ Q_FALLTHROUGH();
+ default:
+ return QGraphicsScene::event(event);
+ }
+}
+
+ModelNode TransitionEditorGraphicsScene::transitionModelNode() const
+{
+ if (transitionEditorView()->isAttached()) {
+ const QString timelineId = transitionEditorWidget()->toolBar()->currentTransitionId();
+ return transitionEditorView()->modelNodeForId(timelineId);
+ }
+
+ return ModelNode();
+}
+
+TransitionEditorPropertyItem *TransitionEditorGraphicsScene::selectedPropertyItem() const
+{
+ return m_selectedProperty;
+}
+
+void TransitionEditorGraphicsScene::setSelectedPropertyItem(TransitionEditorPropertyItem *item)
+{
+ if (m_selectedProperty)
+ m_selectedProperty->update();
+ m_selectedProperty = item;
+ emit selectionChanged();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h
new file mode 100644
index 0000000000..2f04c5b729
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** 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 <timelineeditor/timelinegraphicsscene.h>
+
+#include <qmltimeline.h>
+
+#include <QGraphicsScene>
+
+#include <memory>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+
+namespace QmlDesigner {
+
+class TransitionEditorView;
+class TransitionEditorWidget;
+class TransitionEditorToolBar;
+class TransitionEditorGraphicsLayout;
+
+class TimelineRulerSectionItem;
+class TimelineFrameHandle;
+class TimelineAbstractTool;
+class TimelineMoveTool;
+class TimelineKeyframeItem;
+class TimelinePlaceholder;
+class TimelineToolBar;
+class TransitionEditorPropertyItem;
+
+class TransitionEditorGraphicsScene : public AbstractScrollGraphicsScene
+{
+ Q_OBJECT
+
+signals:
+ void selectionChanged();
+
+ void scroll(const TimelineUtils::Side &side);
+
+public:
+ explicit TransitionEditorGraphicsScene(TransitionEditorWidget *parent);
+
+ ~TransitionEditorGraphicsScene() override;
+
+ void onShow();
+
+ void setTransition(const ModelNode &transition);
+ void clearTransition();
+
+ void setWidth(int width);
+
+ void invalidateLayout();
+ void setDuration(int duration);
+
+ TransitionEditorView *transitionEditorView() const;
+ TransitionEditorWidget *transitionEditorWidget() const;
+ TransitionEditorToolBar *toolBar() const;
+
+ qreal rulerScaling() const override;
+ int rulerWidth() const override;
+ qreal rulerDuration() const override;
+ qreal endFrame() const override;
+ qreal startFrame() const override;
+
+ qreal mapToScene(qreal x) const;
+ qreal mapFromScene(qreal x) const;
+
+ void setRulerScaling(int scaling);
+
+ void invalidateSectionForTarget(const ModelNode &modelNode);
+
+ void invalidateScene();
+ void invalidateCurrentValues();
+ void invalidateRecordButtonsStatus();
+
+ QGraphicsView *graphicsView() const;
+ QGraphicsView *rulerView() const;
+
+ QRectF selectionBounds() const;
+
+ void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items);
+ void clearSelection() override;
+
+ void activateLayout();
+
+ AbstractView *abstractView() const override;
+ ModelNode transitionModelNode() const;
+
+ TransitionEditorPropertyItem *selectedPropertyItem() const;
+ void setSelectedPropertyItem(TransitionEditorPropertyItem *item);
+
+ void invalidateScrollbar() override;
+
+signals:
+ void statusBarMessageChanged(const QString &message);
+
+protected:
+ bool event(QEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void keyPressEvent(QKeyEvent *keyEvent) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+private:
+ void invalidateSections();
+ QList<QGraphicsItem *> itemsAt(const QPointF &pos);
+
+private:
+ TransitionEditorWidget *m_parent = nullptr;
+ TransitionEditorGraphicsLayout *m_layout = nullptr;
+ ModelNode m_transition;
+
+ int m_scrollOffset = 0;
+ TimelineToolDelegate m_tools;
+ TransitionEditorPropertyItem *m_selectedProperty = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp
new file mode 100644
index 0000000000..f5b7b45b05
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** 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 "transitioneditorpropertyitem.h"
+
+#include "abstractview.h"
+#include "timelineconstants.h"
+#include "timelineicons.h"
+#include "transitioneditorgraphicsscene.h"
+
+#include <bindingproperty.h>
+#include <nodeabstractproperty.h>
+#include <rewritertransaction.h>
+#include <rewritingexception.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmlobjectnode.h>
+
+#include <coreplugin/icore.h>
+#include <utils/qtcassert.h>
+#include <utils/utilsicons.h>
+
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <coreplugin/icore.h>
+
+#include <QCursor>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QLineEdit>
+#include <QMenu>
+#include <QPainter>
+
+#include <algorithm>
+
+namespace QmlDesigner {
+
+TransitionEditorPropertyItem *TransitionEditorPropertyItem::create(
+ const ModelNode &animation, TransitionEditorSectionItem *parent)
+{
+ auto item = new TransitionEditorPropertyItem(parent);
+ item->m_animation = animation;
+
+ auto sectionItem = new QGraphicsWidget(item);
+
+ sectionItem->setGeometry(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight);
+
+ sectionItem->setZValue(10);
+ sectionItem->setCursor(Qt::ArrowCursor);
+
+ item->setToolTip(item->propertyName());
+ item->resize(parent->size());
+
+ item->m_barItem = new TransitionEditorBarItem(item);
+ item->invalidateBar();
+
+ return item;
+}
+
+int TransitionEditorPropertyItem::type() const
+{
+ return Type;
+}
+
+void TransitionEditorPropertyItem::updateData()
+{
+ invalidateBar();
+}
+
+void TransitionEditorPropertyItem::updateParentData()
+{
+ TransitionEditorSectionItem::invalidateBar(parentItem());
+}
+
+bool TransitionEditorPropertyItem::isSelected() const
+{
+ return transitionEditorGraphicsScene()->selectedPropertyItem() == this;
+}
+
+QString TransitionEditorPropertyItem::propertyName() const
+{
+ if (m_animation.isValid()) {
+ const QString propertyName = m_animation.variantProperty("property").value().toString();
+ if (!propertyName.isEmpty())
+ return propertyName;
+ return m_animation.variantProperty("properties").value().toString();
+ }
+ return QString();
+}
+
+void TransitionEditorPropertyItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *,
+ QWidget *)
+{
+ painter->save();
+
+ static const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ static const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ static const QColor backgroundColor = Theme::instance()
+ ->qmlDesignerBackgroundColorDarkAlternate();
+
+ painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColor);
+ painter->fillRect(TimelineConstants::textIndentationProperties - 4,
+ 0,
+ TimelineConstants::sectionWidth - TimelineConstants::textIndentationProperties
+ + 4,
+ size().height(),
+ backgroundColor.darker(110));
+
+ painter->setPen(penColor);
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height());
+
+ drawLine(painter,
+ TimelineConstants::textIndentationProperties - 4,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ painter->setPen(textColor);
+
+ const QFontMetrics metrics(font());
+
+ const QString elidedText = metrics.elidedText(propertyName(),
+ Qt::ElideMiddle,
+ qreal(TimelineConstants::sectionWidth) * 2.0 / 3
+ - TimelineConstants::textIndentationProperties,
+ 0);
+
+ painter->drawText(TimelineConstants::textIndentationProperties, 12, elidedText);
+
+ painter->restore();
+}
+
+void TransitionEditorPropertyItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event */) {}
+
+TransitionEditorPropertyItem::TransitionEditorPropertyItem(TransitionEditorSectionItem *parent)
+ : TimelineItem(parent)
+{
+ setPreferredHeight(TimelineConstants::sectionHeight);
+ setMinimumHeight(TimelineConstants::sectionHeight);
+ setMaximumHeight(TimelineConstants::sectionHeight);
+}
+
+TransitionEditorGraphicsScene *TransitionEditorPropertyItem::transitionEditorGraphicsScene() const
+{
+ return qobject_cast<TransitionEditorGraphicsScene *>(scene());
+}
+
+void TransitionEditorPropertyItem::invalidateBar()
+{
+ qreal min = 0;
+ qreal max = 0;
+
+ QTC_ASSERT(m_animation.isValid(), return );
+ QTC_ASSERT(m_animation.hasParentProperty(), return );
+
+ const ModelNode parent = m_animation.parentProperty().parentModelNode();
+
+ for (const ModelNode &child : parent.directSubModelNodes())
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ min = child.variantProperty("duration").value().toDouble();
+
+ max = m_animation.variantProperty("duration").value().toDouble() + min;
+
+ const qreal sceneMin = m_barItem->mapFromFrameToScene(min);
+
+ QRectF barRect(sceneMin,
+ 0,
+ (max - min) * m_barItem->rulerScaling(),
+ TimelineConstants::sectionHeight - 1);
+
+ m_barItem->setRect(barRect);
+}
+
+AbstractView *TransitionEditorPropertyItem::view() const
+{
+ return m_animation.view();
+}
+
+ModelNode TransitionEditorPropertyItem::propertyAnimation() const
+{
+ return m_animation;
+}
+
+ModelNode TransitionEditorPropertyItem::pauseAnimation() const
+{
+ QTC_ASSERT(m_animation.isValid(), return {});
+ QTC_ASSERT(m_animation.hasParentProperty(), return {});
+
+ const ModelNode parent = m_animation.parentProperty().parentModelNode();
+
+ for (const ModelNode &child : parent.directSubModelNodes())
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ return child;
+
+ return {};
+}
+
+void TransitionEditorPropertyItem::select()
+{
+ transitionEditorGraphicsScene()->setSelectedPropertyItem(this);
+ m_barItem->update();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h
new file mode 100644
index 0000000000..aba42b599d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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 "transitioneditorsectionitem.h"
+
+#include <modelnode.h>
+
+#include <QGraphicsRectItem>
+
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+
+namespace QmlDesigner {
+
+class TransitionEditorGraphicsScene;
+
+class TransitionEditorPropertyItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TransitionEditorConstants::transitionEditorPropertyItemUserType };
+
+ static TransitionEditorPropertyItem *create(const ModelNode &animation,
+ TransitionEditorSectionItem *parent = nullptr);
+ int type() const override;
+ void updateData();
+ void updateParentData();
+
+ bool isSelected() const;
+ QString propertyName() const;
+ void invalidateBar();
+ AbstractView *view() const;
+ ModelNode propertyAnimation() const;
+ ModelNode pauseAnimation() const;
+ void select();
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ TransitionEditorPropertyItem(TransitionEditorSectionItem *parent = nullptr);
+ TransitionEditorGraphicsScene *transitionEditorGraphicsScene() const;
+
+ ModelNode m_animation;
+ TransitionEditorBarItem *m_barItem;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp
new file mode 100644
index 0000000000..86442059d9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp
@@ -0,0 +1,803 @@
+/****************************************************************************
+**
+** 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 "transitioneditorsectionitem.h"
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorpropertyitem.h"
+
+#include "timelineactions.h"
+#include "timelineconstants.h"
+#include "timelineicons.h"
+#include "timelinepropertyitem.h"
+#include "timelineutils.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <variantproperty.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <rewritingexception.h>
+
+#include <theme.h>
+
+#include <utils/qtcassert.h>
+
+#include <QAction>
+#include <QApplication>
+#include <QColorDialog>
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QMenu>
+#include <QPainter>
+#include <QPainterPath>
+
+#include <QGraphicsView>
+
+#include <QDebug>
+
+#include <cmath>
+#include <limits>
+
+namespace QmlDesigner {
+
+static void scaleDuration(const ModelNode &node, qreal s)
+{
+ if (node.hasVariantProperty("duration")) {
+ qreal old = node.variantProperty("duration").value().toDouble();
+ node.variantProperty("duration").setValue(qRound(old * s));
+ }
+}
+
+static void moveDuration(const ModelNode &node, qreal s)
+{
+ if (node.hasVariantProperty("duration")) {
+ qreal old = node.variantProperty("duration").value().toDouble();
+ node.variantProperty("duration").setValue(old + s);
+ }
+}
+
+class ClickDummy : public TimelineItem
+{
+public:
+ explicit ClickDummy(TransitionEditorSectionItem *parent)
+ : TimelineItem(parent)
+ {
+ setGeometry(0, 0, TimelineConstants::sectionWidth, TimelineConstants::sectionHeight);
+
+ setZValue(10);
+ setCursor(Qt::ArrowCursor);
+ }
+
+protected:
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+};
+
+TransitionEditorSectionItem::TransitionEditorSectionItem(TimelineItem *parent)
+ : TimelineItem(parent)
+{}
+
+TransitionEditorSectionItem *TransitionEditorSectionItem::create(const ModelNode &animation,
+ TimelineItem *parent)
+{
+ auto item = new TransitionEditorSectionItem(parent);
+
+ ModelNode target;
+
+ if (animation.isValid()) {
+ const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+
+ for (const ModelNode &child : propertyAnimations) {
+ if (child.hasBindingProperty("target"))
+ target = child.bindingProperty("target").resolveToModelNode();
+ }
+ }
+
+ item->m_targetNode = target;
+ item->m_animationNode = animation;
+ item->createPropertyItems();
+
+ if (target.isValid())
+ item->setToolTip(target.id());
+
+ item->m_dummyItem = new ClickDummy(item);
+ item->m_dummyItem->update();
+
+ item->m_barItem = new TransitionEditorBarItem(item);
+ item->invalidateBar();
+ item->invalidateHeight();
+
+ return item;
+}
+
+void TransitionEditorSectionItem::invalidateBar()
+{
+ qreal min = std::numeric_limits<qreal>::max();
+ qreal max = 0;
+
+ if (!m_animationNode.isValid())
+ return;
+
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ qreal locMin = 0;
+ qreal locMax = 0;
+
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation"))
+ locMax = child.variantProperty("duration").value().toDouble();
+ else if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ locMin = child.variantProperty("duration").value().toDouble();
+ }
+
+ locMax = locMax + locMin;
+
+ min = qMin(min, locMin);
+ max = qMax(max, locMax);
+ }
+
+ const qreal sceneMin = m_barItem->mapFromFrameToScene(min);
+
+ QRectF barRect(sceneMin,
+ 0,
+ (max - min) * m_barItem->rulerScaling(),
+ TimelineConstants::sectionHeight - 1);
+
+ m_barItem->setRect(barRect);
+}
+
+int TransitionEditorSectionItem::type() const
+{
+ return Type;
+}
+
+void TransitionEditorSectionItem::updateData(QGraphicsItem *item)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item))
+ sectionItem->updateData();
+}
+
+void TransitionEditorSectionItem::invalidateBar(QGraphicsItem *item)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item))
+ sectionItem->invalidateBar();
+}
+
+void TransitionEditorSectionItem::updateDataForTarget(QGraphicsItem *item,
+ const ModelNode &target,
+ bool *b)
+{
+ if (!target.isValid())
+ return;
+
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) {
+ if (sectionItem->m_targetNode == target) { //TODO update animation node
+ sectionItem->updateData();
+ if (b)
+ *b = true;
+ }
+ }
+}
+
+void TransitionEditorSectionItem::moveAllDurations(qreal offset)
+{
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ moveDuration(child, offset);
+ }
+ }
+}
+
+void TransitionEditorSectionItem::scaleAllDurations(qreal scale)
+{
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation"))
+ scaleDuration(child, scale);
+ }
+ }
+}
+
+qreal TransitionEditorSectionItem::firstFrame()
+{
+ return 0;
+ //if (!m_timeline.isValid())
+ //return 0;
+
+ //return m_timeline.minActualKeyframe(m_targetNode);
+}
+
+AbstractView *TransitionEditorSectionItem::view() const
+{
+ return m_animationNode.view();
+}
+
+bool TransitionEditorSectionItem::isSelected() const
+{
+ return m_targetNode.isValid() && m_targetNode.isSelected();
+}
+
+ModelNode TransitionEditorSectionItem::targetNode() const
+{
+ return m_targetNode;
+}
+
+static QPixmap rotateby90(const QPixmap &pixmap)
+{
+ QImage sourceImage = pixmap.toImage();
+ QImage destImage(pixmap.height(), pixmap.width(), sourceImage.format());
+
+ for (int x = 0; x < pixmap.width(); x++)
+ for (int y = 0; y < pixmap.height(); y++)
+ destImage.setPixel(y, x, sourceImage.pixel(x, y));
+
+ QPixmap result = QPixmap::fromImage(destImage);
+
+ result.setDevicePixelRatio(pixmap.devicePixelRatio());
+
+ return result;
+}
+
+static int devicePixelHeight(const QPixmap &pixmap)
+{
+ return pixmap.height() / pixmap.devicePixelRatioF();
+}
+
+void TransitionEditorSectionItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem * /*option*/,
+ QWidget *)
+{
+ if (m_targetNode.isValid()) {
+ painter->save();
+
+ const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ QColor brushColor = Theme::getColor(Theme::BackgroundColorDark);
+
+ int fillOffset = 0;
+ if (isSelected()) {
+ brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ fillOffset = 1;
+ }
+
+ painter->fillRect(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - fillOffset,
+ brushColor);
+ painter->fillRect(TimelineConstants::sectionWidth,
+ 0,
+ size().width() - TimelineConstants::sectionWidth,
+ size().height(),
+ Theme::instance()->qmlDesignerBackgroundColorDarkAlternate());
+
+ painter->setPen(penColor);
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height() - 1);
+ drawLine(painter,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ static const QPixmap arrow = Theme::getPixmap("down-arrow");
+
+ static const QPixmap arrow90 = rotateby90(arrow);
+
+ const QPixmap rotatedArrow = collapsed() ? arrow90 : arrow;
+
+ const int textOffset = QFontMetrics(font()).ascent()
+ + (TimelineConstants::sectionHeight - QFontMetrics(font()).height())
+ / 2;
+
+ painter->drawPixmap(collapsed() ? 6 : 4,
+ (TimelineConstants::sectionHeight - devicePixelHeight(rotatedArrow)) / 2,
+ rotatedArrow);
+
+ painter->setPen(textColor);
+
+ QFontMetrics fm(painter->font());
+ const QString elidedId = fm.elidedText(m_targetNode.id(),
+ Qt::ElideMiddle,
+ TimelineConstants::sectionWidth
+ - TimelineConstants::textIndentationSections);
+ painter->drawText(TimelineConstants::textIndentationSections, textOffset, elidedId);
+
+ painter->restore();
+ }
+}
+
+void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight
+ || event->pos().x() < TimelineConstants::textIndentationSections) {
+ TimelineItem::mouseDoubleClickEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton) {
+ event->accept();
+ toggleCollapsed();
+ }
+}
+
+void TransitionEditorSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mousePressEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton)
+ event->accept();
+}
+
+void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mouseReleaseEvent(event);
+ return;
+ }
+
+ if (event->button() != Qt::LeftButton)
+ return;
+
+ event->accept();
+
+ if (event->pos().x() > TimelineConstants::textIndentationSections
+ && event->button() == Qt::LeftButton) {
+ if (m_targetNode.isValid())
+ m_targetNode.view()->setSelectedModelNode(m_targetNode);
+ } else {
+ toggleCollapsed();
+ }
+ update();
+}
+
+void TransitionEditorSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event)
+{
+ TimelineItem::resizeEvent(event);
+
+ for (auto child : propertyItems()) {
+ TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child);
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+}
+
+void TransitionEditorSectionItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *) {}
+
+void TransitionEditorSectionItem::updateData()
+{
+ invalidateBar();
+ resize(rulerWidth(), size().height());
+ invalidateProperties();
+ update();
+}
+
+const QList<QGraphicsItem *> TransitionEditorSectionItem::propertyItems() const
+{
+ QList<QGraphicsItem *> list;
+
+ const QList<QGraphicsItem *> children = childItems();
+ for (auto child : children) {
+ if (m_barItem != child && m_dummyItem != child)
+ list.append(child);
+ }
+
+ return list;
+}
+
+void TransitionEditorSectionItem::invalidateHeight()
+{
+ int height = 0;
+ bool visible = true;
+
+ if (collapsed()) {
+ height = TimelineConstants::sectionHeight;
+ visible = false;
+ } else {
+ const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+
+ height = TimelineConstants::sectionHeight
+ + propertyAnimations.count() * TimelineConstants::sectionHeight;
+ visible = true;
+ }
+
+ for (auto child : propertyItems())
+ child->setVisible(visible);
+
+ setPreferredHeight(height);
+ setMinimumHeight(height);
+ setMaximumHeight(height);
+
+ auto transitionScene = qobject_cast<TransitionEditorGraphicsScene *>(scene());
+ transitionScene->activateLayout();
+}
+
+void TransitionEditorSectionItem::createPropertyItems()
+{
+ int yPos = TimelineConstants::sectionHeight;
+ const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+ for (const auto &anim : propertyAnimations) {
+ auto item = TransitionEditorPropertyItem::create(anim, this);
+ item->setY(yPos);
+ yPos = yPos + TimelineConstants::sectionHeight;
+ }
+}
+
+void TransitionEditorSectionItem::invalidateProperties()
+{
+ for (auto child : propertyItems()) {
+ delete child;
+ }
+
+ createPropertyItems();
+
+ for (auto child : propertyItems()) {
+ TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child);
+ item->updateData();
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+ invalidateHeight();
+}
+
+bool TransitionEditorSectionItem::collapsed() const
+{
+ return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded");
+}
+
+qreal TransitionEditorSectionItem::rulerWidth() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene())->rulerWidth();
+}
+
+void TransitionEditorSectionItem::toggleCollapsed()
+{
+ QTC_ASSERT(m_targetNode.isValid(), return );
+
+ if (collapsed())
+ m_targetNode.setAuxiliaryData("timeline_expanded", true);
+ else
+ m_targetNode.removeAuxiliaryData("timeline_expanded");
+
+ invalidateHeight();
+}
+
+TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorSectionItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ setAcceptHoverEvents(true);
+ setPen(Qt::NoPen);
+}
+
+TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorPropertyItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ setAcceptHoverEvents(true);
+ setPen(Qt::NoPen);
+}
+
+void TransitionEditorBarItem::itemMoved(const QPointF &start, const QPointF &end)
+{
+ if (isActiveHandle(Location::Undefined))
+ dragInit(rect(), start);
+
+ qreal min = qreal(TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ - scrollOffset());
+ qreal max = qreal(abstractScrollGraphicsScene()->rulerWidth() - TimelineConstants::sectionWidth
+ + rect().width());
+
+ const qreal minFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->startFrame());
+ const qreal maxFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->endFrame());
+
+ if (min < minFrameX)
+ min = minFrameX;
+
+ if (max > maxFrameX)
+ max = maxFrameX;
+
+ if (isActiveHandle(Location::Center))
+ dragCenter(rect(), end, min, max);
+ else
+ dragHandle(rect(), end, min, max);
+
+ emit abstractScrollGraphicsScene()->statusBarMessageChanged(
+ tr("Range from %1 to %2")
+ .arg(qRound(mapFromSceneToFrame(rect().x())))
+ .arg(qRound(mapFromSceneToFrame(rect().width() + rect().x()))));
+}
+
+void TransitionEditorBarItem::commitPosition(const QPointF & /*point*/)
+{
+ if (sectionItem() && sectionItem()->view()) {
+ if (m_handle != Location::Undefined) {
+ sectionItem()
+ ->view()
+ ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() {
+ qreal scaleFactor = rect().width() / m_oldRect.width();
+
+ qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling();
+ qreal supposedFirstFrame = qRound(moved);
+
+ sectionItem()->scaleAllDurations(scaleFactor);
+ sectionItem()->moveAllDurations(supposedFirstFrame);
+ sectionItem()->updateData();
+ });
+ }
+ } else if (propertyItem() && propertyItem()->view()) {
+ if (m_handle != Location::Undefined) {
+ propertyItem()
+ ->view()
+ ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() {
+ qreal scaleFactor = rect().width() / m_oldRect.width();
+ qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling();
+ qreal supposedFirstFrame = qRound(moved);
+ scaleDuration(propertyItem()->propertyAnimation(), scaleFactor);
+ moveDuration(propertyItem()->pauseAnimation(), supposedFirstFrame);
+ propertyItem()->updateData();
+ propertyItem()->updateParentData();
+ });
+ }
+ }
+
+ m_handle = Location::Undefined;
+ m_bounds = Location::Undefined;
+ m_pivot = 0.0;
+ m_oldRect = QRectF();
+ scrollOffsetChanged();
+}
+
+void TransitionEditorBarItem::scrollOffsetChanged()
+{
+ if (sectionItem())
+ sectionItem()->invalidateBar();
+ else if (propertyItem())
+ propertyItem()->invalidateBar();
+}
+
+void TransitionEditorBarItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ QColor brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ QColor brushColorSection = Theme::getColor(Theme::QmlDesigner_HighlightColor).darker(120);
+ QColor penColor = Theme::getColor(Theme::QmlDesigner_HighlightColor).lighter(140);
+
+ const QRectF itemRect = rect();
+
+ painter->save();
+ painter->setClipRect(TimelineConstants::sectionWidth,
+ 0,
+ itemRect.width() + itemRect.x(),
+ itemRect.height());
+
+ if (sectionItem())
+ painter->fillRect(itemRect, brushColorSection);
+ else
+ painter->fillRect(itemRect, brushColor);
+
+ if (propertyItem() && propertyItem()->isSelected()) {
+ painter->setPen(penColor);
+ painter->drawRect(itemRect.adjusted(0, 0, 0, -1));
+ }
+
+ painter->restore();
+}
+
+void TransitionEditorBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
+{
+ const auto p = event->pos();
+
+ QRectF left, right;
+ if (handleRects(rect(), left, right)) {
+ if (left.contains(p) || right.contains(p)) {
+ if (cursor().shape() != Qt::SizeHorCursor)
+ setCursor(QCursor(Qt::SizeHorCursor));
+ } else if (rect().contains(p)) {
+ if (cursor().shape() != Qt::ClosedHandCursor)
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+ } else {
+ if (rect().contains(p))
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+}
+
+void TransitionEditorBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event*/) {}
+
+void TransitionEditorBarItem::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/)
+{
+ if (propertyItem())
+ propertyItem()->select();
+}
+
+TransitionEditorSectionItem *TransitionEditorBarItem::sectionItem() const
+{
+ return qgraphicsitem_cast<TransitionEditorSectionItem *>(parentItem());
+}
+
+TransitionEditorPropertyItem *TransitionEditorBarItem::propertyItem() const
+{
+ return qgraphicsitem_cast<TransitionEditorPropertyItem *>(parentItem());
+}
+
+void TransitionEditorBarItem::dragInit(const QRectF &rect, const QPointF &pos)
+{
+ QRectF left, right;
+ m_oldRect = rect;
+ if (handleRects(rect, left, right)) {
+ if (left.contains(pos)) {
+ m_handle = Location::Left;
+ m_pivot = pos.x() - left.topLeft().x();
+ } else if (right.contains(pos)) {
+ m_handle = Location::Right;
+ m_pivot = pos.x() - right.topRight().x();
+ } else if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+
+ } else {
+ if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+ }
+}
+
+void TransitionEditorBarItem::dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ if (validateBounds(pos.x() - rect.topLeft().x())) {
+ qreal targetX = pos.x() - m_pivot;
+
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.moveLeft(targetX);
+ if (rect.topLeft().x() < min) {
+ rect.moveLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.topRight().x() > max) {
+ rect.moveRight(max);
+ setOutOfBounds(Location::Right);
+ }
+ setRect(rect);
+ }
+}
+
+void TransitionEditorBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ QRectF left, right;
+ handleRects(rect, left, right);
+
+ if (isActiveHandle(Location::Left)) {
+ if (validateBounds(pos.x() - left.topLeft().x())) {
+ qreal targetX = pos.x() - m_pivot;
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.setLeft(targetX);
+ if (rect.left() < min) {
+ rect.setLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.left() >= rect.right() - minimumBarWidth)
+ rect.setLeft(rect.right() - minimumBarWidth);
+
+ setRect(rect);
+ }
+ } else if (isActiveHandle(Location::Right)) {
+ if (validateBounds(pos.x() - right.topRight().x())) {
+ qreal targetX = pos.x() - m_pivot;
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.setRight(targetX);
+ if (rect.right() > max) {
+ rect.setRight(max);
+ setOutOfBounds(Location::Right);
+ } else if (rect.right() <= rect.left() + minimumBarWidth)
+ rect.setRight(rect.left() + minimumBarWidth);
+
+ setRect(rect);
+ }
+ }
+}
+
+bool TransitionEditorBarItem::handleRects(const QRectF &rect, QRectF &left, QRectF &right) const
+{
+ if (rect.width() < minimumBarWidth)
+ return false;
+
+ const qreal handleSize = rect.height();
+
+ auto handleRect = QRectF(0, 0, handleSize, handleSize);
+ handleRect.moveCenter(rect.center());
+
+ handleRect.moveLeft(rect.left());
+ left = handleRect;
+
+ handleRect.moveRight(rect.right());
+ right = handleRect;
+
+ return true;
+}
+
+bool TransitionEditorBarItem::isActiveHandle(Location location) const
+{
+ return m_handle == location;
+}
+
+void TransitionEditorBarItem::setOutOfBounds(Location location)
+{
+ m_bounds = location;
+ update();
+}
+
+bool TransitionEditorBarItem::validateBounds(qreal distance)
+{
+ update();
+ if (m_bounds == Location::Left) {
+ if (distance > m_pivot)
+ m_bounds = Location::Center;
+ return false;
+
+ } else if (m_bounds == Location::Right) {
+ if (distance < m_pivot)
+ m_bounds = Location::Center;
+ return false;
+ }
+ return true;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h
new file mode 100644
index 0000000000..22aa21dd28
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** 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 "transitioneditorconstants.h"
+
+#include <timelineeditor/timelineitem.h>
+#include <timelineeditor/timelinemovableabstractitem.h>
+
+#include <modelnode.h>
+#include <qmltimeline.h>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QPainter)
+
+namespace QmlDesigner {
+
+class TransitionEditorSectionItem;
+class TransitionEditorPropertyItem;
+
+class TransitionEditorBarItem : public TimelineMovableAbstractItem
+{
+ Q_DECLARE_TR_FUNCTIONS(TimelineBarItem)
+
+ enum class Location { Undefined, Center, Left, Right };
+
+public:
+ explicit TransitionEditorBarItem(TransitionEditorSectionItem *parent);
+ explicit TransitionEditorBarItem(TransitionEditorPropertyItem *parent);
+
+ void itemMoved(const QPointF &start, const QPointF &end) override;
+ void commitPosition(const QPointF &point) override;
+
+protected:
+ void scrollOffsetChanged() override;
+ void paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+
+private:
+ TransitionEditorSectionItem *sectionItem() const;
+ TransitionEditorPropertyItem *propertyItem() const;
+
+ void dragInit(const QRectF &rect, const QPointF &pos);
+ void dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ void dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ bool handleRects(const QRectF &rect, QRectF &left, QRectF &right) const;
+ bool isActiveHandle(Location location) const;
+
+ void setOutOfBounds(Location location);
+ bool validateBounds(qreal pivot);
+
+private:
+ Location m_handle = Location::Undefined;
+
+ Location m_bounds = Location::Undefined;
+
+ qreal m_pivot = 0.0;
+
+ QRectF m_oldRect;
+
+ static constexpr qreal minimumBarWidth = 2.0
+ * static_cast<qreal>(TimelineConstants::sectionHeight);
+};
+
+class TransitionEditorSectionItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TransitionEditorConstants::transitionEditorSectionItemUserType };
+
+ static TransitionEditorSectionItem *create(const ModelNode &animation,
+ TimelineItem *parent);
+
+ void invalidateBar();
+
+ int type() const override;
+
+ static void updateData(QGraphicsItem *item);
+ static void invalidateBar(QGraphicsItem *item);
+ static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b);
+
+ void moveAllDurations(qreal offset);
+ void scaleAllDurations(qreal scale);
+ qreal firstFrame();
+ AbstractView *view() const;
+ bool isSelected() const;
+
+ ModelNode targetNode() const;
+ void updateData();
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void resizeEvent(QGraphicsSceneResizeEvent *event) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ void invalidateHeight();
+ void invalidateProperties();
+ bool collapsed() const;
+ qreal rulerWidth() const;
+ void toggleCollapsed();
+ void createPropertyItems();
+ const QList<QGraphicsItem *> propertyItems() const;
+
+ TransitionEditorSectionItem(TimelineItem *parent = nullptr);
+ ModelNode m_targetNode;
+ ModelNode m_animationNode;
+
+ TransitionEditorBarItem *m_barItem;
+ TimelineItem *m_dummyItem;
+
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp
new file mode 100644
index 0000000000..57993d7ac2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** 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 "transitioneditorsettingsdialog.h"
+#include "timelinesettingsdialog.h"
+#include "transitioneditorview.h"
+#include "ui_transitioneditorsettingsdialog.h"
+
+#include "timelineicons.h"
+#include "timelinesettingsmodel.h"
+#include "transitionform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QKeyEvent>
+#include <QToolBar>
+
+namespace QmlDesigner {
+
+static void deleteAllTabs(QTabWidget *tabWidget)
+{
+ while (tabWidget->count() > 0) {
+ QWidget *w = tabWidget->widget(0);
+ tabWidget->removeTab(0);
+ delete w;
+ }
+}
+
+static ModelNode getTransitionFromTabWidget(QTabWidget *tabWidget)
+{
+ QWidget *w = tabWidget->currentWidget();
+ if (w)
+ return qobject_cast<TransitionForm *>(w)->transition();
+ return QmlTimeline();
+}
+
+static void setTabForTransition(QTabWidget *tabWidget, const ModelNode &timeline)
+{
+ for (int i = 0; i < tabWidget->count(); ++i) {
+ QWidget *w = tabWidget->widget(i);
+ if (qobject_cast<TransitionForm *>(w)->transition() == timeline) {
+ tabWidget->setCurrentIndex(i);
+ return;
+ }
+ }
+}
+
+TransitionEditorSettingsDialog::TransitionEditorSettingsDialog(QWidget *parent,
+ class TransitionEditorView *view)
+ : QDialog(parent)
+ , ui(new Ui::TransitionEditorSettingsDialog)
+ , m_transitionEditorView(view)
+{
+ //m_timelineSettingsModel = new TimelineSettingsModel(this, view);
+
+ ui->setupUi(this);
+
+ auto *transitionCornerWidget = new QToolBar;
+
+ auto *transitionAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(),
+ tr("Add Transition"));
+ auto *transitionRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
+ tr("Remove Transition"));
+
+ connect(transitionAddAction, &QAction::triggered, this, [this]() {
+ setupTransitions(m_transitionEditorView->addNewTransition());
+ });
+
+ connect(transitionRemoveAction, &QAction::triggered, this, [this]() {
+ ModelNode transition = getTransitionFromTabWidget(ui->timelineTab);
+ if (transition.isValid()) {
+ transition.destroy();
+ setupTransitions({});
+ }
+ });
+
+ transitionCornerWidget->addAction(transitionAddAction);
+ transitionCornerWidget->addAction(transitionRemoveAction);
+
+ ui->timelineTab->setCornerWidget(transitionCornerWidget, Qt::TopRightCorner);
+
+ setupTransitions({});
+
+ connect(ui->timelineTab, &QTabWidget::currentChanged, this, [this]() {
+ m_currentTransition = getTransitionFromTabWidget(ui->timelineTab);
+ });
+}
+
+void TransitionEditorSettingsDialog::setCurrentTransition(const ModelNode &timeline)
+{
+ m_currentTransition = timeline;
+ setTabForTransition(ui->timelineTab, m_currentTransition);
+}
+
+TransitionEditorSettingsDialog::~TransitionEditorSettingsDialog()
+{
+ delete ui;
+}
+
+void TransitionEditorSettingsDialog::keyPressEvent(QKeyEvent *event)
+{
+ switch (event->key()) {
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ /* ignore */
+ break;
+
+ default:
+ QDialog::keyPressEvent(event);
+ }
+}
+
+void TransitionEditorSettingsDialog::setupTransitions(const ModelNode &newTransition)
+{
+ deleteAllTabs(ui->timelineTab);
+
+ const QList<ModelNode> &transitions = m_transitionEditorView->allTransitions();
+
+ if (transitions.isEmpty()) {
+ m_currentTransition = {};
+ auto transitionForm = new TransitionForm(this);
+ transitionForm->setDisabled(true);
+ ui->timelineTab->addTab(transitionForm, tr("No Transition"));
+ return;
+ }
+
+ for (const auto &transition : transitions)
+ addTransitionTab(transition);
+
+ if (newTransition.isValid()) {
+ m_currentTransition = newTransition;
+ } else {
+ m_currentTransition = transitions.constFirst();
+ }
+
+ setTabForTransition(ui->timelineTab, m_currentTransition);
+}
+
+void TransitionEditorSettingsDialog::addTransitionTab(const QmlTimeline &node)
+{
+ auto transitionForm = new TransitionForm(this);
+ ui->timelineTab->addTab(transitionForm, node.modelNode().displayName());
+ transitionForm->setTransition(node);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h
new file mode 100644
index 0000000000..ef9c6ad0c4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.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 <qmltimeline.h>
+
+#include <QDialog>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+class TransitionForm;
+class TransitionEditorView;
+
+namespace Ui {
+class TransitionEditorSettingsDialog;
+}
+
+class TransitionEditorSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorSettingsDialog(QWidget *parent, class TransitionEditorView *view);
+ void setCurrentTransition(const ModelNode &timeline);
+ ~TransitionEditorSettingsDialog() override;
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void setupTransitions(const ModelNode &node);
+
+ void addTransitionTab(const QmlTimeline &node);
+
+ Ui::TransitionEditorSettingsDialog *ui;
+
+ TransitionEditorView *m_transitionEditorView;
+ ModelNode m_currentTransition;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui
new file mode 100644
index 0000000000..282c2c475e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TransitionEditorSettingsDialog</class>
+ <widget class="QDialog" name="QmlDesigner::TransitionEditorSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>519</width>
+ <height>582</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Transition Settings</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="timelineTab">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp
new file mode 100644
index 0000000000..af19629234
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp
@@ -0,0 +1,342 @@
+/****************************************************************************
+**
+** 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 "transitioneditortoolbar.h"
+#include "transitioneditorgraphicsscene.h"
+
+#include "timelineconstants.h"
+
+#include "timelineicons.h"
+
+#include "timelineview.h"
+#include "timelinewidget.h"
+
+#include <designeractionmanager.h>
+#include <nodelistproperty.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
+#include <coreplugin/icore.h>
+
+#include <utils/algorithm.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QIntValidator>
+#include <QLineEdit>
+#include <QResizeEvent>
+#include <QSlider>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+static bool isSpacer(QObject *object)
+{
+ return object->property("spacer_widget").toBool();
+}
+
+static QWidget *createSpacer()
+{
+ QWidget *spacer = new QWidget();
+ spacer->setProperty("spacer_widget", true);
+ return spacer;
+}
+
+static int controlWidth(QToolBar *bar, QObject *control)
+{
+ QWidget *widget = nullptr;
+
+ if (auto *action = qobject_cast<QAction *>(control))
+ widget = bar->widgetForAction(action);
+
+ if (widget == nullptr)
+ widget = qobject_cast<QWidget *>(control);
+
+ if (widget)
+ return widget->width();
+
+ return 0;
+}
+
+static QAction *createAction(const Core::Id &id,
+ const QIcon &icon,
+ const QString &name,
+ const QKeySequence &shortcut)
+{
+ QString text = QString("%1 (%2)").arg(name).arg(shortcut.toString());
+
+ Core::Context context(TimelineConstants::C_QMLTIMELINE);
+
+ auto *action = new QAction(icon, text);
+ auto *command = Core::ActionManager::registerAction(action, id, context);
+ command->setDefaultKeySequence(shortcut);
+
+ return action;
+}
+
+TransitionEditorToolBar::TransitionEditorToolBar(QWidget *parent)
+ : QToolBar(parent)
+ , m_grp()
+{
+ setContentsMargins(0, 0, 0, 0);
+ createLeftControls();
+ createCenterControls();
+ createRightControls();
+}
+
+void TransitionEditorToolBar::reset() {}
+
+int TransitionEditorToolBar::scaleFactor() const
+{
+ if (m_scale)
+ return m_scale->value();
+ return 0;
+}
+
+QString TransitionEditorToolBar::currentTransitionId() const
+{
+ return m_transitionComboBox->currentText();
+}
+
+void TransitionEditorToolBar::setBlockReflection(bool block)
+{
+ m_blockReflection = block;
+}
+
+void TransitionEditorToolBar::updateComboBox(const ModelNode &root)
+{
+ if (root.isValid() && root.hasProperty("transitions")) {
+ NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ for (const ModelNode &transition : transitions.directSubNodes())
+ m_transitionComboBox->addItem(transition.id());
+ }
+}
+
+void TransitionEditorToolBar::setCurrentTransition(const ModelNode &transition)
+{
+ if (m_blockReflection)
+ return;
+
+ if (transition.isValid()) {
+ m_transitionComboBox->clear();
+ const ModelNode root = transition.view()->rootModelNode();
+ updateComboBox(root);
+ m_transitionComboBox->setCurrentText(transition.id());
+ } else {
+ m_transitionComboBox->clear();
+ m_transitionComboBox->setCurrentText("");
+ }
+}
+
+void TransitionEditorToolBar::setDuration(qreal frame)
+{
+ auto text = QString::number(frame, 'f', 0);
+ m_duration->setText(text);
+}
+
+void TransitionEditorToolBar::setScaleFactor(int factor)
+{
+ const QSignalBlocker blocker(m_scale);
+ m_scale->setValue(factor);
+}
+
+void TransitionEditorToolBar::setActionEnabled(const QString &name, bool enabled)
+{
+ for (auto *action : actions())
+ if (action->objectName() == name)
+ action->setEnabled(enabled);
+}
+
+void TransitionEditorToolBar::createLeftControls()
+{
+ auto addActionToGroup = [&](QAction *action) {
+ addAction(action);
+ m_grp << action;
+ };
+
+ auto addWidgetToGroup = [&](QWidget *widget) {
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ auto addSpacingToGroup = [&](int width) {
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ addSpacingToGroup(5);
+
+ auto *settingsAction = createAction(TimelineConstants::C_SETTINGS,
+ TimelineIcons::ANIMATION.icon(),
+ tr("Transition Settings"),
+ QKeySequence(Qt::Key_S));
+ connect(settingsAction,
+ &QAction::triggered,
+ this,
+ &TransitionEditorToolBar::settingDialogClicked);
+
+ addActionToGroup(settingsAction);
+
+ addWidgetToGroup(createSpacer());
+
+ m_transitionComboBox = new QComboBox(this);
+ addWidgetToGroup(m_transitionComboBox);
+
+ connect(m_transitionComboBox, &QComboBox::currentTextChanged, this, [this]() {
+ emit currentTransitionChanged(m_transitionComboBox->currentText());
+ });
+}
+
+static QLineEdit *createToolBarLineEdit(QWidget *parent)
+{
+ auto lineEdit = new QLineEdit(parent);
+ lineEdit->setStyleSheet("* { background-color: rgba(0, 0, 0, 0); }");
+ lineEdit->setFixedWidth(48);
+ lineEdit->setAlignment(Qt::AlignCenter);
+
+ QPalette pal = parent->palette();
+ pal.setColor(QPalette::Text, Theme::instance()->color(Utils::Theme::PanelTextColorLight));
+ lineEdit->setPalette(pal);
+ QValidator *validator = new QIntValidator(-100000, 100000, lineEdit);
+ lineEdit->setValidator(validator);
+
+ return lineEdit;
+}
+
+void TransitionEditorToolBar::createCenterControls()
+{
+ addSpacing(10);
+
+ auto *curvePicker = createAction(TimelineConstants::C_CURVE_PICKER,
+ TimelineIcons::CURVE_EDITOR.icon(),
+ tr("Easing Curve Editor"),
+ QKeySequence(Qt::Key_C));
+
+ curvePicker->setObjectName("Easing Curve Editor");
+ connect(curvePicker, &QAction::triggered, this, &TransitionEditorToolBar::openEasingCurveEditor);
+ addAction(curvePicker);
+
+ addSpacing(10);
+
+#if 0
+ addSeparator();
+
+ addSpacing(10);
+
+ auto *curveEditor = new QAction(TimelineIcons::CURVE_PICKER.icon(), tr("Curve Editor"));
+ addAction(curveEditor);
+#endif
+}
+
+void TransitionEditorToolBar::createRightControls()
+{
+ auto *spacer = createSpacer();
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ addWidget(spacer);
+
+ addSeparator();
+ addSpacing(10);
+
+ auto *zoomOut = createAction(TimelineConstants::C_ZOOM_OUT,
+ TimelineIcons::ZOOM_SMALL.icon(),
+ tr("Zoom Out"),
+ QKeySequence(QKeySequence::ZoomOut));
+
+ connect(zoomOut, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() - m_scale->pageStep());
+ });
+ addAction(zoomOut);
+
+ addSpacing(10);
+
+ m_scale = new QSlider(this);
+ m_scale->setOrientation(Qt::Horizontal);
+ m_scale->setMaximumWidth(200);
+ m_scale->setMinimumWidth(100);
+ m_scale->setMinimum(0);
+ m_scale->setMaximum(100);
+ m_scale->setValue(0);
+
+ connect(m_scale, &QSlider::valueChanged, this, &TransitionEditorToolBar::scaleFactorChanged);
+ addWidget(m_scale);
+
+ addSpacing(10);
+
+ auto *zoomIn = createAction(TimelineConstants::C_ZOOM_IN,
+ TimelineIcons::ZOOM_BIG.icon(),
+ tr("Zoom In"),
+ QKeySequence(QKeySequence::ZoomIn));
+
+ connect(zoomIn, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() + m_scale->pageStep());
+ });
+ addAction(zoomIn);
+
+ addSpacing(10);
+
+ addSeparator();
+
+ m_duration = createToolBarLineEdit(this);
+ addWidget(m_duration);
+
+ auto emitEndChanged = [this]() { emit durationChanged(m_duration->text().toInt()); };
+ connect(m_duration, &QLineEdit::editingFinished, emitEndChanged);
+}
+
+void TransitionEditorToolBar::addSpacing(int width)
+{
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+}
+
+void TransitionEditorToolBar::resizeEvent(QResizeEvent *event)
+{
+ Q_UNUSED(event)
+
+ int width = 0;
+ QWidget *spacer = nullptr;
+ for (auto *object : qAsConst(m_grp)) {
+ if (isSpacer(object))
+ spacer = qobject_cast<QWidget *>(object);
+ else
+ width += controlWidth(this, object);
+ }
+
+ if (spacer) {
+ int spacerWidth = TimelineConstants::sectionWidth - width - 12;
+ spacer->setFixedWidth(spacerWidth > 0 ? spacerWidth : 0);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h
new file mode 100644
index 0000000000..eba74021ca
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** 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 "animationcurvedialog.h"
+#include "animationcurveeditormodel.h"
+
+#include <QToolBar>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+QT_FORWARD_DECLARE_CLASS(QObject)
+QT_FORWARD_DECLARE_CLASS(QResizeEvent)
+QT_FORWARD_DECLARE_CLASS(QSlider)
+QT_FORWARD_DECLARE_CLASS(QWidget)
+
+namespace QmlDesigner {
+
+class TimelineWidget;
+
+class QmlTimeline;
+
+class TransitionEditorToolBar : public QToolBar
+{
+ Q_OBJECT
+
+signals:
+ void settingDialogClicked();
+
+ void scaleFactorChanged(int value);
+ void durationChanged(int value);
+ void currentTransitionChanged(const QString &name);
+ void openEasingCurveEditor();
+
+public:
+ explicit TransitionEditorToolBar(QWidget *parent = nullptr);
+
+ void reset();
+
+ int scaleFactor() const;
+ QString currentTransitionId() const;
+
+ void setBlockReflection(bool block);
+ void setCurrentTransition(const ModelNode &transition);
+ void setDuration(qreal frame);
+ void setScaleFactor(int factor);
+
+ void setActionEnabled(const QString &name, bool enabled);
+
+ void updateComboBox(const ModelNode &root);
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void createLeftControls();
+ void createCenterControls();
+ void createRightControls();
+ void addSpacing(int width);
+
+ QList<QObject *> m_grp;
+
+ QComboBox *m_transitionComboBox = nullptr;
+ QSlider *m_scale = nullptr;
+ QLineEdit *m_duration = nullptr;
+
+ bool m_blockReflection = false;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp
new file mode 100644
index 0000000000..a7ead0afd2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp
@@ -0,0 +1,348 @@
+/****************************************************************************
+**
+** 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 "transitioneditorview.h"
+
+#include "transitioneditortoolbar.h"
+#include "transitioneditorwidget.h"
+
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorsettingsdialog.h"
+
+#include <bindingproperty.h>
+#include <exception.h>
+#include <modelnodecontextmenu_helper.h>
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+#include <viewmanager.h>
+#include <qmldesignericons.h>
+#include <qmldesignerplugin.h>
+#include <qmlitemnode.h>
+#include <qmlobjectnode.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <designmodecontext.h>
+
+#include <coreplugin/icore.h>
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QTimer>
+
+namespace QmlDesigner {
+
+TransitionEditorView::TransitionEditorView(QObject *parent)
+ : AbstractView(parent)
+ , m_transitionEditorWidget(nullptr)
+{
+
+}
+
+TransitionEditorView::~TransitionEditorView() = default;
+
+void TransitionEditorView::modelAttached(Model *model)
+{
+ AbstractView::modelAttached(model);
+ if (m_transitionEditorWidget)
+ m_transitionEditorWidget->init();
+}
+
+void TransitionEditorView::modelAboutToBeDetached(Model *model)
+{
+ m_transitionEditorWidget->reset();
+
+ AbstractView::modelAboutToBeDetached(model);
+}
+
+void TransitionEditorView::nodeCreated(const ModelNode & /*createdNode*/) {}
+
+void TransitionEditorView::nodeAboutToBeRemoved(const ModelNode & /*removedNode*/) {}
+
+void TransitionEditorView::nodeRemoved(const ModelNode & removedNode,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags /*propertyChange*/)
+{
+ if (parentProperty.name() == "transitions")
+ widget()->updateData(removedNode);
+}
+
+void TransitionEditorView::nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty & /*oldPropertyParent*/,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ if (newPropertyParent.name() == "transitions")
+ asyncUpdate(node);
+
+ const ModelNode parent = newPropertyParent.parentModelNode();
+
+ qDebug() << Q_FUNC_INFO << parent;
+ if (parent.isValid() && parent.metaInfo().isValid()
+ && parent.metaInfo().isSubclassOf("QtQuick.Transition")) {
+ asyncUpdate(parent);
+ }
+}
+
+void TransitionEditorView::instancePropertyChanged(
+ const QList<QPair<ModelNode, PropertyName>> & /*propertyList*/)
+{
+
+}
+
+void TransitionEditorView::variantPropertiesChanged(
+ const QList<VariantProperty> & /* propertyList */,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+
+}
+
+void TransitionEditorView::bindingPropertiesChanged(
+ const QList<BindingProperty> & /*propertyList */,
+ AbstractView::PropertyChangeFlags /* propertyChange */)
+{
+
+}
+
+void TransitionEditorView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeList*/,
+ const QList<ModelNode> & /*lastSelectedNodeList*/)
+{
+
+}
+
+void TransitionEditorView::propertiesAboutToBeRemoved(
+ const QList<AbstractProperty> & /*propertyList */)
+{
+
+}
+
+void TransitionEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
+{
+ for (const AbstractProperty &property : propertyList) {
+ if (property.name() == "transitions")
+ widget()->init();
+ }
+}
+
+bool TransitionEditorView::hasWidget() const
+{
+ return true;
+}
+
+void TransitionEditorView::nodeIdChanged(const ModelNode &node, const QString &, const QString &)
+{
+ if (node.metaInfo().isValid() && node.metaInfo().isSubclassOf("QtQuick.Transition"))
+ widget()->init();
+}
+
+void TransitionEditorView::currentStateChanged(const ModelNode &)
+{
+
+}
+
+TransitionEditorWidget *TransitionEditorView::widget() const
+{
+ return m_transitionEditorWidget;
+}
+
+void TransitionEditorView::registerActions()
+{
+
+}
+
+ModelNode TransitionEditorView::addNewTransition()
+{
+ QList<QmlModelState> states;
+ const ModelNode root = rootModelNode();
+
+ if (QmlVisualNode::isValidQmlVisualNode(root)) {
+ states = QmlVisualNode(root).states().allStates();
+ }
+
+ if (states.isEmpty()) {
+ Core::AsynchronousMessageBox::warning(tr("No States Defined"),
+ tr("There are no states defined in this component."));
+ return {};
+ }
+
+ QHash<QString, QStringList> idPropertyList;
+
+ const QVector<TypeName> validProperties = {"int", "real", "double", "qreal", "color", "QColor"};
+
+ for (const QmlModelState &state : qAsConst(states)) {
+ for (const QmlPropertyChanges & change : state.propertyChanges()) {
+ QStringList locList;
+ const ModelNode target = change.target();
+ const QString targetId = target.id();
+ if (target.isValid() && target.hasMetaInfo()) {
+ for (const VariantProperty &property : change.modelNode().variantProperties()) {
+ TypeName typeName = target.metaInfo().propertyTypeName(property.name());
+
+ if (validProperties.contains(typeName))
+ locList.append(QString::fromUtf8(property.name()));
+ }
+ if (idPropertyList.contains(targetId)) {
+ QStringList newlist = idPropertyList.value(targetId);
+ for (const QString &str :locList)
+ if (!newlist.contains(str))
+ newlist.append(str);
+ idPropertyList.insert(targetId, newlist);
+ } else {
+ if (!locList.isEmpty())
+ idPropertyList.insert(targetId, locList);
+ }
+ }
+ }
+ }
+
+ ModelNode transition;
+
+ if (!idPropertyList.isEmpty()) {
+ executeInTransaction(
+ " TransitionEditorView::addNewTransition", [&transition, idPropertyList, root, this]() {
+ transition = createModelNode("QtQuick.Transition",
+ 2,
+ 0,
+ {{
+ "from",
+ "*",
+ },
+ {
+ "to",
+ "*",
+ }});
+ transition.setAuxiliaryData("transitionDuration", 2000);
+ transition.validId();
+ root.nodeListProperty("transitions").reparentHere(transition);
+
+ for (const QString &id : idPropertyList.keys()) {
+ ModelNode parallelAnimation = createModelNode("QtQuick.ParallelAnimation",
+ 2,
+ 12);
+ transition.defaultNodeAbstractProperty().reparentHere(parallelAnimation);
+ for (const QString &property : idPropertyList.value(id)) {
+ ModelNode sequentialAnimation
+ = createModelNode("QtQuick.SequentialAnimation", 2, 12);
+ parallelAnimation.defaultNodeAbstractProperty().reparentHere(
+ sequentialAnimation);
+
+ ModelNode pauseAnimation = createModelNode("QtQuick.PauseAnimation",
+ 2,
+ 12,
+ {{"duration", 50}});
+ sequentialAnimation.defaultNodeAbstractProperty().reparentHere(
+ pauseAnimation);
+
+ ModelNode propertyAnimation = createModelNode("QtQuick.PropertyAnimation",
+ 2,
+ 12,
+ {{"property", property},
+ {"duration", 150}});
+ propertyAnimation.bindingProperty("target").setExpression(id);
+ sequentialAnimation.defaultNodeAbstractProperty().reparentHere(
+ propertyAnimation);
+ }
+ }
+ });
+ }
+
+ if (m_transitionEditorWidget)
+ m_transitionEditorWidget->init();
+
+ return transition;
+}
+
+TransitionEditorWidget *TransitionEditorView::createWidget()
+{
+ if (!m_transitionEditorWidget)
+ m_transitionEditorWidget = new TransitionEditorWidget(this);
+
+ //auto *timelineContext = new TimelineContext(m_timelineWidget);
+ //Core::ICore::addContextObject(timelineContext);
+
+ return m_transitionEditorWidget;
+}
+
+WidgetInfo TransitionEditorView::widgetInfo()
+{
+ return createWidgetInfo(createWidget(),
+ nullptr,
+ "TransitionEditor",
+ WidgetInfo::BottomPane,
+ 0,
+ tr("Transition Editor"));
+}
+
+void TransitionEditorView::openSettingsDialog()
+{
+ auto dialog = new TransitionEditorSettingsDialog(Core::ICore::dialogParent(), this);
+
+ auto transition = widget()->graphicsScene()->transitionModelNode();
+ if (transition.isValid())
+ dialog->setCurrentTransition(transition);
+
+ QObject::connect(dialog, &TransitionEditorSettingsDialog::rejected, [this, dialog]() {
+ widget()->init();
+ dialog->deleteLater();
+ });
+
+ QObject::connect(dialog, &TransitionEditorSettingsDialog::accepted, [this, dialog]() {
+ widget()->init();
+ dialog->deleteLater();
+ });
+
+ dialog->show();
+}
+
+const QList<ModelNode> TransitionEditorView::allTransitions() const
+{
+ if (rootModelNode().isValid() && rootModelNode().hasProperty("transitions")) {
+ NodeAbstractProperty transitions = rootModelNode().nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ return transitions.directSubNodes();
+ }
+ return {};
+}
+
+void TransitionEditorView::asyncUpdate(const ModelNode &transition)
+{
+ static bool updateTriggered = false;
+
+ if (!updateTriggered && (transition.id() == widget()->toolBar()->currentTransitionId())) {
+ updateTriggered = true;
+ QTimer::singleShot(0, [this, transition]() {
+ widget()->updateData(transition);
+ updateTriggered = false;
+ });
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h
new file mode 100644
index 0000000000..faa383c621
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** 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 "animationcurvedialog.h"
+#include "animationcurveeditormodel.h"
+#include "treeitem.h"
+
+#include <abstractview.h>
+
+#include <QPointer>
+
+namespace QmlDesigner {
+
+class TransitionEditorWidget;
+
+class TransitionEditorView : public AbstractView
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorView(QObject *parent = nullptr);
+ ~TransitionEditorView() override;
+ //Abstract View
+ WidgetInfo widgetInfo() override;
+ void modelAttached(Model *model) override;
+ void modelAboutToBeDetached(Model *model) override;
+ void nodeCreated(const ModelNode &createdNode) override;
+ void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
+ void nodeRemoved(const ModelNode &removedNode,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags propertyChange) override;
+ void nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent,
+ PropertyChangeFlags propertyChange) override;
+ void instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList) override;
+ void variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void bindingPropertiesChanged(const QList<BindingProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
+ const QList<ModelNode> &lastSelectedNodeList) override;
+
+ void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override;
+ void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
+
+ bool hasWidget() const override;
+
+ void nodeIdChanged(const ModelNode &node, const QString &, const QString &) override;
+
+ void currentStateChanged(const ModelNode &node) override;
+
+ TransitionEditorWidget *widget() const;
+
+ void insertKeyframe(const ModelNode &target, const PropertyName &propertyName);
+
+ void registerActions();
+
+ ModelNode addNewTransition();
+
+ void openSettingsDialog();
+
+ const QList<ModelNode> allTransitions() const;
+
+ void asyncUpdate(const ModelNode &transition);
+
+private:
+ TransitionEditorWidget *createWidget();
+
+ TransitionEditorWidget *m_transitionEditorWidget = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp
new file mode 100644
index 0000000000..5d27b4c969
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** 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 "transitioneditorwidget.h"
+
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorpropertyitem.h"
+#include "transitioneditortoolbar.h"
+#include "transitioneditorview.h"
+
+#include <timelineeditor/easingcurvedialog.h>
+#include <timelineeditor/timelineconstants.h>
+#include <timelineeditor/timelineicons.h>
+
+#include <bindingproperty.h>
+#include <nodeabstractproperty.h>
+#include <nodemetainfo.h>
+
+#include <qmldesignerplugin.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+
+#include <coreplugin/icore.h>
+
+#include <theme.h>
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMargins>
+#include <QPushButton>
+#include <QResizeEvent>
+#include <QScrollBar>
+#include <QShowEvent>
+#include <QSlider>
+#include <QSpacerItem>
+#include <QVBoxLayout>
+#include <QtGlobal>
+
+namespace QmlDesigner {
+
+class Eventfilter : public QObject
+{
+public:
+ Eventfilter(QObject *parent)
+ : QObject(parent)
+ {}
+
+ bool eventFilter(QObject *, QEvent *event) override
+ {
+ if (event->type() == QEvent::Wheel) {
+ event->accept();
+ return true;
+ }
+ return false;
+ }
+};
+
+TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view)
+ : QWidget()
+ , m_toolbar(new TransitionEditorToolBar(this))
+ , m_rulerView(new QGraphicsView(this))
+ , m_graphicsView(new QGraphicsView(this))
+ , m_scrollbar(new QScrollBar(this))
+ , m_statusBar(new QLabel(this))
+ , m_transitionEditorView(view)
+ , m_graphicsScene(new TransitionEditorGraphicsScene(this))
+ , m_addButton(new QPushButton(this))
+ , m_onboardingContainer(new QWidget(this))
+{
+ setWindowTitle(tr("Transition", "Title of transition view"));
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ const QString css = Theme::replaceCssColors(QString::fromUtf8(
+ Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css"))));
+
+ m_scrollbar->setStyleSheet(css);
+ m_scrollbar->setOrientation(Qt::Horizontal);
+
+ QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ sizePolicy1.setHorizontalStretch(0);
+ sizePolicy1.setVerticalStretch(0);
+ sizePolicy1.setHeightForWidth(m_graphicsView->sizePolicy().hasHeightForWidth());
+
+ m_rulerView->setObjectName("RulerView");
+ m_rulerView->setFixedHeight(TimelineConstants::rulerHeight);
+ m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_rulerView->viewport()->installEventFilter(new Eventfilter(this));
+ m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus);
+ m_rulerView->setStyleSheet(css);
+ m_rulerView->setFrameShape(QFrame::NoFrame);
+ m_rulerView->setFrameShadow(QFrame::Plain);
+ m_rulerView->setLineWidth(0);
+ m_rulerView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->setScene(graphicsScene());
+
+ m_graphicsView->setStyleSheet(css);
+ m_graphicsView->setObjectName("SceneView");
+ m_graphicsView->setFrameShape(QFrame::NoFrame);
+ m_graphicsView->setFrameShadow(QFrame::Plain);
+ m_graphicsView->setLineWidth(0);
+ m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ m_graphicsView->setSizePolicy(sizePolicy1);
+ m_graphicsView->setScene(graphicsScene());
+ m_graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ auto *scrollBarLayout = new QHBoxLayout;
+ scrollBarLayout->addSpacing(TimelineConstants::sectionWidth);
+ scrollBarLayout->addWidget(m_scrollbar);
+
+ QMargins margins(0, 0, 0, QApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
+
+ auto *contentLayout = new QVBoxLayout;
+ contentLayout->setContentsMargins(margins);
+ contentLayout->addWidget(m_rulerView);
+ contentLayout->addWidget(m_graphicsView);
+ contentLayout->addLayout(scrollBarLayout);
+ contentLayout->addWidget(m_statusBar);
+ m_statusBar->setIndent(2);
+ m_statusBar->setFixedHeight(TimelineConstants::rulerHeight);
+
+ auto *widgetLayout = new QVBoxLayout;
+ widgetLayout->setContentsMargins(0, 0, 0, 0);
+ widgetLayout->setSpacing(0);
+ widgetLayout->addWidget(m_toolbar);
+ widgetLayout->addWidget(m_addButton);
+
+ m_addButton->setIcon(TimelineIcons::ADD_TIMELINE_TOOLBAR.icon());
+ m_addButton->setToolTip(tr("Add Transition"));
+ m_addButton->setFlat(true);
+ m_addButton->setFixedSize(32, 32);
+
+ widgetLayout->addWidget(m_onboardingContainer);
+
+ auto *onboardingTopLabel = new QLabel(m_onboardingContainer);
+ auto *onboardingBottomLabel = new QLabel(m_onboardingContainer);
+ auto *onboardingBottomIcon = new QLabel(m_onboardingContainer);
+
+ auto *onboardingLayout = new QVBoxLayout;
+ auto *onboardingSublayout = new QHBoxLayout;
+ auto *leftSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ auto *rightSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ auto *topSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
+ auto *bottomSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
+
+ QString labelText = tr("This file does not contain transitions. <br><br> \
+ To create an animation, add a transition by clicking the + button.");
+ onboardingTopLabel->setText(labelText);
+ onboardingTopLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+
+ m_onboardingContainer->setLayout(onboardingLayout);
+ onboardingLayout->setContentsMargins(0, 0, 0, 0);
+ onboardingLayout->setSpacing(0);
+ onboardingLayout->addSpacerItem(topSpacer);
+ onboardingLayout->addWidget(onboardingTopLabel);
+ onboardingLayout->addLayout(onboardingSublayout);
+
+ onboardingSublayout->setContentsMargins(0, 0, 0, 0);
+ onboardingSublayout->setSpacing(0);
+ onboardingSublayout->addSpacerItem(leftSpacer);
+
+ onboardingBottomLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
+ onboardingBottomLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ onboardingSublayout->addWidget(onboardingBottomLabel);
+ onboardingBottomLabel->setText(tr("To edit the transition settings, click "));
+
+ onboardingBottomIcon->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ onboardingBottomIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ onboardingSublayout->addWidget(onboardingBottomIcon);
+ onboardingBottomIcon->setPixmap(TimelineIcons::ANIMATION.pixmap());
+
+ onboardingSublayout->addSpacerItem(rightSpacer);
+ onboardingLayout->addSpacerItem(bottomSpacer);
+
+ widgetLayout->addLayout(contentLayout);
+ this->setLayout(widgetLayout);
+
+ connectToolbar();
+
+ auto setScrollOffset = [this]() { graphicsScene()->setScrollOffset(m_scrollbar->value()); };
+ connect(m_scrollbar, &QSlider::valueChanged, this, setScrollOffset);
+
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::statusBarMessageChanged,
+ this,
+ [this](const QString &message) { m_statusBar->setText(message); });
+
+ connect(m_addButton, &QPushButton::clicked, this, [this]() {
+ m_transitionEditorView->addNewTransition();
+ });
+}
+
+void TransitionEditorWidget::setTransitionActive(bool b)
+{
+ if (b) {
+ m_toolbar->setVisible(true);
+ m_graphicsView->setVisible(true);
+ m_rulerView->setVisible(true);
+ m_scrollbar->setVisible(true);
+ m_addButton->setVisible(false);
+ m_onboardingContainer->setVisible(false);
+ m_graphicsView->update();
+ m_rulerView->update();
+ } else {
+ m_toolbar->setVisible(false);
+ m_graphicsView->setVisible(false);
+ m_rulerView->setVisible(false);
+ m_scrollbar->setVisible(false);
+ m_addButton->setVisible(true);
+ m_onboardingContainer->setVisible(true);
+ }
+}
+
+void TransitionEditorWidget::connectToolbar()
+{
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::selectionChanged,
+ this,
+ &TransitionEditorWidget::selectionChanged);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::openEasingCurveEditor,
+ this,
+ &TransitionEditorWidget::openEasingCurveEditor);
+
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::scroll,
+ this,
+ &TransitionEditorWidget::scroll);
+
+ auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); };
+ connect(m_toolbar, &TransitionEditorToolBar::scaleFactorChanged, setRulerScaling);
+
+ auto setDuration = [this](int end) { graphicsScene()->setDuration(end); };
+ connect(m_toolbar, &TransitionEditorToolBar::durationChanged, setDuration);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::settingDialogClicked,
+ transitionEditorView(),
+ &TransitionEditorView::openSettingsDialog);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::currentTransitionChanged,
+ this,
+ [this](const QString &transitionName) {
+ const ModelNode transition = transitionEditorView()->modelNodeForId(transitionName);
+ if (transition.isValid()) {
+ m_graphicsScene->setTransition(transition);
+ }
+ });
+}
+
+void TransitionEditorWidget::changeScaleFactor(int factor)
+{
+ m_toolbar->setScaleFactor(factor);
+}
+
+void TransitionEditorWidget::scroll(const TimelineUtils::Side &side)
+{
+ if (side == TimelineUtils::Side::Left)
+ m_scrollbar->setValue(m_scrollbar->value() - m_scrollbar->singleStep());
+ else if (side == TimelineUtils::Side::Right)
+ m_scrollbar->setValue(m_scrollbar->value() + m_scrollbar->singleStep());
+}
+
+void TransitionEditorWidget::selectionChanged()
+{
+ if (graphicsScene()->selectedPropertyItem() != nullptr)
+ m_toolbar->setActionEnabled("Curve Picker", true);
+ else
+ m_toolbar->setActionEnabled("Curve Picker", false);
+}
+
+void TransitionEditorWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
+{
+ if (transitionEditorView())
+ transitionEditorView()->contextHelp(callback);
+ else
+ callback({});
+}
+
+void TransitionEditorWidget::init()
+{
+ ModelNode root = transitionEditorView()->rootModelNode();
+ ModelNode transition;
+
+ if (root.isValid() && root.hasProperty("transitions")) {
+ NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ transition = transitions.directSubNodes().first();
+ }
+
+ m_graphicsScene->setTransition(transition);
+ setTransitionActive(transition.isValid());
+
+ m_graphicsScene->setWidth(m_graphicsView->viewport()->width());
+
+ m_toolbar->setScaleFactor(0);
+
+ m_toolbar->setCurrentTransition(transition);
+
+ qreal duration = 2000;
+ if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration"))
+ duration = transition.auxiliaryData("transitionDuration").toDouble();
+
+ m_toolbar->setDuration(duration);
+
+ m_graphicsScene->setRulerScaling(0);
+}
+
+void TransitionEditorWidget::updateData(const ModelNode &transition)
+{
+ if (!transition.isValid()) {
+ init();
+ return;
+ }
+
+ if (transition.metaInfo().isValid()
+ && transition.metaInfo().isSubclassOf("QtQuick.Transition")) {
+ if (transition.id() == m_toolbar->currentTransitionId()) {
+ m_graphicsScene->setTransition(transition);
+ } else {
+ m_toolbar->updateComboBox(transition.view()->rootModelNode());
+ }
+ }
+}
+
+void TransitionEditorWidget::reset()
+{
+ graphicsScene()->clearTransition();
+ m_toolbar->reset();
+ m_statusBar->clear();
+}
+
+TransitionEditorGraphicsScene *TransitionEditorWidget::graphicsScene() const
+{
+ return m_graphicsScene;
+}
+
+TransitionEditorToolBar *TransitionEditorWidget::toolBar() const
+{
+ return m_toolbar;
+}
+
+void TransitionEditorWidget::setupScrollbar(int min, int max, int current)
+{
+ bool b = m_scrollbar->blockSignals(true);
+ m_scrollbar->setMinimum(min);
+ m_scrollbar->setMaximum(max);
+ m_scrollbar->setValue(current);
+ m_scrollbar->setSingleStep((max - min) / 10);
+ m_scrollbar->blockSignals(b);
+}
+
+void TransitionEditorWidget::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event)
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+ graphicsScene()->invalidateLayout();
+ graphicsScene()->invalidate();
+ graphicsScene()->onShow();
+}
+
+void TransitionEditorWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+}
+
+TransitionEditorView *TransitionEditorWidget::transitionEditorView() const
+{
+ return m_transitionEditorView;
+}
+
+void TransitionEditorWidget::openEasingCurveEditor()
+{
+ if (TransitionEditorPropertyItem *item = graphicsScene()->selectedPropertyItem()) {
+ QList<ModelNode> animations;
+ animations.append(item->propertyAnimation());
+ EasingCurveDialog::runDialog(animations, Core::ICore::dialogParent());
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h
new file mode 100644
index 0000000000..97f57f1c2f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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 "timelineeditor/timelineutils.h"
+
+#include <coreplugin/icontext.h>
+
+#include <QWidget>
+
+#include <functional>
+
+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)
+
+namespace QmlDesigner {
+
+class TransitionEditorView;
+class TransitionEditorToolBar;
+class TransitionEditorGraphicsScene;
+class ModelNode;
+
+class TransitionEditorWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorWidget(TransitionEditorView *view);
+ void contextHelp(const Core::IContext::HelpCallback &callback) const;
+
+ TransitionEditorGraphicsScene *graphicsScene() const;
+ TransitionEditorView *transitionEditorView() const;
+ TransitionEditorToolBar *toolBar() const;
+
+ void init();
+ void reset();
+
+ void setupScrollbar(int min, int max, int current);
+ void changeScaleFactor(int factor);
+
+ void updateData(const ModelNode &transition);
+public slots:
+ void selectionChanged();
+ void scroll(const TimelineUtils::Side &side);
+
+protected:
+ void showEvent(QShowEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void connectToolbar();
+ void setTransitionActive(bool b);
+ void openEasingCurveEditor();
+
+ TransitionEditorToolBar *m_toolbar = nullptr;
+
+ QGraphicsView *m_rulerView = nullptr;
+
+ QGraphicsView *m_graphicsView = nullptr;
+
+ QScrollBar *m_scrollbar = nullptr;
+
+ QLabel *m_statusBar = nullptr;
+
+ TransitionEditorView *m_transitionEditorView = nullptr;
+
+ TransitionEditorGraphicsScene *m_graphicsScene;
+
+ QPushButton *m_addButton = nullptr;
+
+ QWidget *m_onboardingContainer = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp
new file mode 100644
index 0000000000..239817197b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** 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 "transitionform.h"
+#include "timelineform.h"
+#include "ui_transitionform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+#include <qmlitemnode.h>
+
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+TransitionForm::TransitionForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::TransitionForm)
+{
+ ui->setupUi(this);
+
+ connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+
+ static QString lastString;
+
+ const QString newId = ui->idLineEdit->text();
+
+ if (newId == lastString)
+ return;
+
+ lastString = newId;
+
+ if (newId == m_transition.id())
+ return;
+
+ bool error = false;
+
+ if (!ModelNode::isValidId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 is an invalid id.").arg(newId));
+ error = true;
+ } else if (m_transition.view()->hasId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 already exists.").arg(newId));
+ error = true;
+ } else {
+ m_transition.setIdWithRefactoring(newId);
+ }
+
+ if (error) {
+ lastString.clear();
+ ui->idLineEdit->setText(m_transition.id());
+ }
+ });
+
+ connect(ui->listWidgetTo, &QListWidget::itemChanged, this, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ QTC_ASSERT(root.isValid(), return );
+ const int stateCount = root.states().names().count();
+
+ QStringList stateNames;
+
+ for (const QListWidgetItem *item : ui->listWidgetTo->findItems("*", Qt::MatchWildcard)) {
+ if (item->checkState() == Qt::Checked)
+ stateNames.append(item->text());
+ }
+
+ QString toValue;
+ if (stateCount == stateNames.count())
+ toValue = "*";
+ else
+ toValue = stateNames.join(",");
+
+ m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, toValue]() {
+ m_transition.variantProperty("to").setValue(toValue);
+ });
+ });
+
+ connect(ui->listWidgetFrom, &QListWidget::itemChanged, this, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ QTC_ASSERT(root.isValid(), return );
+ const int stateCount = root.states().names().count();
+
+ QStringList stateNames;
+
+ for (const QListWidgetItem *item : ui->listWidgetFrom->findItems("*", Qt::MatchWildcard)) {
+ if (item->checkState() == Qt::Checked)
+ stateNames.append(item->text());
+ }
+
+ QString fromValue;
+ if (stateCount == stateNames.count())
+ fromValue = "*";
+ else
+ fromValue = stateNames.join(",");
+
+ m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, fromValue]() {
+ m_transition.variantProperty("from").setValue(fromValue);
+ });
+ });
+}
+
+TransitionForm::~TransitionForm()
+{
+ delete ui;
+}
+
+void TransitionForm::setTransition(const ModelNode &transition)
+{
+ m_transition = transition;
+
+ if (m_transition.isValid()) {
+ ui->idLineEdit->setText(m_transition.displayName());
+ }
+ setupStatesLists();
+}
+
+ModelNode TransitionForm::transition() const
+{
+ return m_transition;
+}
+
+void TransitionForm::setupStatesLists()
+{
+ bool bTo = ui->listWidgetTo->blockSignals(true);
+ bool bFrom = ui->listWidgetFrom->blockSignals(true);
+ QAbstractItemModel *modelTo = ui->listWidgetTo->model();
+ modelTo->removeRows(0, modelTo->rowCount());
+
+ QAbstractItemModel *modelFrom = ui->listWidgetFrom->model();
+ modelFrom->removeRows(0, modelFrom->rowCount());
+
+ bool starFrom = true;
+ bool starTo = true;
+
+ QStringList fromList;
+ QStringList toList;
+
+ if (m_transition.hasVariantProperty("from")
+ && m_transition.variantProperty("from").value().toString().trimmed() != "*") {
+ starFrom = false;
+ fromList = m_transition.variantProperty("from").value().toString().split(",");
+ }
+
+ if (m_transition.hasVariantProperty("to")
+ && m_transition.variantProperty("to").value().toString().trimmed() != "*") {
+ starTo = false;
+ toList = m_transition.variantProperty("to").value().toString().split(",");
+ }
+
+ if (m_transition.isValid()) {
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ if (root.isValid()) {
+ const QmlModelStateGroup states = root.states();
+ for (const QString &stateName : states.names()) {
+ auto itemTo = new QListWidgetItem(stateName, ui->listWidgetTo);
+ ui->listWidgetTo->addItem(itemTo);
+ itemTo->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
+ if (starTo || toList.contains(stateName))
+ itemTo->setCheckState(Qt::Checked);
+ else
+ itemTo->setCheckState(Qt::Unchecked);
+
+ auto itemFrom = new QListWidgetItem(stateName, ui->listWidgetFrom);
+ ui->listWidgetFrom->addItem(itemFrom);
+ itemFrom->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
+ if (starFrom || fromList.contains(stateName))
+ itemFrom->setCheckState(Qt::Checked);
+ else
+ itemFrom->setCheckState(Qt::Unchecked);
+ }
+ }
+ }
+ ui->listWidgetTo->blockSignals(bTo);
+ ui->listWidgetFrom->blockSignals(bFrom);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.h b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h
new file mode 100644
index 0000000000..9999557769
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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 <qmltimeline.h>
+
+#include <QWidget>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+namespace Ui {
+class TransitionForm;
+}
+
+class TransitionForm : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionForm(QWidget *parent);
+ ~TransitionForm() override;
+ void setTransition(const ModelNode &transition);
+ ModelNode transition() const;
+
+private:
+ void setupStatesLists();
+
+ Ui::TransitionForm *ui;
+ ModelNode m_transition;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui
new file mode 100644
index 0000000000..560c2065af
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TransitionForm</class>
+ <widget class="QWidget" name="QmlDesigner::TransitionForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>641</width>
+ <height>170</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Timeline Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QListWidget" name="listWidgetTo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Transition ID:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="2">
+ <spacer name="horizontalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0">
+ <widget class="QListWidget" name="listWidgetFrom"/>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QLineEdit" name="idLineEdit">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>From</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>To</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h
index d779cc15bf..f37bae869e 100644
--- a/src/plugins/qmldesigner/designercore/include/qmlitemnode.h
+++ b/src/plugins/qmldesigner/designercore/include/qmlitemnode.h
@@ -157,7 +157,6 @@ public:
void assignTargetFlowItem(const QmlFlowTargetNode &flowItem);
QmlFlowItemNode flowItemParent() const;
void destroyTarget();
- ModelNode decisionNodeForTransition(const ModelNode &transition) const;
};
class QMLDESIGNERCORE_EXPORT QmlFlowItemNode : public QmlItemNode
@@ -168,6 +167,8 @@ public:
static bool isValidQmlFlowItemNode(const ModelNode &modelNode);
QList<QmlFlowActionAreaNode> flowActionAreas() const;
QmlFlowViewNode flowView() const;
+
+ static ModelNode decisionNodeForTransition(const ModelNode &transition);
};
class QMLDESIGNERCORE_EXPORT QmlFlowViewNode : public QmlItemNode
diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
index 32cd4e6602..2e3b5be32b 100644
--- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
+++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp
@@ -69,6 +69,7 @@
#include "variantproperty.h"
#include "view3dactioncommand.h"
+#include <designersettings.h>
#include <metainfo.h>
#include <model.h>
#include <modelnode.h>
@@ -82,6 +83,10 @@
#include <coreplugin/documentmanager.h>
#endif
+#include <projectexplorer/target.h>
+
+#include <qmlprojectmanager/qmlmultilanguageaspect.h>
+
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -540,7 +545,14 @@ void NodeInstanceView::auxiliaryDataChanged(const ModelNode &node,
}
}
} else if (node.isRootNode() && name == "language@Internal") {
- nodeInstanceServer()->changeLanguage({value.toString()});
+ const QString languageAsString = value.toString();
+ if (m_currentTarget) {
+ if (auto rc = m_currentTarget->activeRunConfiguration()) {
+ if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>())
+ multiLanguageAspect->setLastUsedLanguage(languageAsString);
+ }
+ }
+ nodeInstanceServer()->changeLanguage({languageAsString});
} else if (node.isRootNode() && name == "previewSize@Internal") {
nodeInstanceServer()->changePreviewImageSize(value.toSize());
}
@@ -981,17 +993,27 @@ CreateSceneCommand NodeInstanceView::createCreateSceneCommand()
}
}
+ QString lastUsedLanguage;
+ if (m_currentTarget) {
+ if (auto rc = m_currentTarget->activeRunConfiguration()) {
+ if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>())
+ lastUsedLanguage = multiLanguageAspect->lastUsedLanguage();
+ }
+ }
- return CreateSceneCommand(instanceContainerList,
- reparentContainerList,
- idContainerList,
- valueContainerList,
- bindingContainerList,
- auxiliaryContainerVector,
- importVector,
- mockupTypesVector,
- model()->fileUrl(),
- m_edit3DToolStates[model()->fileUrl()]);
+ return CreateSceneCommand(
+ instanceContainerList,
+ reparentContainerList,
+ idContainerList,
+ valueContainerList,
+ bindingContainerList,
+ auxiliaryContainerVector,
+ importVector,
+ mockupTypesVector,
+ model()->fileUrl(),
+ m_edit3DToolStates[model()->fileUrl()],
+ lastUsedLanguage
+ );
}
ClearSceneCommand NodeInstanceView::createClearSceneCommand() const
diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp
index cfdc030c86..fcd5e88404 100644
--- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp
+++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.cpp
@@ -36,17 +36,20 @@
#include <app/app_version.h>
+#include <coreplugin/messagebox.h>
+#include <coreplugin/icore.h>
+
#include <projectexplorer/kit.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
-#include <coreplugin/messagebox.h>
-#include <coreplugin/icore.h>
+
+#include <qmlprojectmanager/qmlmultilanguageaspect.h>
+
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
-#include <coreplugin/icore.h>
#include <utils/algorithm.h>
#include <utils/environment.h>
@@ -164,18 +167,6 @@ QString PuppetCreator::getStyleConfigFileName() const
return QString();
}
-QString PuppetCreator::getMultilanguageDatabaseFilePath() const
-{
-#ifndef QMLDESIGNER_TEST
- if (m_target) {
- auto filePath = m_target->project()->projectDirectory().pathAppended("/multilanguage-experimental-v1.db");
- if (filePath.exists())
- return filePath.toString();
- }
-#endif
- return {};
-}
-
PuppetCreator::PuppetCreator(ProjectExplorer::Target *target, const Model *model)
: m_target(target)
@@ -496,11 +487,6 @@ QProcessEnvironment PuppetCreator::processEnvironment() const
environment.set("QMLDESIGNER_RC_PATHS", m_qrcMapping);
}
- const QString multilanguageDatabaseFilePath = getMultilanguageDatabaseFilePath();
-
- if (!multilanguageDatabaseFilePath.isEmpty())
- environment.set("QT_MULTILANGUAGE_DATABASE", multilanguageDatabaseFilePath);
-
#ifndef QMLDESIGNER_TEST
auto view = QmlDesignerPlugin::instance()->viewManager().nodeInstanceView();
view->emitCustomNotification("PuppetStatus", {}, {QVariant(m_qrcMapping)});
@@ -527,6 +513,13 @@ QProcessEnvironment PuppetCreator::processEnvironment() const
importPaths.append(designerImports);
customFileSelectors = m_target->additionalData("CustomFileSelectorsData").toStringList();
+
+ if (auto *rc = m_target->activeRunConfiguration()) {
+ if (auto multiLanguageAspect = rc->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) {
+ if (!multiLanguageAspect->databaseFilePath().isEmpty())
+ environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString());
+ }
+ }
}
customFileSelectors.append("DesignMode");
diff --git a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h
index f8033fd3e1..bafea8fa3e 100644
--- a/src/plugins/qmldesigner/designercore/instances/puppetcreator.h
+++ b/src/plugins/qmldesigner/designercore/instances/puppetcreator.h
@@ -102,7 +102,6 @@ protected:
bool useOnlyFallbackPuppet() const;
QString getStyleConfigFileName() const;
- QString getMultilanguageDatabaseFilePath() const;
private:
mutable QString m_compileLog;
diff --git a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp
index 18bea1990c..b35a54a3fe 100644
--- a/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp
+++ b/src/plugins/qmldesigner/designercore/model/modeltotextmerger.cpp
@@ -388,6 +388,8 @@ PropertyNameList ModelToTextMerger::propertyOrder()
PropertyName("wrapMode"),
PropertyName(),
PropertyName("states"),
+ PropertyName("to"),
+ PropertyName("from"),
PropertyName("transitions")
};
diff --git a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp
index 959f417c4c..fe42f2b7e0 100644
--- a/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp
+++ b/src/plugins/qmldesigner/designercore/model/qmlitemnode.cpp
@@ -614,32 +614,6 @@ void QmlFlowActionAreaNode::destroyTarget()
}
}
-ModelNode QmlFlowActionAreaNode::decisionNodeForTransition(const ModelNode &transition) const
-{
- ModelNode target = targetTransition();
-
- if (target.isValid() && target.hasMetaInfo() && QmlVisualNode::isFlowTransition(target)) {
-
- ModelNode finalTarget = target.bindingProperty("to").resolveToModelNode();
-
- if (finalTarget.isValid() && finalTarget.hasMetaInfo() && QmlVisualNode::isFlowDecision(finalTarget)) {
- if (finalTarget.hasBindingProperty("targets")
- && finalTarget.bindingProperty("targets").resolveToModelNodeList().contains(transition))
- return finalTarget;
- }
- QmlFlowViewNode flowView(view()->rootModelNode());
- if (flowView.isValid()) {
- for (const ModelNode &target : flowView.decicions()) {
- if (target.hasBindingProperty("targets")
- && target.bindingProperty("targets").resolveToModelNodeList().contains(transition))
- return target;
- }
- }
- }
-
- return {};
-}
-
bool QmlFlowViewNode::isValid() const
{
return isValidQmlFlowViewNode(modelNode());
@@ -781,12 +755,38 @@ QmlFlowViewNode QmlFlowTargetNode::flowView() const
return view()->rootModelNode();
}
+ModelNode QmlFlowItemNode::decisionNodeForTransition(const ModelNode &transition)
+{
+ ModelNode target = transition;
+
+ if (target.isValid() && target.hasMetaInfo() && QmlVisualNode::isFlowTransition(target)) {
+
+ ModelNode finalTarget = target.bindingProperty("to").resolveToModelNode();
+
+ if (finalTarget.isValid() && finalTarget.hasMetaInfo() && QmlVisualNode::isFlowDecision(finalTarget)) {
+ if (finalTarget.hasBindingProperty("targets")
+ && finalTarget.bindingProperty("targets").resolveToModelNodeList().contains(transition))
+ return finalTarget;
+ }
+ QmlFlowViewNode flowView(transition.view()->rootModelNode());
+ if (flowView.isValid()) {
+ for (const ModelNode target : flowView.decicions()) {
+ if (target.hasBindingProperty("targets")
+ && target.bindingProperty("targets").resolveToModelNodeList().contains(transition))
+ return target;
+ }
+ }
+ }
+
+ return {};
+}
+
ModelNode QmlFlowTargetNode::findSourceForDecisionNode() const
{
if (!isFlowDecision())
return {};
- for (const ModelNode &transition : flowView().transitionsForTarget(modelNode())) {
+ for (const ModelNode transition : flowView().transitionsForTarget(modelNode())) {
if (transition.hasBindingProperty("from")) {
const ModelNode source = transition.bindingProperty("from").resolveToModelNode();
if (source.isValid()) {
diff --git a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
index f2f7f02e64..5d0cd5da35 100644
--- a/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
+++ b/src/plugins/qmldesigner/designercore/model/rewriterview.cpp
@@ -53,6 +53,8 @@
#include <utils/changeset.h>
#include <utils/qtcassert.h>
+#include <QRegExp>
+
#include <utility>
#include <vector>
@@ -539,6 +541,9 @@ QString RewriterView::auxiliaryDataAsQML() const
QTC_ASSERT(!m_canonicalIntModelNode.isEmpty(), return {});
int columnCount = 0;
+
+ const QRegExp safeName("[a-z][a-zA-Z0-9]*");
+
for (const auto &node : allModelNodes()) {
QHash<PropertyName, QVariant> data = node.auxiliaryData();
if (!data.isEmpty()) {
@@ -571,6 +576,9 @@ QString RewriterView::auxiliaryDataAsQML() const
if (idIsQmlKeyWord(key))
continue;
+ if (!safeName.exactMatch(key))
+ continue;
+
const QVariant value = data.value(key.toUtf8());
QString strValue = value.toString();
diff --git a/src/plugins/qmldesigner/designersettings.cpp b/src/plugins/qmldesigner/designersettings.cpp
index 91091c53d6..c0e8b3bdf1 100644
--- a/src/plugins/qmldesigner/designersettings.cpp
+++ b/src/plugins/qmldesigner/designersettings.cpp
@@ -50,8 +50,8 @@ void DesignerSettings::fromSettings(QSettings *settings)
restoreValue(settings, DesignerSettingsKey::ITEMSPACING, 6);
restoreValue(settings, DesignerSettingsKey::CONTAINERPADDING, 8);
- restoreValue(settings, DesignerSettingsKey::CANVASWIDTH, 40000);
- restoreValue(settings, DesignerSettingsKey::CANVASHEIGHT, 40000);
+ restoreValue(settings, DesignerSettingsKey::CANVASWIDTH, 100000);
+ restoreValue(settings, DesignerSettingsKey::CANVASHEIGHT, 100000);
restoreValue(settings, DesignerSettingsKey::ROOT_ELEMENT_INIT_WIDTH, 640);
restoreValue(settings, DesignerSettingsKey::ROOT_ELEMENT_INIT_HEIGHT, 480);
restoreValue(settings, DesignerSettingsKey::WARNING_FOR_FEATURES_IN_DESIGNER, true);
diff --git a/src/plugins/qmldesigner/designersettings.h b/src/plugins/qmldesigner/designersettings.h
index 4d25edb8bc..42bc957b96 100644
--- a/src/plugins/qmldesigner/designersettings.h
+++ b/src/plugins/qmldesigner/designersettings.h
@@ -25,7 +25,8 @@
#pragma once
-#include <QtGlobal>
+#include <qmldesignercorelib_global.h>
+
#include <QHash>
#include <QVariant>
#include <QByteArray>
@@ -70,7 +71,7 @@ const char ALWAYS_DESIGN_MODE[] = "AlwaysDesignMode";
const char DISABLE_ITEM_LIBRARY_UPDATE_TIMER[] = "DisableItemLibraryUpdateTimer";
}
-class DesignerSettings : public QHash<QByteArray, QVariant>
+class QMLDESIGNERCORE_EXPORT DesignerSettings : public QHash<QByteArray, QVariant>
{
public:
DesignerSettings();
diff --git a/src/plugins/qmldesigner/qmldesigner.pro b/src/plugins/qmldesigner/qmldesigner.pro
index 1986677fac..43cb1e9be5 100644
--- a/src/plugins/qmldesigner/qmldesigner.pro
+++ b/src/plugins/qmldesigner/qmldesigner.pro
@@ -1,4 +1,9 @@
TEMPLATE = subdirs
CONFIG += ordered
-SUBDIRS = qmldesignerplugin.pro qtquickplugin componentsplugin qmlpreviewplugin
+SUBDIRS = \
+ qmldesignerplugin.pro \
+ qtquickplugin \
+ componentsplugin \
+ qmlpreviewplugin \
+ assetexporterplugin
diff --git a/src/plugins/qmldesigner/qmldesigner.qbs b/src/plugins/qmldesigner/qmldesigner.qbs
index d9ff0b56c5..c0dad85750 100644
--- a/src/plugins/qmldesigner/qmldesigner.qbs
+++ b/src/plugins/qmldesigner/qmldesigner.qbs
@@ -7,5 +7,6 @@ Project {
"qtquickplugin/qtquickplugin.qbs",
"componentsplugin/componentsplugin.qbs",
"qmlpreviewplugin/qmlpreviewplugin.qbs",
+ "assetexporterplugin/assetexporterplugin.qbs"
]
}
diff --git a/src/plugins/qmldesigner/qmldesigner_dependencies.pri b/src/plugins/qmldesigner/qmldesigner_dependencies.pri
index cba0186ce7..321d2c2b07 100644
--- a/src/plugins/qmldesigner/qmldesigner_dependencies.pri
+++ b/src/plugins/qmldesigner/qmldesigner_dependencies.pri
@@ -11,6 +11,7 @@ QTC_PLUGIN_DEPENDS += \
qtsupport \
projectexplorer \
qmakeprojectmanager \
+ qmlprojectmanager \
resourceeditor
INCLUDEPATH *= \
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp
index fa2afe7e81..a80009f3da 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.cpp
+++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp
@@ -41,6 +41,7 @@
#include <formeditor/transitiontool.h>
#include <texttool/texttool.h>
#include <timelineeditor/timelineview.h>
+#include <transitioneditor/transitioneditorview.h>
#include <pathtool/pathtool.h>
#include <qmljseditor/qmljseditor.h>
@@ -239,6 +240,10 @@ bool QmlDesignerPlugin::delayedInitialize()
timelineView->registerActions();
}
+ auto transitionEditorView = new QmlDesigner::TransitionEditorView;
+ d->viewManager.registerViewTakingOwnership(transitionEditorView);
+ transitionEditorView->registerActions();
+
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::SourceTool);
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::ColorTool);
d->viewManager.registerFormEditorToolTakingOwnership(new QmlDesigner::AnnotationTool);
@@ -447,7 +452,6 @@ void QmlDesignerPlugin::activateAutoSynchronization()
currentDesignDocument()->loadDocument(currentDesignDocument()->plainTextEdit());
currentDesignDocument()->updateActiveTarget();
- currentDesignDocument()->updateActiveTarget();
d->mainWidget.enableWidgets();
currentDesignDocument()->attachRewriterToModel();
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro
index 0095aa8f10..bc0da99050 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.pro
+++ b/src/plugins/qmldesigner/qmldesignerplugin.pro
@@ -32,6 +32,7 @@ include(components/curveeditor/curveeditor.pri)
include(components/bindingeditor/bindingeditor.pri)
include(components/annotationeditor/annotationeditor.pri)
include(components/richtexteditor/richtexteditor.pri)
+include(components/transitioneditor/transitioneditor.pri)
BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH)
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs
index dbd37279e5..8d649a0f8f 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.qbs
+++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs
@@ -845,6 +845,26 @@ Project {
"timelineeditor/timelineview.h",
"timelineeditor/timelinewidget.cpp",
"timelineeditor/timelinewidget.h",
+ "transitioneditor/transitioneditorview.cpp",
+ "transitioneditor/transitioneditorview.h",
+ "transitioneditor/transitioneditorwidget.cpp",
+ "transitioneditor/transitioneditorwidget.h",
+ "transitioneditor/transitioneditortoolbar.cpp",
+ "transitioneditor/transitioneditortoolbar.h",
+ "transitioneditor/transitioneditorgraphicsscene.cpp",
+ "transitioneditor/transitioneditorgraphicsscene.h",
+ "transitioneditor/transitioneditorgraphicslayout.cpp",
+ "transitioneditor/transitioneditorgraphicslayout.h",
+ "transitioneditor/transitioneditorsectionitem.cpp",
+ "transitioneditor/transitioneditorsectionitem.h",
+ "transitioneditor/transitioneditorpropertyitem.cpp",
+ "transitioneditor/transitioneditorpropertyitem.h",
+ "transitioneditor/transitioneditorsettingsdialog.cpp",
+ "transitioneditor/transitioneditorsettingsdialog.h",
+ "transitioneditor/transitioneditorsettingsdialog.ui"
+ "transitioneditor/transitionform.cpp",
+ "transitioneditor/transitionform.h",
+ "transitioneditor/transitioneditor.qrc"
]
}
diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
index 330446d840..b94f87bc40 100644
--- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
+++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
@@ -27,6 +27,7 @@
#include "qmlpreviewactions.h"
#include <zoomaction.h>
+#include <designersettings.h>
#include <utils/utilsicons.h>
#include <projectexplorer/projectexplorer.h>
@@ -47,12 +48,10 @@ const Utils::Icon previewIcon({
static void handleAction(const SelectionContext &context)
{
if (context.view()->isAttached()) {
- if (context.toggled()) {
+ if (context.toggled())
ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE);
- QmlPreviewPlugin::setQmlFile();
- } else {
+ else
QmlPreviewPlugin::stopAllRunControls();
- }
}
}
@@ -216,42 +215,46 @@ SwitchLanguageComboboxAction::SwitchLanguageComboboxAction(QObject *parent)
connect(ProjectExplorer::SessionManager::instance(),
&ProjectExplorer::SessionManager::startupProjectChanged,
this,
- &SwitchLanguageComboboxAction::refreshProjectLocales);
+ &SwitchLanguageComboboxAction::updateProjectLocales);
}
QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent)
{
QPointer<QComboBox> comboBox = new QComboBox(parent);
comboBox->setToolTip(tr("Switch the language used by preview."));
- connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this, comboBox](int index) {
- if (index == 0)
- emit currentLocaleChanged("");
- else
- emit currentLocaleChanged(comboBox->currentText());
- });
+ comboBox->addItem(tr("Default"));
auto refreshComboBoxFunction = [this, comboBox] (ProjectExplorer::Project *project) {
if (comboBox) {
- refreshProjectLocales(project);
- comboBox->clear();
- comboBox->addItem(tr("Default"));
- comboBox->addItems(m_localeStrings);
+ if (updateProjectLocales(project)) {
+ comboBox->clear();
+ comboBox->addItem(tr("Default"));
+ comboBox->addItems(m_localeStrings);
+ }
}
};
- connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged,
- refreshComboBoxFunction);
+ connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged,
+ comboBox, refreshComboBoxFunction);
if (auto project = SessionManager::startupProject())
refreshComboBoxFunction(project);
+ // do this after refreshComboBoxFunction so we do not get currentLocaleChanged signals at initialization
+ connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), [this, comboBox](int index) {
+ if (index == 0) // == Default
+ emit currentLocaleChanged("");
+ else
+ emit currentLocaleChanged(comboBox->currentText());
+ });
+
return comboBox;
}
-void SwitchLanguageComboboxAction::refreshProjectLocales(Project *project)
+bool SwitchLanguageComboboxAction::updateProjectLocales(Project *project)
{
if (!project)
- return;
+ return false;
+ auto previousLocales = m_localeStrings;
m_localeStrings.clear();
const auto projectDirectory = project->rootProjectDirectory().toFileInfo().absoluteFilePath();
const QDir languageDirectory(projectDirectory + "/i18n");
@@ -262,6 +265,7 @@ void SwitchLanguageComboboxAction::refreshProjectLocales(Project *project)
const QString locale = qmFile.left(localeEndPosition).mid(localeStartPosition);
return locale;
});
+ return previousLocales != m_localeStrings;
}
SwitchLanguageAction::SwitchLanguageAction()
diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h
index 8a71ee07c2..a23f125ca1 100644
--- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h
+++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.h
@@ -113,7 +113,7 @@ signals:
protected:
QWidget *createWidget(QWidget *parent) override;
private:
- void refreshProjectLocales(ProjectExplorer::Project *project);
+ bool updateProjectLocales(ProjectExplorer::Project *project);
QStringList m_localeStrings;
};
diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
index a6437d62f9..f1caffa1a7 100644
--- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
+++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
@@ -27,6 +27,7 @@
#include <qmlprojectmanager/qmlproject.h>
#include <qmlprojectmanager/qmlmainfileaspect.h>
+#include <qmlprojectmanager/qmlmultilanguageaspect.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
@@ -152,10 +153,9 @@ LocalQmlPreviewSupport::LocalQmlPreviewSupport(ProjectExplorer::RunControl *runC
}
}
- if (runControl->project()) {
- auto multilanguageDatabaseFilePath = runControl->project()->projectDirectory().pathAppended("/multilanguage-experimental-v1.db");
- if (multilanguageDatabaseFilePath.exists())
- runnable.environment.set("QT_MULTILANGUAGE_DATABASE", multilanguageDatabaseFilePath.toString());
+ if (auto multiLanguageAspect = runControl->aspect<QmlProjectManager::QmlMultiLanguageAspect>()) {
+ if (!multiLanguageAspect->databaseFilePath().isEmpty())
+ runnable.environment.set("QT_MULTILANGUAGE_DATABASE", multiLanguageAspect->databaseFilePath().toString());
}
Utils::QtcProcess::addArg(&runnable.commandLineArguments,
diff --git a/src/plugins/qmlprojectmanager/CMakeLists.txt b/src/plugins/qmlprojectmanager/CMakeLists.txt
index 1d8527c221..fa037362a5 100644
--- a/src/plugins/qmlprojectmanager/CMakeLists.txt
+++ b/src/plugins/qmlprojectmanager/CMakeLists.txt
@@ -6,6 +6,7 @@ add_qtc_plugin(QmlProjectManager
fileformat/qmlprojectfileformat.cpp fileformat/qmlprojectfileformat.h
fileformat/qmlprojectitem.cpp fileformat/qmlprojectitem.h
qmlmainfileaspect.cpp qmlmainfileaspect.h
+ qmlmultilanguageaspect.cpp qmlmultilanguageaspect.h
qmlproject.cpp qmlproject.h
qmlproject.qrc
qmlprojectconstants.h
diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
new file mode 100644
index 0000000000..64dd8a93d6
--- /dev/null
+++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
@@ -0,0 +1,128 @@
+/****************************************************************************
+**
+** 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 "qmlmultilanguageaspect.h"
+
+#include <extensionsystem/pluginmanager.h>
+#include <extensionsystem/pluginspec.h>
+
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/target.h>
+
+static bool isMultilanguagePresent()
+{
+ const QVector<ExtensionSystem::PluginSpec *> specs = ExtensionSystem::PluginManager::plugins();
+ return std::find_if(specs.begin(),
+ specs.end(),
+ [](ExtensionSystem::PluginSpec *spec) {
+ return spec->name() == "MultiLanguage";
+ })
+ != specs.end();
+}
+
+static Utils::FilePath getMultilanguageDatabaseFilePath(ProjectExplorer::Target *target)
+{
+ if (target) {
+ auto filePath = target->project()->projectDirectory().pathAppended("/multilanguage-experimental-v1.db");
+ if (filePath.exists())
+ return filePath;
+ }
+ return {};
+}
+
+static QObject *getPreviewPlugin()
+{
+ auto pluginIt = std::find_if(ExtensionSystem::PluginManager::plugins().begin(),
+ ExtensionSystem::PluginManager::plugins().end(),
+ [](const ExtensionSystem::PluginSpec *p) {
+ return p->name() == "QmlPreview";
+ });
+
+ if (pluginIt != ExtensionSystem::PluginManager::plugins().constEnd())
+ return (*pluginIt)->plugin();
+
+ return nullptr;
+}
+
+
+namespace QmlProjectManager {
+
+QmlMultiLanguageAspect::QmlMultiLanguageAspect(ProjectExplorer::Target *target)
+ : m_target(target)
+{
+ setVisible(isMultilanguagePresent());
+ setSettingsKey(Constants::USE_MULTILANGUAGE_KEY);
+ setLabel(tr("Use MultiLanguage translation database."), BaseBoolAspect::LabelPlacement::AtCheckBox);
+ setToolTip(tr("Enable loading application with special desktop SQLite translation database."));
+
+ setDefaultValue(!databaseFilePath().isEmpty());
+ QVariantMap getDefaultValues;
+ fromMap(getDefaultValues);
+
+ if (auto previewPlugin = getPreviewPlugin())
+ connect(previewPlugin, SIGNAL(localeChanged(QString)), this, SLOT(setLastUsedLanguage(QString)));
+}
+
+QmlMultiLanguageAspect::~QmlMultiLanguageAspect()
+{
+}
+
+void QmlMultiLanguageAspect::setLastUsedLanguage(const QString &language)
+{
+ if (auto previewPlugin = getPreviewPlugin())
+ previewPlugin->setProperty("locale", language);
+ if (m_lastUsedLanguage != language) {
+ m_lastUsedLanguage = language;
+ emit changed();
+ }
+}
+
+QString QmlMultiLanguageAspect::lastUsedLanguage() const
+{
+ return m_lastUsedLanguage;
+}
+
+Utils::FilePath QmlMultiLanguageAspect::databaseFilePath() const
+{
+ if (m_databaseFilePath.isEmpty())
+ m_databaseFilePath = getMultilanguageDatabaseFilePath(m_target);
+ return m_databaseFilePath;
+}
+
+void QmlMultiLanguageAspect::toMap(QVariantMap &map) const
+{
+ BaseBoolAspect::toMap(map);
+ if (!m_lastUsedLanguage.isEmpty())
+ map.insert(Constants::LAST_USED_LANGUAGE, m_lastUsedLanguage);
+}
+
+void QmlMultiLanguageAspect::fromMap(const QVariantMap &map)
+{
+ BaseBoolAspect::fromMap(map);
+ setLastUsedLanguage(map.value(Constants::LAST_USED_LANGUAGE, "en").toString());
+}
+
+} // namespace QmlProjectManager
diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h
new file mode 100644
index 0000000000..163552caf0
--- /dev/null
+++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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 "qmlprojectmanager_global.h"
+
+#include <qmlprojectmanager/qmlprojectmanagerconstants.h>
+
+#include <projectexplorer/runconfigurationaspects.h>
+
+namespace QmlProjectManager {
+
+class QMLPROJECTMANAGER_EXPORT QmlMultiLanguageAspect : public ProjectExplorer::BaseBoolAspect
+{
+ Q_OBJECT
+public:
+ explicit QmlMultiLanguageAspect(ProjectExplorer::Target *target);
+ ~QmlMultiLanguageAspect() override;
+
+ QString lastUsedLanguage() const;
+ Utils::FilePath databaseFilePath() const;
+ void toMap(QVariantMap &map) const final;
+ void fromMap(const QVariantMap &map) final;
+
+public slots:
+ void setLastUsedLanguage(const QString &language);
+
+private:
+ ProjectExplorer::Target *m_target = nullptr;
+ mutable Utils::FilePath m_databaseFilePath;
+ QString m_lastUsedLanguage;
+};
+
+} // namespace QmlProjectManager
diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanager.pro b/src/plugins/qmlprojectmanager/qmlprojectmanager.pro
index 049c5a801b..330d93d601 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectmanager.pro
+++ b/src/plugins/qmlprojectmanager/qmlprojectmanager.pro
@@ -7,6 +7,7 @@ DEFINES += QMLPROJECTMANAGER_LIBRARY
HEADERS += \
qmlmainfileaspect.h \
+ qmlmultilanguageaspect.h \
qmlproject.h \
qmlprojectplugin.h \
qmlprojectconstants.h \
@@ -17,6 +18,7 @@ HEADERS += \
SOURCES += \
qmlmainfileaspect.cpp \
+ qmlmultilanguageaspect.cpp \
qmlproject.cpp \
qmlprojectplugin.cpp \
qmlprojectnodes.cpp \
diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs b/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs
index 8d466ab262..1a160bff1c 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs
+++ b/src/plugins/qmlprojectmanager/qmlprojectmanager.qbs
@@ -16,6 +16,7 @@ QtcPlugin {
name: "General"
files: [
"qmlmainfileaspect.cpp", "qmlmainfileaspect.h",
+ "qmlmultilanguageaspect.cpp", "qmlmultilanguageaspect.h",
"qmlproject.cpp", "qmlproject.h",
"qmlproject.qrc",
"qmlprojectconstants.h",
diff --git a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h
index cddac079dc..2abdb9fa34 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h
+++ b/src/plugins/qmlprojectmanager/qmlprojectmanagerconstants.h
@@ -34,6 +34,8 @@ const char QML_PROJECT_ID[] = "QmlProjectManager.QmlProject";
const char QML_VIEWER_ARGUMENTS_KEY[] = "QmlProjectManager.QmlRunConfiguration.QDeclarativeViewerArguments";
const char QML_VIEWER_TARGET_DISPLAY_NAME[] = "QML Viewer";
const char QML_MAINSCRIPT_KEY[] = "QmlProjectManager.QmlRunConfiguration.MainScript";
+const char USE_MULTILANGUAGE_KEY[] = "QmlProjectManager.QmlRunConfiguration.UseMultiLanguage";
+const char LAST_USED_LANGUAGE[] = "QmlProjectManager.QmlRunConfiguration.LastUsedLanguage";
const char USER_ENVIRONMENT_CHANGES_KEY[] = "QmlProjectManager.QmlRunConfiguration.UserEnvironmentChanges";
} // namespace Constants
diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
index 5afb957789..8ec0c29246 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
+++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
@@ -27,7 +27,7 @@
#include "qmlproject.h"
#include "qmlprojectmanagerconstants.h"
#include "qmlmainfileaspect.h"
-#include "qmlmainfileaspect.h"
+#include "qmlmultilanguageaspect.h"
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
@@ -59,6 +59,7 @@ using namespace QtSupport;
using namespace Utils;
namespace QmlProjectManager {
+class QmlMultiLanguageAspect;
namespace Internal {
// QmlProjectRunConfiguration
@@ -81,30 +82,12 @@ private:
BaseStringAspect *m_qmlViewerAspect = nullptr;
QmlMainFileAspect *m_qmlMainFileAspect = nullptr;
+ QmlMultiLanguageAspect *m_multiLanguageAspect = nullptr;
};
QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id)
: RunConfiguration(target, id)
{
- auto envAspect = addAspect<EnvironmentAspect>();
-
- auto envModifier = [this](Environment env) {
- if (auto bs = dynamic_cast<const QmlBuildSystem *>(activeBuildSystem()))
- env.modify(bs->environment());
- return env;
- };
-
- const Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(target->kit());
- if (deviceTypeId == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
- envAspect->addPreferredBaseEnvironment(tr("System Environment"), [envModifier] {
- return envModifier(Environment::systemEnvironment());
- });
- }
-
- envAspect->addSupportedBaseEnvironment(tr("Clean Environment"), [envModifier] {
- return envModifier(Environment());
- });
-
m_qmlViewerAspect = addAspect<BaseStringAspect>();
m_qmlViewerAspect->setLabelText(tr("QML Viewer:"));
m_qmlViewerAspect->setPlaceHolderText(commandLine().executable().toString());
@@ -123,6 +106,34 @@ QmlProjectRunConfiguration::QmlProjectRunConfiguration(Target *target, Id id)
connect(target, &Target::kitChanged, this, &RunConfiguration::update);
+ m_multiLanguageAspect = addAspect<QmlMultiLanguageAspect>(target);
+
+ auto envAspect = addAspect<EnvironmentAspect>();
+ connect(m_multiLanguageAspect, &QmlMultiLanguageAspect::changed, envAspect, &EnvironmentAspect::environmentChanged);
+
+ auto envModifier = [this](Environment env) {
+ if (auto bs = dynamic_cast<const QmlBuildSystem *>(activeBuildSystem()))
+ env.modify(bs->environment());
+
+ if (m_multiLanguageAspect && m_multiLanguageAspect->value() && !m_multiLanguageAspect->databaseFilePath().isEmpty()) {
+ env.set("QT_MULTILANGUAGE_DATABASE", m_multiLanguageAspect->databaseFilePath().toString());
+ env.set("QT_MULTILANGUAGE_LANGUAGE", m_multiLanguageAspect->lastUsedLanguage());
+ }
+ return env;
+ };
+
+ const Id deviceTypeId = DeviceTypeKitAspect::deviceTypeId(target->kit());
+ if (deviceTypeId == ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
+ envAspect->addPreferredBaseEnvironment(tr("System Environment"), [envModifier] {
+ return envModifier(Environment::systemEnvironment());
+ });
+ }
+
+ envAspect->addSupportedBaseEnvironment(tr("Clean Environment"), [envModifier] {
+ Environment environment;
+ return envModifier(environment);
+ });
+
setDisplayName(tr("QML Scene", "QMLRunConfiguration display name."));
update();
}
@@ -208,6 +219,10 @@ QString QmlProjectRunConfiguration::commandLineArguments() const
const QString main = bs->targetFile(FilePath::fromString(mainScript())).toString();
if (!main.isEmpty())
QtcProcess::addArg(&args, main, osType);
+
+ if (m_multiLanguageAspect && m_multiLanguageAspect->value())
+ QtcProcess::addArg(&args, "-qmljsdebugger=file:unused_if_debugger_arguments_added,services:DebugTranslation", osType);
+
return args;
}
diff --git a/tests/unit/mockup/projectexplorer/project.h b/tests/unit/mockup/projectexplorer/project.h
index e193ad404d..9e8e6a5311 100644
--- a/tests/unit/mockup/projectexplorer/project.h
+++ b/tests/unit/mockup/projectexplorer/project.h
@@ -44,7 +44,7 @@ public:
Target *activeTarget() const { return {}; }
- QVariant namedSettings(const QString &name) const { return settings.at(name); }
+ QVariant namedSettings(const QString &name) const { return settings[name]; }
void setNamedSettings(const QString &name, const QVariant &value) { settings[name] = value; }
Utils::FilePath rootProjectDirectoryPath;