diff options
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 ¶llel : 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; |