aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Hartmann <thomas.hartmann@qt.io>2020-06-17 19:10:35 +0200
committerThomas Hartmann <thomas.hartmann@qt.io>2020-06-26 09:44:08 +0000
commit454ff4c46bbfd0a068813bd6a0bb6929f1986e76 (patch)
tree617bec977ecc0626b5662db0ecbc67e58c433e5b
parent4e9b6d68c180f3df76fb30a0d9b6fb5bebabc7f5 (diff)
QmlDesigner: First implementation of TransitionEditor
Change-Id: I14391e872f6a257a2cdf75e7d577de64c384c1fd Reviewed-by: Tim Jenssen <tim.jenssen@qt.io> Reviewed-by: Henning Gründl <henning.gruendl@qt.io> Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io>
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt16
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri35
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc4
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h37
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp172
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h89
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp431
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h146
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp237
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h73
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp803
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h145
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp177
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h66
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui74
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp342
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h92
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp348
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h97
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp414
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h103
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp211
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.h57
-rw-r--r--src/plugins/qmldesigner/components/transitioneditor/transitionform.ui87
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.cpp5
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.pro1
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.qbs20
27 files changed, 4282 insertions, 0 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 866ca57cbc..94a4f449a7 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -669,6 +669,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
animationcurve.cpp animationcurve.h
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri
new file mode 100644
index 0000000000..8f9a9dec9e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.pri
@@ -0,0 +1,35 @@
+QT *= qml quick core
+
+VPATH += $$PWD
+
+INCLUDEPATH += $$PWD
+
+SOURCES += \
+ transitioneditorview.cpp \
+ transitioneditorwidget.cpp \
+ transitioneditortoolbar.cpp \
+ transitioneditorgraphicsscene.cpp \
+ transitioneditorgraphicslayout.cpp \
+ transitioneditorsectionitem.cpp \
+ transitioneditorpropertyitem.cpp \
+ transitioneditorsettingsdialog.cpp \
+ transitionform.cpp
+
+HEADERS += \
+ transitioneditorconstants \
+ transitioneditorview.h \
+ transitioneditorwidget.h \
+ transitioneditortoolbar.h \
+ transitioneditorgraphicsscene.h \
+ transitioneditorgraphicslayout.h \
+ transitioneditorsectionitem.h \
+ transitioneditorpropertyitem.h \
+ transitioneditorsettingsdialog.h \
+ transitionform.h
+
+RESOURCES += \
+ transitioneditor.qrc
+
+FORMS += \
+ transitioneditorsettingsdialog.ui \
+ transitionform.ui
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc
new file mode 100644
index 0000000000..a2a962a6b8
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditor.qrc
@@ -0,0 +1,4 @@
+<RCC>
+ <qresource prefix="/transitioneditor">
+ </qresource>
+</RCC>
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h
new file mode 100644
index 0000000000..c06249eacb
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorconstants.h
@@ -0,0 +1,37 @@
+/****************************************************************************
+**
+** Copyright (C) 2019 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <QGraphicsItem>
+
+namespace QmlDesigner {
+namespace TransitionEditorConstants {
+
+const int transitionEditorSectionItemUserType = QGraphicsItem::UserType + 6;
+const int transitionEditorPropertyItemUserType = QGraphicsItem::UserType + 7;
+
+} // namespace TransitionEditorConstants
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp
new file mode 100644
index 0000000000..02e1258dfd
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.cpp
@@ -0,0 +1,172 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorgraphicslayout.h"
+
+#include "timelinegraphicsscene.h"
+#include "timelineplaceholder.h"
+#include "timelinesectionitem.h"
+#include "timelineview.h"
+#include "transitioneditorsectionitem.h"
+
+#include <QGraphicsLinearLayout>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+TransitionEditorGraphicsLayout::TransitionEditorGraphicsLayout(QGraphicsScene *scene,
+ TimelineItem *parent)
+ : TimelineItem(parent)
+ , m_layout(new QGraphicsLinearLayout)
+ , m_rulerItem(TimelineRulerSectionItem::create(scene, this))
+ , m_placeholder1(TimelinePlaceholder::create(scene, this))
+ , m_placeholder2(TimelinePlaceholder::create(scene, this))
+{
+ m_layout->setOrientation(Qt::Vertical);
+ m_layout->setSpacing(0);
+ m_layout->setContentsMargins(0, 0, 0, 0);
+
+ m_layout->addItem(m_rulerItem);
+ m_layout->addItem(m_placeholder1);
+ m_layout->addItem(m_placeholder2);
+
+ setLayout(m_layout);
+
+ setPos(QPointF(0, 0));
+
+ connect(m_rulerItem,
+ &TimelineRulerSectionItem::rulerClicked,
+ this,
+ &TransitionEditorGraphicsLayout::rulerClicked);
+}
+
+TransitionEditorGraphicsLayout::~TransitionEditorGraphicsLayout() = default;
+
+double TransitionEditorGraphicsLayout::rulerWidth() const
+{
+ return m_rulerItem->preferredWidth();
+}
+
+double TransitionEditorGraphicsLayout::rulerScaling() const
+{
+ return m_rulerItem->rulerScaling();
+}
+
+double TransitionEditorGraphicsLayout::rulerDuration() const
+{
+ return m_rulerItem->rulerDuration();
+}
+
+double TransitionEditorGraphicsLayout::endFrame() const
+{
+ return m_rulerItem->endFrame();
+}
+
+void TransitionEditorGraphicsLayout::setWidth(int width)
+{
+ m_rulerItem->setSizeHints(width);
+ m_placeholder1->setMinimumWidth(width);
+ m_placeholder2->setMinimumWidth(width);
+ setPreferredWidth(width);
+ setMaximumWidth(width);
+}
+
+void TransitionEditorGraphicsLayout::setTransition(const ModelNode &transition)
+{
+ m_layout->removeItem(m_rulerItem);
+ m_layout->removeItem(m_placeholder1);
+ m_layout->removeItem(m_placeholder2);
+
+ m_rulerItem->setParentItem(nullptr);
+ m_placeholder1->setParentItem(nullptr);
+ m_placeholder2->setParentItem(nullptr);
+
+ qDeleteAll(this->childItems());
+
+ m_rulerItem->setParentItem(this);
+
+ qreal duration = 2000;
+ if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration"))
+ duration = transition.auxiliaryData("transitionDuration").toDouble();
+
+ setDuration(duration);
+ m_layout->addItem(m_rulerItem);
+
+ m_placeholder1->setParentItem(this);
+ m_layout->addItem(m_placeholder1);
+
+ m_layout->invalidate();
+
+ if (transition.isValid() && !transition.directSubModelNodes().isEmpty()) {
+ for (const ModelNode &parallel : transition.directSubModelNodes()) {
+ auto item = TransitionEditorSectionItem::create(parallel, this);
+ m_layout->addItem(item);
+ }
+ }
+
+ m_placeholder2->setParentItem(this);
+ m_layout->addItem(m_placeholder2);
+
+ if (auto *scene = timelineScene())
+ if (auto *view = scene->timelineView())
+ if (!transition.isValid() && view->isAttached())
+ emit scaleFactorChanged(0);
+}
+
+void TransitionEditorGraphicsLayout::setDuration(qreal duration)
+{
+ m_rulerItem->invalidateRulerSize(duration);
+}
+
+void TransitionEditorGraphicsLayout::setRulerScaleFactor(int factor)
+{
+ m_rulerItem->setRulerScaleFactor(factor);
+}
+
+void TransitionEditorGraphicsLayout::invalidate()
+{
+ m_layout->invalidate();
+}
+
+int TransitionEditorGraphicsLayout::maximumScrollValue() const
+{
+ const qreal w = this->geometry().width() - qreal(TimelineConstants::sectionWidth);
+ const qreal duration = m_rulerItem->rulerDuration() + m_rulerItem->rulerDuration() * 0.1;
+ const qreal maxr = m_rulerItem->rulerScaling() * duration - w;
+ return std::round(qMax(maxr, 0.0));
+}
+
+void TransitionEditorGraphicsLayout::activate()
+{
+ m_layout->activate();
+}
+
+TimelineRulerSectionItem *TransitionEditorGraphicsLayout::ruler() const
+{
+ return m_rulerItem;
+}
+
+} // End namespace QmlDesigner.
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h
new file mode 100644
index 0000000000..9362abffdf
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicslayout.h
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineitem.h"
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+
+namespace QmlDesigner {
+
+class TimelineItem;
+class TimelineRulerSectionItem;
+class TimelinePlaceholder;
+
+class ModelNode;
+
+class TransitionEditorGraphicsLayout : public TimelineItem
+{
+ Q_OBJECT
+
+signals:
+ void rulerClicked(const QPointF &pos);
+
+ void scaleFactorChanged(int factor);
+
+public:
+ TransitionEditorGraphicsLayout(QGraphicsScene *scene, TimelineItem *parent = nullptr);
+
+ ~TransitionEditorGraphicsLayout() override;
+
+public:
+ double rulerWidth() const;
+
+ double rulerScaling() const;
+
+ double rulerDuration() const;
+
+ double endFrame() const;
+
+ void setWidth(int width);
+
+ void setTransition(const ModelNode &transition);
+
+ void setDuration(qreal duration);
+
+ void setRulerScaleFactor(int factor);
+
+ void invalidate();
+
+ int maximumScrollValue() const;
+
+ void activate();
+
+ TimelineRulerSectionItem *ruler() const;
+
+private:
+ QGraphicsLinearLayout *m_layout = nullptr;
+
+ TimelineRulerSectionItem *m_rulerItem = nullptr;
+
+ TimelinePlaceholder *m_placeholder1 = nullptr;
+
+ TimelinePlaceholder *m_placeholder2 = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp
new file mode 100644
index 0000000000..036fe173f5
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.cpp
@@ -0,0 +1,431 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorgraphicsscene.h"
+
+#include "transitioneditorgraphicslayout.h"
+#include "transitioneditorpropertyitem.h"
+#include "transitioneditorsectionitem.h"
+#include "transitioneditortoolbar.h"
+#include "transitioneditorview.h"
+#include "transitioneditorwidget.h"
+
+#include "timelineactions.h"
+#include "timelineitem.h"
+#include "timelinemovableabstractitem.h"
+#include "timelinemovetool.h"
+#include "timelineplaceholder.h"
+#include "timelinepropertyitem.h"
+#include "timelinesectionitem.h"
+
+#include <designdocumentview.h>
+#include <exception.h>
+#include <rewritertransaction.h>
+#include <rewriterview.h>
+#include <viewmanager.h>
+#include <qmldesignerplugin.h>
+#include <qmlobjectnode.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <bindingproperty.h>
+
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <utils/hostosinfo.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QGraphicsLinearLayout>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QKeyEvent>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+static int deleteKey()
+{
+ if (Utils::HostOsInfo::isMacHost())
+ return Qt::Key_Backspace;
+
+ return Qt::Key_Delete;
+}
+
+TransitionEditorGraphicsScene::TransitionEditorGraphicsScene(TransitionEditorWidget *parent)
+ : AbstractScrollGraphicsScene(parent)
+ , m_parent(parent)
+ , m_layout(new TransitionEditorGraphicsLayout(this))
+ , m_tools(this)
+{
+ addItem(m_layout);
+
+ setSceneRect(m_layout->geometry());
+
+ connect(m_layout, &QGraphicsWidget::geometryChanged, this, [this]() {
+ auto rect = m_layout->geometry();
+
+ setSceneRect(rect);
+
+ if (auto *gview = graphicsView())
+ gview->setSceneRect(rect.adjusted(0, TimelineConstants::rulerHeight, 0, 0));
+
+ if (auto *rview = rulerView())
+ rview->setSceneRect(rect);
+ });
+
+ auto changeScale = [this](int factor) {
+ transitionEditorWidget()->changeScaleFactor(factor);
+ setRulerScaling(qreal(factor));
+ };
+ connect(m_layout, &TransitionEditorGraphicsLayout::scaleFactorChanged, changeScale);
+}
+
+TransitionEditorGraphicsScene::~TransitionEditorGraphicsScene()
+{
+ QSignalBlocker block(this);
+ qDeleteAll(items());
+}
+
+void TransitionEditorGraphicsScene::invalidateScrollbar()
+{
+ double max = m_layout->maximumScrollValue();
+ transitionEditorWidget()->setupScrollbar(0, max, scrollOffset());
+ if (scrollOffset() > max)
+ setScrollOffset(max);
+}
+
+void TransitionEditorGraphicsScene::onShow()
+{
+ emit m_layout->scaleFactorChanged(0);
+}
+
+void TransitionEditorGraphicsScene::setTransition(const ModelNode &transition)
+{
+ clearSelection();
+ m_layout->setTransition(transition);
+}
+
+void TransitionEditorGraphicsScene::clearTransition()
+{
+ m_transition = {};
+ m_layout->setTransition({});
+}
+
+void TransitionEditorGraphicsScene::setWidth(int width)
+{
+ m_layout->setWidth(width);
+ invalidateScrollbar();
+}
+
+void TransitionEditorGraphicsScene::invalidateLayout()
+{
+ m_layout->invalidate();
+}
+
+void TransitionEditorGraphicsScene::setDuration(int duration)
+{
+ if (m_transition.isValid())
+ m_transition.setAuxiliaryData("transitionDuration", duration);
+ m_layout->setDuration(duration);
+ qreal scaling = m_layout->rulerScaling();
+ setRulerScaling(scaling);
+}
+
+qreal TransitionEditorGraphicsScene::rulerScaling() const
+{
+ return m_layout->rulerScaling();
+}
+
+int TransitionEditorGraphicsScene::rulerWidth() const
+{
+ return m_layout->rulerWidth();
+}
+
+qreal TransitionEditorGraphicsScene::rulerDuration() const
+{
+ return m_layout->rulerDuration();
+}
+
+qreal TransitionEditorGraphicsScene::endFrame() const
+{
+ return m_layout->endFrame();
+}
+
+qreal TransitionEditorGraphicsScene::startFrame() const
+{
+ return 0;
+}
+
+qreal TransitionEditorGraphicsScene::mapToScene(qreal x) const
+{
+ return TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ + (x - startFrame()) * rulerScaling() - scrollOffset();
+}
+
+qreal TransitionEditorGraphicsScene::mapFromScene(qreal x) const
+{
+ auto xPosOffset = (x - TimelineConstants::sectionWidth - TimelineConstants::timelineLeftOffset)
+ + scrollOffset();
+
+ return xPosOffset / rulerScaling() + startFrame();
+}
+
+void TransitionEditorGraphicsScene::setRulerScaling(int scaleFactor)
+{
+ m_layout->setRulerScaleFactor(scaleFactor);
+
+ setScrollOffset(0);
+ invalidateSections();
+ invalidateScrollbar();
+ update();
+}
+
+void TransitionEditorGraphicsScene::invalidateSectionForTarget(const ModelNode &target)
+{
+ if (!target.isValid())
+ return;
+
+ bool found = false;
+
+ const QList<QGraphicsItem *> items = m_layout->childItems();
+ for (auto child : items)
+ TimelineSectionItem::updateDataForTarget(child, target, &found);
+
+ if (!found)
+ invalidateScene();
+
+ clearSelection();
+ invalidateLayout();
+}
+
+void TransitionEditorGraphicsScene::invalidateScene()
+{
+ invalidateScrollbar();
+}
+
+void TransitionEditorGraphicsScene::invalidateCurrentValues()
+{
+ const QList<QGraphicsItem *> constItems = items();
+ for (auto item : constItems)
+ TimelinePropertyItem::updateTextEdit(item);
+}
+
+QGraphicsView *TransitionEditorGraphicsScene::graphicsView() const
+{
+ const QList<QGraphicsView *> constViews = views();
+ for (auto *v : constViews)
+ if (v->objectName() == "SceneView")
+ return v;
+
+ return nullptr;
+}
+
+QGraphicsView *TransitionEditorGraphicsScene::rulerView() const
+{
+ const QList<QGraphicsView *> constViews = views();
+ for (auto *v : constViews)
+ if (v->objectName() == "RulerView")
+ return v;
+
+ return nullptr;
+}
+
+QRectF TransitionEditorGraphicsScene::selectionBounds() const
+{
+ QRectF bbox;
+
+ return bbox;
+}
+
+void TransitionEditorGraphicsScene::clearSelection()
+{
+ if (m_selectedProperty)
+ m_selectedProperty->update();
+
+ m_selectedProperty = nullptr;
+ AbstractScrollGraphicsScene::clearSelection();
+}
+
+QList<QGraphicsItem *> TransitionEditorGraphicsScene::itemsAt(const QPointF &pos)
+{
+ QTransform transform;
+
+ if (auto *gview = graphicsView())
+ transform = gview->transform();
+
+ return items(pos, Qt::IntersectsItemShape, Qt::DescendingOrder, transform);
+}
+
+void TransitionEditorGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+
+ m_tools.mousePressEvent(topItem, event);
+ QGraphicsScene::mousePressEvent(event);
+}
+
+void TransitionEditorGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseMoveEvent(topItem, event);
+ QGraphicsScene::mouseMoveEvent(event);
+}
+
+void TransitionEditorGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ /* The tool has handle the event last. */
+ QGraphicsScene::mouseReleaseEvent(event);
+ m_tools.mouseReleaseEvent(topItem, event);
+}
+
+void TransitionEditorGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ auto topItem = TimelineMovableAbstractItem::topMoveableItem(itemsAt(event->scenePos()));
+ m_tools.mouseDoubleClickEvent(topItem, event);
+ QGraphicsScene::mouseDoubleClickEvent(event);
+}
+
+void TransitionEditorGraphicsScene::keyPressEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyPressEvent(keyEvent);
+ return;
+ }
+
+ if (keyEvent->modifiers().testFlag(Qt::ControlModifier)) {
+ QGraphicsScene::keyPressEvent(keyEvent);
+ } else {
+ switch (keyEvent->key()) {
+ case Qt::Key_Left:
+ emit scroll(TimelineUtils::Side::Left);
+ keyEvent->accept();
+ break;
+
+ case Qt::Key_Right:
+ emit scroll(TimelineUtils::Side::Right);
+ keyEvent->accept();
+ break;
+
+ default:
+ QGraphicsScene::keyPressEvent(keyEvent);
+ break;
+ }
+ }
+}
+
+void TransitionEditorGraphicsScene::keyReleaseEvent(QKeyEvent *keyEvent)
+{
+ if (qgraphicsitem_cast<QGraphicsProxyWidget *>(focusItem())) {
+ keyEvent->ignore();
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+ return;
+ }
+
+ QGraphicsScene::keyReleaseEvent(keyEvent);
+}
+
+void TransitionEditorGraphicsScene::invalidateSections()
+{
+ const QList<QGraphicsItem *> children = m_layout->childItems();
+ for (auto child : children)
+ TransitionEditorSectionItem::updateData(child);
+
+ clearSelection();
+ invalidateLayout();
+}
+
+TransitionEditorView *TransitionEditorGraphicsScene::transitionEditorView() const
+{
+ return m_parent->transitionEditorView();
+}
+
+TransitionEditorWidget *TransitionEditorGraphicsScene::transitionEditorWidget() const
+{
+ return m_parent;
+}
+
+TransitionEditorToolBar *TransitionEditorGraphicsScene::toolBar() const
+{
+ return transitionEditorWidget()->toolBar();
+}
+
+void TransitionEditorGraphicsScene::activateLayout()
+{
+ m_layout->activate();
+}
+
+AbstractView *TransitionEditorGraphicsScene::abstractView() const
+{
+ return transitionEditorView();
+}
+
+bool TransitionEditorGraphicsScene::event(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::ShortcutOverride:
+ if (static_cast<QKeyEvent *>(event)->key() == deleteKey()) {
+ QGraphicsScene::keyPressEvent(static_cast<QKeyEvent *>(event));
+ event->accept();
+ return true;
+ }
+ Q_FALLTHROUGH();
+ default:
+ return QGraphicsScene::event(event);
+ }
+}
+
+ModelNode TransitionEditorGraphicsScene::transitionModelNode() const
+{
+ if (transitionEditorView()->isAttached()) {
+ const QString timelineId = transitionEditorWidget()->toolBar()->currentTransitionId();
+ return transitionEditorView()->modelNodeForId(timelineId);
+ }
+
+ return ModelNode();
+}
+
+TransitionEditorPropertyItem *TransitionEditorGraphicsScene::selectedPropertyItem() const
+{
+ return m_selectedProperty;
+}
+
+void TransitionEditorGraphicsScene::setSelectedPropertyItem(TransitionEditorPropertyItem *item)
+{
+ if (m_selectedProperty)
+ m_selectedProperty->update();
+ m_selectedProperty = item;
+ emit selectionChanged();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h
new file mode 100644
index 0000000000..2f04c5b729
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorgraphicsscene.h
@@ -0,0 +1,146 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+
+#include <timelineeditor/timelinegraphicsscene.h>
+
+#include <qmltimeline.h>
+
+#include <QGraphicsScene>
+
+#include <memory>
+
+QT_FORWARD_DECLARE_CLASS(QGraphicsLinearLayout)
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+
+namespace QmlDesigner {
+
+class TransitionEditorView;
+class TransitionEditorWidget;
+class TransitionEditorToolBar;
+class TransitionEditorGraphicsLayout;
+
+class TimelineRulerSectionItem;
+class TimelineFrameHandle;
+class TimelineAbstractTool;
+class TimelineMoveTool;
+class TimelineKeyframeItem;
+class TimelinePlaceholder;
+class TimelineToolBar;
+class TransitionEditorPropertyItem;
+
+class TransitionEditorGraphicsScene : public AbstractScrollGraphicsScene
+{
+ Q_OBJECT
+
+signals:
+ void selectionChanged();
+
+ void scroll(const TimelineUtils::Side &side);
+
+public:
+ explicit TransitionEditorGraphicsScene(TransitionEditorWidget *parent);
+
+ ~TransitionEditorGraphicsScene() override;
+
+ void onShow();
+
+ void setTransition(const ModelNode &transition);
+ void clearTransition();
+
+ void setWidth(int width);
+
+ void invalidateLayout();
+ void setDuration(int duration);
+
+ TransitionEditorView *transitionEditorView() const;
+ TransitionEditorWidget *transitionEditorWidget() const;
+ TransitionEditorToolBar *toolBar() const;
+
+ qreal rulerScaling() const override;
+ int rulerWidth() const override;
+ qreal rulerDuration() const override;
+ qreal endFrame() const override;
+ qreal startFrame() const override;
+
+ qreal mapToScene(qreal x) const;
+ qreal mapFromScene(qreal x) const;
+
+ void setRulerScaling(int scaling);
+
+ void invalidateSectionForTarget(const ModelNode &modelNode);
+
+ void invalidateScene();
+ void invalidateCurrentValues();
+ void invalidateRecordButtonsStatus();
+
+ QGraphicsView *graphicsView() const;
+ QGraphicsView *rulerView() const;
+
+ QRectF selectionBounds() const;
+
+ void selectKeyframes(const SelectionMode &mode, const QList<TimelineKeyframeItem *> &items);
+ void clearSelection() override;
+
+ void activateLayout();
+
+ AbstractView *abstractView() const override;
+ ModelNode transitionModelNode() const;
+
+ TransitionEditorPropertyItem *selectedPropertyItem() const;
+ void setSelectedPropertyItem(TransitionEditorPropertyItem *item);
+
+ void invalidateScrollbar() override;
+
+signals:
+ void statusBarMessageChanged(const QString &message);
+
+protected:
+ bool event(QEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+
+ void keyPressEvent(QKeyEvent *keyEvent) override;
+ void keyReleaseEvent(QKeyEvent *keyEvent) override;
+
+private:
+ void invalidateSections();
+ QList<QGraphicsItem *> itemsAt(const QPointF &pos);
+
+private:
+ TransitionEditorWidget *m_parent = nullptr;
+ TransitionEditorGraphicsLayout *m_layout = nullptr;
+ ModelNode m_transition;
+
+ int m_scrollOffset = 0;
+ TimelineToolDelegate m_tools;
+ TransitionEditorPropertyItem *m_selectedProperty = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp
new file mode 100644
index 0000000000..f5b7b45b05
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.cpp
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorpropertyitem.h"
+
+#include "abstractview.h"
+#include "timelineconstants.h"
+#include "timelineicons.h"
+#include "transitioneditorgraphicsscene.h"
+
+#include <bindingproperty.h>
+#include <nodeabstractproperty.h>
+#include <rewritertransaction.h>
+#include <rewritingexception.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmlobjectnode.h>
+
+#include <coreplugin/icore.h>
+#include <utils/qtcassert.h>
+#include <utils/utilsicons.h>
+
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <coreplugin/icore.h>
+
+#include <QCursor>
+#include <QGraphicsProxyWidget>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QLineEdit>
+#include <QMenu>
+#include <QPainter>
+
+#include <algorithm>
+
+namespace QmlDesigner {
+
+TransitionEditorPropertyItem *TransitionEditorPropertyItem::create(
+ const ModelNode &animation, TransitionEditorSectionItem *parent)
+{
+ auto item = new TransitionEditorPropertyItem(parent);
+ item->m_animation = animation;
+
+ auto sectionItem = new QGraphicsWidget(item);
+
+ sectionItem->setGeometry(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight);
+
+ sectionItem->setZValue(10);
+ sectionItem->setCursor(Qt::ArrowCursor);
+
+ item->setToolTip(item->propertyName());
+ item->resize(parent->size());
+
+ item->m_barItem = new TransitionEditorBarItem(item);
+ item->invalidateBar();
+
+ return item;
+}
+
+int TransitionEditorPropertyItem::type() const
+{
+ return Type;
+}
+
+void TransitionEditorPropertyItem::updateData()
+{
+ invalidateBar();
+}
+
+void TransitionEditorPropertyItem::updateParentData()
+{
+ TransitionEditorSectionItem::invalidateBar(parentItem());
+}
+
+bool TransitionEditorPropertyItem::isSelected() const
+{
+ return transitionEditorGraphicsScene()->selectedPropertyItem() == this;
+}
+
+QString TransitionEditorPropertyItem::propertyName() const
+{
+ if (m_animation.isValid()) {
+ const QString propertyName = m_animation.variantProperty("property").value().toString();
+ if (!propertyName.isEmpty())
+ return propertyName;
+ return m_animation.variantProperty("properties").value().toString();
+ }
+ return QString();
+}
+
+void TransitionEditorPropertyItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *,
+ QWidget *)
+{
+ painter->save();
+
+ static const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ static const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ static const QColor backgroundColor = Theme::instance()
+ ->qmlDesignerBackgroundColorDarkAlternate();
+
+ painter->fillRect(0, 0, TimelineConstants::sectionWidth, size().height(), backgroundColor);
+ painter->fillRect(TimelineConstants::textIndentationProperties - 4,
+ 0,
+ TimelineConstants::sectionWidth - TimelineConstants::textIndentationProperties
+ + 4,
+ size().height(),
+ backgroundColor.darker(110));
+
+ painter->setPen(penColor);
+
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height());
+
+ drawLine(painter,
+ TimelineConstants::textIndentationProperties - 4,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ painter->setPen(textColor);
+
+ const QFontMetrics metrics(font());
+
+ const QString elidedText = metrics.elidedText(propertyName(),
+ Qt::ElideMiddle,
+ qreal(TimelineConstants::sectionWidth) * 2.0 / 3
+ - TimelineConstants::textIndentationProperties,
+ 0);
+
+ painter->drawText(TimelineConstants::textIndentationProperties, 12, elidedText);
+
+ painter->restore();
+}
+
+void TransitionEditorPropertyItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event */) {}
+
+TransitionEditorPropertyItem::TransitionEditorPropertyItem(TransitionEditorSectionItem *parent)
+ : TimelineItem(parent)
+{
+ setPreferredHeight(TimelineConstants::sectionHeight);
+ setMinimumHeight(TimelineConstants::sectionHeight);
+ setMaximumHeight(TimelineConstants::sectionHeight);
+}
+
+TransitionEditorGraphicsScene *TransitionEditorPropertyItem::transitionEditorGraphicsScene() const
+{
+ return qobject_cast<TransitionEditorGraphicsScene *>(scene());
+}
+
+void TransitionEditorPropertyItem::invalidateBar()
+{
+ qreal min = 0;
+ qreal max = 0;
+
+ QTC_ASSERT(m_animation.isValid(), return );
+ QTC_ASSERT(m_animation.hasParentProperty(), return );
+
+ const ModelNode parent = m_animation.parentProperty().parentModelNode();
+
+ for (const ModelNode &child : parent.directSubModelNodes())
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ min = child.variantProperty("duration").value().toDouble();
+
+ max = m_animation.variantProperty("duration").value().toDouble() + min;
+
+ const qreal sceneMin = m_barItem->mapFromFrameToScene(min);
+
+ QRectF barRect(sceneMin,
+ 0,
+ (max - min) * m_barItem->rulerScaling(),
+ TimelineConstants::sectionHeight - 1);
+
+ m_barItem->setRect(barRect);
+}
+
+AbstractView *TransitionEditorPropertyItem::view() const
+{
+ return m_animation.view();
+}
+
+ModelNode TransitionEditorPropertyItem::propertyAnimation() const
+{
+ return m_animation;
+}
+
+ModelNode TransitionEditorPropertyItem::pauseAnimation() const
+{
+ QTC_ASSERT(m_animation.isValid(), return {});
+ QTC_ASSERT(m_animation.hasParentProperty(), return {});
+
+ const ModelNode parent = m_animation.parentProperty().parentModelNode();
+
+ for (const ModelNode &child : parent.directSubModelNodes())
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ return child;
+
+ return {};
+}
+
+void TransitionEditorPropertyItem::select()
+{
+ transitionEditorGraphicsScene()->setSelectedPropertyItem(this);
+ m_barItem->update();
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h
new file mode 100644
index 0000000000..aba42b599d
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorpropertyitem.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "transitioneditorsectionitem.h"
+
+#include <modelnode.h>
+
+#include <QGraphicsRectItem>
+
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+
+namespace QmlDesigner {
+
+class TransitionEditorGraphicsScene;
+
+class TransitionEditorPropertyItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TransitionEditorConstants::transitionEditorPropertyItemUserType };
+
+ static TransitionEditorPropertyItem *create(const ModelNode &animation,
+ TransitionEditorSectionItem *parent = nullptr);
+ int type() const override;
+ void updateData();
+ void updateParentData();
+
+ bool isSelected() const;
+ QString propertyName() const;
+ void invalidateBar();
+ AbstractView *view() const;
+ ModelNode propertyAnimation() const;
+ ModelNode pauseAnimation() const;
+ void select();
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ TransitionEditorPropertyItem(TransitionEditorSectionItem *parent = nullptr);
+ TransitionEditorGraphicsScene *transitionEditorGraphicsScene() const;
+
+ ModelNode m_animation;
+ TransitionEditorBarItem *m_barItem;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp
new file mode 100644
index 0000000000..86442059d9
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.cpp
@@ -0,0 +1,803 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorsectionitem.h"
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorpropertyitem.h"
+
+#include "timelineactions.h"
+#include "timelineconstants.h"
+#include "timelineicons.h"
+#include "timelinepropertyitem.h"
+#include "timelineutils.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <variantproperty.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <rewritingexception.h>
+
+#include <theme.h>
+
+#include <utils/qtcassert.h>
+
+#include <QAction>
+#include <QApplication>
+#include <QColorDialog>
+#include <QGraphicsScene>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QMenu>
+#include <QPainter>
+#include <QPainterPath>
+
+#include <QGraphicsView>
+
+#include <QDebug>
+
+#include <cmath>
+#include <limits>
+
+namespace QmlDesigner {
+
+static void scaleDuration(const ModelNode &node, qreal s)
+{
+ if (node.hasVariantProperty("duration")) {
+ qreal old = node.variantProperty("duration").value().toDouble();
+ node.variantProperty("duration").setValue(qRound(old * s));
+ }
+}
+
+static void moveDuration(const ModelNode &node, qreal s)
+{
+ if (node.hasVariantProperty("duration")) {
+ qreal old = node.variantProperty("duration").value().toDouble();
+ node.variantProperty("duration").setValue(old + s);
+ }
+}
+
+class ClickDummy : public TimelineItem
+{
+public:
+ explicit ClickDummy(TransitionEditorSectionItem *parent)
+ : TimelineItem(parent)
+ {
+ setGeometry(0, 0, TimelineConstants::sectionWidth, TimelineConstants::sectionHeight);
+
+ setZValue(10);
+ setCursor(Qt::ArrowCursor);
+ }
+
+protected:
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override
+ {
+ scene()->sendEvent(parentItem(), event);
+ }
+};
+
+TransitionEditorSectionItem::TransitionEditorSectionItem(TimelineItem *parent)
+ : TimelineItem(parent)
+{}
+
+TransitionEditorSectionItem *TransitionEditorSectionItem::create(const ModelNode &animation,
+ TimelineItem *parent)
+{
+ auto item = new TransitionEditorSectionItem(parent);
+
+ ModelNode target;
+
+ if (animation.isValid()) {
+ const QList<ModelNode> propertyAnimations = animation.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+
+ for (const ModelNode &child : propertyAnimations) {
+ if (child.hasBindingProperty("target"))
+ target = child.bindingProperty("target").resolveToModelNode();
+ }
+ }
+
+ item->m_targetNode = target;
+ item->m_animationNode = animation;
+ item->createPropertyItems();
+
+ if (target.isValid())
+ item->setToolTip(target.id());
+
+ item->m_dummyItem = new ClickDummy(item);
+ item->m_dummyItem->update();
+
+ item->m_barItem = new TransitionEditorBarItem(item);
+ item->invalidateBar();
+ item->invalidateHeight();
+
+ return item;
+}
+
+void TransitionEditorSectionItem::invalidateBar()
+{
+ qreal min = std::numeric_limits<qreal>::max();
+ qreal max = 0;
+
+ if (!m_animationNode.isValid())
+ return;
+
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ qreal locMin = 0;
+ qreal locMax = 0;
+
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation"))
+ locMax = child.variantProperty("duration").value().toDouble();
+ else if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ locMin = child.variantProperty("duration").value().toDouble();
+ }
+
+ locMax = locMax + locMin;
+
+ min = qMin(min, locMin);
+ max = qMax(max, locMax);
+ }
+
+ const qreal sceneMin = m_barItem->mapFromFrameToScene(min);
+
+ QRectF barRect(sceneMin,
+ 0,
+ (max - min) * m_barItem->rulerScaling(),
+ TimelineConstants::sectionHeight - 1);
+
+ m_barItem->setRect(barRect);
+}
+
+int TransitionEditorSectionItem::type() const
+{
+ return Type;
+}
+
+void TransitionEditorSectionItem::updateData(QGraphicsItem *item)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item))
+ sectionItem->updateData();
+}
+
+void TransitionEditorSectionItem::invalidateBar(QGraphicsItem *item)
+{
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item))
+ sectionItem->invalidateBar();
+}
+
+void TransitionEditorSectionItem::updateDataForTarget(QGraphicsItem *item,
+ const ModelNode &target,
+ bool *b)
+{
+ if (!target.isValid())
+ return;
+
+ if (auto sectionItem = qgraphicsitem_cast<TransitionEditorSectionItem *>(item)) {
+ if (sectionItem->m_targetNode == target) { //TODO update animation node
+ sectionItem->updateData();
+ if (b)
+ *b = true;
+ }
+ }
+}
+
+void TransitionEditorSectionItem::moveAllDurations(qreal offset)
+{
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PauseAnimation"))
+ moveDuration(child, offset);
+ }
+ }
+}
+
+void TransitionEditorSectionItem::scaleAllDurations(qreal scale)
+{
+ for (const ModelNode &sequential : m_animationNode.directSubModelNodes()) {
+ for (const ModelNode &child : sequential.directSubModelNodes()) {
+ if (child.hasMetaInfo() && child.isSubclassOf("QtQuick.PropertyAnimation"))
+ scaleDuration(child, scale);
+ }
+ }
+}
+
+qreal TransitionEditorSectionItem::firstFrame()
+{
+ return 0;
+ //if (!m_timeline.isValid())
+ //return 0;
+
+ //return m_timeline.minActualKeyframe(m_targetNode);
+}
+
+AbstractView *TransitionEditorSectionItem::view() const
+{
+ return m_animationNode.view();
+}
+
+bool TransitionEditorSectionItem::isSelected() const
+{
+ return m_targetNode.isValid() && m_targetNode.isSelected();
+}
+
+ModelNode TransitionEditorSectionItem::targetNode() const
+{
+ return m_targetNode;
+}
+
+static QPixmap rotateby90(const QPixmap &pixmap)
+{
+ QImage sourceImage = pixmap.toImage();
+ QImage destImage(pixmap.height(), pixmap.width(), sourceImage.format());
+
+ for (int x = 0; x < pixmap.width(); x++)
+ for (int y = 0; y < pixmap.height(); y++)
+ destImage.setPixel(y, x, sourceImage.pixel(x, y));
+
+ QPixmap result = QPixmap::fromImage(destImage);
+
+ result.setDevicePixelRatio(pixmap.devicePixelRatio());
+
+ return result;
+}
+
+static int devicePixelHeight(const QPixmap &pixmap)
+{
+ return pixmap.height() / pixmap.devicePixelRatioF();
+}
+
+void TransitionEditorSectionItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem * /*option*/,
+ QWidget *)
+{
+ if (m_targetNode.isValid()) {
+ painter->save();
+
+ const QColor textColor = Theme::getColor(Theme::PanelTextColorLight);
+ const QColor penColor = Theme::instance()->qmlDesignerBackgroundColorDarker();
+ QColor brushColor = Theme::getColor(Theme::BackgroundColorDark);
+
+ int fillOffset = 0;
+ if (isSelected()) {
+ brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ fillOffset = 1;
+ }
+
+ painter->fillRect(0,
+ 0,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - fillOffset,
+ brushColor);
+ painter->fillRect(TimelineConstants::sectionWidth,
+ 0,
+ size().width() - TimelineConstants::sectionWidth,
+ size().height(),
+ Theme::instance()->qmlDesignerBackgroundColorDarkAlternate());
+
+ painter->setPen(penColor);
+ drawLine(painter,
+ TimelineConstants::sectionWidth - 1,
+ 0,
+ TimelineConstants::sectionWidth - 1,
+ size().height() - 1);
+ drawLine(painter,
+ TimelineConstants::sectionWidth,
+ TimelineConstants::sectionHeight - 1,
+ size().width(),
+ TimelineConstants::sectionHeight - 1);
+
+ static const QPixmap arrow = Theme::getPixmap("down-arrow");
+
+ static const QPixmap arrow90 = rotateby90(arrow);
+
+ const QPixmap rotatedArrow = collapsed() ? arrow90 : arrow;
+
+ const int textOffset = QFontMetrics(font()).ascent()
+ + (TimelineConstants::sectionHeight - QFontMetrics(font()).height())
+ / 2;
+
+ painter->drawPixmap(collapsed() ? 6 : 4,
+ (TimelineConstants::sectionHeight - devicePixelHeight(rotatedArrow)) / 2,
+ rotatedArrow);
+
+ painter->setPen(textColor);
+
+ QFontMetrics fm(painter->font());
+ const QString elidedId = fm.elidedText(m_targetNode.id(),
+ Qt::ElideMiddle,
+ TimelineConstants::sectionWidth
+ - TimelineConstants::textIndentationSections);
+ painter->drawText(TimelineConstants::textIndentationSections, textOffset, elidedId);
+
+ painter->restore();
+ }
+}
+
+void TransitionEditorSectionItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight
+ || event->pos().x() < TimelineConstants::textIndentationSections) {
+ TimelineItem::mouseDoubleClickEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton) {
+ event->accept();
+ toggleCollapsed();
+ }
+}
+
+void TransitionEditorSectionItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mousePressEvent(event);
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton)
+ event->accept();
+}
+
+void TransitionEditorSectionItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ if (event->pos().y() > TimelineConstants::sectionHeight) {
+ TimelineItem::mouseReleaseEvent(event);
+ return;
+ }
+
+ if (event->button() != Qt::LeftButton)
+ return;
+
+ event->accept();
+
+ if (event->pos().x() > TimelineConstants::textIndentationSections
+ && event->button() == Qt::LeftButton) {
+ if (m_targetNode.isValid())
+ m_targetNode.view()->setSelectedModelNode(m_targetNode);
+ } else {
+ toggleCollapsed();
+ }
+ update();
+}
+
+void TransitionEditorSectionItem::resizeEvent(QGraphicsSceneResizeEvent *event)
+{
+ TimelineItem::resizeEvent(event);
+
+ for (auto child : propertyItems()) {
+ TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child);
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+}
+
+void TransitionEditorSectionItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *) {}
+
+void TransitionEditorSectionItem::updateData()
+{
+ invalidateBar();
+ resize(rulerWidth(), size().height());
+ invalidateProperties();
+ update();
+}
+
+const QList<QGraphicsItem *> TransitionEditorSectionItem::propertyItems() const
+{
+ QList<QGraphicsItem *> list;
+
+ const QList<QGraphicsItem *> children = childItems();
+ for (auto child : children) {
+ if (m_barItem != child && m_dummyItem != child)
+ list.append(child);
+ }
+
+ return list;
+}
+
+void TransitionEditorSectionItem::invalidateHeight()
+{
+ int height = 0;
+ bool visible = true;
+
+ if (collapsed()) {
+ height = TimelineConstants::sectionHeight;
+ visible = false;
+ } else {
+ const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+
+ height = TimelineConstants::sectionHeight
+ + propertyAnimations.count() * TimelineConstants::sectionHeight;
+ visible = true;
+ }
+
+ for (auto child : propertyItems())
+ child->setVisible(visible);
+
+ setPreferredHeight(height);
+ setMinimumHeight(height);
+ setMaximumHeight(height);
+
+ auto transitionScene = qobject_cast<TransitionEditorGraphicsScene *>(scene());
+ transitionScene->activateLayout();
+}
+
+void TransitionEditorSectionItem::createPropertyItems()
+{
+ int yPos = TimelineConstants::sectionHeight;
+ const QList<ModelNode> propertyAnimations = m_animationNode.subModelNodesOfType(
+ "QtQuick.PropertyAnimation");
+ for (const auto &anim : propertyAnimations) {
+ auto item = TransitionEditorPropertyItem::create(anim, this);
+ item->setY(yPos);
+ yPos = yPos + TimelineConstants::sectionHeight;
+ }
+}
+
+void TransitionEditorSectionItem::invalidateProperties()
+{
+ for (auto child : propertyItems()) {
+ delete child;
+ }
+
+ createPropertyItems();
+
+ for (auto child : propertyItems()) {
+ TransitionEditorPropertyItem *item = static_cast<TransitionEditorPropertyItem *>(child);
+ item->updateData();
+ item->resize(size().width(), TimelineConstants::sectionHeight);
+ }
+ invalidateHeight();
+}
+
+bool TransitionEditorSectionItem::collapsed() const
+{
+ return m_targetNode.isValid() && !m_targetNode.hasAuxiliaryData("timeline_expanded");
+}
+
+qreal TransitionEditorSectionItem::rulerWidth() const
+{
+ return static_cast<TimelineGraphicsScene *>(scene())->rulerWidth();
+}
+
+void TransitionEditorSectionItem::toggleCollapsed()
+{
+ QTC_ASSERT(m_targetNode.isValid(), return );
+
+ if (collapsed())
+ m_targetNode.setAuxiliaryData("timeline_expanded", true);
+ else
+ m_targetNode.removeAuxiliaryData("timeline_expanded");
+
+ invalidateHeight();
+}
+
+TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorSectionItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ setAcceptHoverEvents(true);
+ setPen(Qt::NoPen);
+}
+
+TransitionEditorBarItem::TransitionEditorBarItem(TransitionEditorPropertyItem *parent)
+ : TimelineMovableAbstractItem(parent)
+{
+ setAcceptHoverEvents(true);
+ setPen(Qt::NoPen);
+}
+
+void TransitionEditorBarItem::itemMoved(const QPointF &start, const QPointF &end)
+{
+ if (isActiveHandle(Location::Undefined))
+ dragInit(rect(), start);
+
+ qreal min = qreal(TimelineConstants::sectionWidth + TimelineConstants::timelineLeftOffset
+ - scrollOffset());
+ qreal max = qreal(abstractScrollGraphicsScene()->rulerWidth() - TimelineConstants::sectionWidth
+ + rect().width());
+
+ const qreal minFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->startFrame());
+ const qreal maxFrameX = mapFromFrameToScene(abstractScrollGraphicsScene()->endFrame());
+
+ if (min < minFrameX)
+ min = minFrameX;
+
+ if (max > maxFrameX)
+ max = maxFrameX;
+
+ if (isActiveHandle(Location::Center))
+ dragCenter(rect(), end, min, max);
+ else
+ dragHandle(rect(), end, min, max);
+
+ emit abstractScrollGraphicsScene()->statusBarMessageChanged(
+ tr("Range from %1 to %2")
+ .arg(qRound(mapFromSceneToFrame(rect().x())))
+ .arg(qRound(mapFromSceneToFrame(rect().width() + rect().x()))));
+}
+
+void TransitionEditorBarItem::commitPosition(const QPointF & /*point*/)
+{
+ if (sectionItem() && sectionItem()->view()) {
+ if (m_handle != Location::Undefined) {
+ sectionItem()
+ ->view()
+ ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() {
+ qreal scaleFactor = rect().width() / m_oldRect.width();
+
+ qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling();
+ qreal supposedFirstFrame = qRound(moved);
+
+ sectionItem()->scaleAllDurations(scaleFactor);
+ sectionItem()->moveAllDurations(supposedFirstFrame);
+ sectionItem()->updateData();
+ });
+ }
+ } else if (propertyItem() && propertyItem()->view()) {
+ if (m_handle != Location::Undefined) {
+ propertyItem()
+ ->view()
+ ->executeInTransaction("TransitionEditorBarItem::commitPosition", [this]() {
+ qreal scaleFactor = rect().width() / m_oldRect.width();
+ qreal moved = (rect().topLeft().x() - m_oldRect.topLeft().x()) / rulerScaling();
+ qreal supposedFirstFrame = qRound(moved);
+ scaleDuration(propertyItem()->propertyAnimation(), scaleFactor);
+ moveDuration(propertyItem()->pauseAnimation(), supposedFirstFrame);
+ propertyItem()->updateData();
+ propertyItem()->updateParentData();
+ });
+ }
+ }
+
+ m_handle = Location::Undefined;
+ m_bounds = Location::Undefined;
+ m_pivot = 0.0;
+ m_oldRect = QRectF();
+ scrollOffsetChanged();
+}
+
+void TransitionEditorBarItem::scrollOffsetChanged()
+{
+ if (sectionItem())
+ sectionItem()->invalidateBar();
+ else if (propertyItem())
+ propertyItem()->invalidateBar();
+}
+
+void TransitionEditorBarItem::paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget)
+{
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+
+ QColor brushColor = Theme::getColor(Theme::QmlDesigner_HighlightColor);
+ QColor brushColorSection = Theme::getColor(Theme::QmlDesigner_HighlightColor).darker(120);
+ QColor penColor = Theme::getColor(Theme::QmlDesigner_HighlightColor).lighter(140);
+
+ const QRectF itemRect = rect();
+
+ painter->save();
+ painter->setClipRect(TimelineConstants::sectionWidth,
+ 0,
+ itemRect.width() + itemRect.x(),
+ itemRect.height());
+
+ if (sectionItem())
+ painter->fillRect(itemRect, brushColorSection);
+ else
+ painter->fillRect(itemRect, brushColor);
+
+ if (propertyItem() && propertyItem()->isSelected()) {
+ painter->setPen(penColor);
+ painter->drawRect(itemRect.adjusted(0, 0, 0, -1));
+ }
+
+ painter->restore();
+}
+
+void TransitionEditorBarItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
+{
+ const auto p = event->pos();
+
+ QRectF left, right;
+ if (handleRects(rect(), left, right)) {
+ if (left.contains(p) || right.contains(p)) {
+ if (cursor().shape() != Qt::SizeHorCursor)
+ setCursor(QCursor(Qt::SizeHorCursor));
+ } else if (rect().contains(p)) {
+ if (cursor().shape() != Qt::ClosedHandCursor)
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+ } else {
+ if (rect().contains(p))
+ setCursor(QCursor(Qt::ClosedHandCursor));
+ }
+}
+
+void TransitionEditorBarItem::contextMenuEvent(QGraphicsSceneContextMenuEvent * /*event*/) {}
+
+void TransitionEditorBarItem::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/)
+{
+ if (propertyItem())
+ propertyItem()->select();
+}
+
+TransitionEditorSectionItem *TransitionEditorBarItem::sectionItem() const
+{
+ return qgraphicsitem_cast<TransitionEditorSectionItem *>(parentItem());
+}
+
+TransitionEditorPropertyItem *TransitionEditorBarItem::propertyItem() const
+{
+ return qgraphicsitem_cast<TransitionEditorPropertyItem *>(parentItem());
+}
+
+void TransitionEditorBarItem::dragInit(const QRectF &rect, const QPointF &pos)
+{
+ QRectF left, right;
+ m_oldRect = rect;
+ if (handleRects(rect, left, right)) {
+ if (left.contains(pos)) {
+ m_handle = Location::Left;
+ m_pivot = pos.x() - left.topLeft().x();
+ } else if (right.contains(pos)) {
+ m_handle = Location::Right;
+ m_pivot = pos.x() - right.topRight().x();
+ } else if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+
+ } else {
+ if (rect.contains(pos)) {
+ m_handle = Location::Center;
+ m_pivot = pos.x() - rect.topLeft().x();
+ }
+ }
+}
+
+void TransitionEditorBarItem::dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ if (validateBounds(pos.x() - rect.topLeft().x())) {
+ qreal targetX = pos.x() - m_pivot;
+
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.moveLeft(targetX);
+ if (rect.topLeft().x() < min) {
+ rect.moveLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.topRight().x() > max) {
+ rect.moveRight(max);
+ setOutOfBounds(Location::Right);
+ }
+ setRect(rect);
+ }
+}
+
+void TransitionEditorBarItem::dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max)
+{
+ QRectF left, right;
+ handleRects(rect, left, right);
+
+ if (isActiveHandle(Location::Left)) {
+ if (validateBounds(pos.x() - left.topLeft().x())) {
+ qreal targetX = pos.x() - m_pivot;
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.setLeft(targetX);
+ if (rect.left() < min) {
+ rect.setLeft(min);
+ setOutOfBounds(Location::Left);
+ } else if (rect.left() >= rect.right() - minimumBarWidth)
+ rect.setLeft(rect.right() - minimumBarWidth);
+
+ setRect(rect);
+ }
+ } else if (isActiveHandle(Location::Right)) {
+ if (validateBounds(pos.x() - right.topRight().x())) {
+ qreal targetX = pos.x() - m_pivot;
+ if (QApplication::keyboardModifiers() & Qt::ShiftModifier) { // snapping
+ qreal snappedTargetFrame = abstractScrollGraphicsScene()->snap(mapFromSceneToFrame(targetX));
+ targetX = mapFromFrameToScene(snappedTargetFrame);
+ }
+ rect.setRight(targetX);
+ if (rect.right() > max) {
+ rect.setRight(max);
+ setOutOfBounds(Location::Right);
+ } else if (rect.right() <= rect.left() + minimumBarWidth)
+ rect.setRight(rect.left() + minimumBarWidth);
+
+ setRect(rect);
+ }
+ }
+}
+
+bool TransitionEditorBarItem::handleRects(const QRectF &rect, QRectF &left, QRectF &right) const
+{
+ if (rect.width() < minimumBarWidth)
+ return false;
+
+ const qreal handleSize = rect.height();
+
+ auto handleRect = QRectF(0, 0, handleSize, handleSize);
+ handleRect.moveCenter(rect.center());
+
+ handleRect.moveLeft(rect.left());
+ left = handleRect;
+
+ handleRect.moveRight(rect.right());
+ right = handleRect;
+
+ return true;
+}
+
+bool TransitionEditorBarItem::isActiveHandle(Location location) const
+{
+ return m_handle == location;
+}
+
+void TransitionEditorBarItem::setOutOfBounds(Location location)
+{
+ m_bounds = location;
+ update();
+}
+
+bool TransitionEditorBarItem::validateBounds(qreal distance)
+{
+ update();
+ if (m_bounds == Location::Left) {
+ if (distance > m_pivot)
+ m_bounds = Location::Center;
+ return false;
+
+ } else if (m_bounds == Location::Right) {
+ if (distance < m_pivot)
+ m_bounds = Location::Center;
+ return false;
+ }
+ return true;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h
new file mode 100644
index 0000000000..22aa21dd28
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsectionitem.h
@@ -0,0 +1,145 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "transitioneditorconstants.h"
+
+#include <timelineeditor/timelineitem.h>
+#include <timelineeditor/timelinemovableabstractitem.h>
+
+#include <modelnode.h>
+#include <qmltimeline.h>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QPainter)
+
+namespace QmlDesigner {
+
+class TransitionEditorSectionItem;
+class TransitionEditorPropertyItem;
+
+class TransitionEditorBarItem : public TimelineMovableAbstractItem
+{
+ Q_DECLARE_TR_FUNCTIONS(TimelineBarItem)
+
+ enum class Location { Undefined, Center, Left, Right };
+
+public:
+ explicit TransitionEditorBarItem(TransitionEditorSectionItem *parent);
+ explicit TransitionEditorBarItem(TransitionEditorPropertyItem *parent);
+
+ void itemMoved(const QPointF &start, const QPointF &end) override;
+ void commitPosition(const QPointF &point) override;
+
+protected:
+ void scrollOffsetChanged() override;
+ void paint(QPainter *painter,
+ const QStyleOptionGraphicsItem *option,
+ QWidget *widget = nullptr) override;
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+
+private:
+ TransitionEditorSectionItem *sectionItem() const;
+ TransitionEditorPropertyItem *propertyItem() const;
+
+ void dragInit(const QRectF &rect, const QPointF &pos);
+ void dragCenter(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ void dragHandle(QRectF rect, const QPointF &pos, qreal min, qreal max);
+ bool handleRects(const QRectF &rect, QRectF &left, QRectF &right) const;
+ bool isActiveHandle(Location location) const;
+
+ void setOutOfBounds(Location location);
+ bool validateBounds(qreal pivot);
+
+private:
+ Location m_handle = Location::Undefined;
+
+ Location m_bounds = Location::Undefined;
+
+ qreal m_pivot = 0.0;
+
+ QRectF m_oldRect;
+
+ static constexpr qreal minimumBarWidth = 2.0
+ * static_cast<qreal>(TimelineConstants::sectionHeight);
+};
+
+class TransitionEditorSectionItem : public TimelineItem
+{
+ Q_OBJECT
+
+public:
+ enum { Type = TransitionEditorConstants::transitionEditorSectionItemUserType };
+
+ static TransitionEditorSectionItem *create(const ModelNode &animation,
+ TimelineItem *parent);
+
+ void invalidateBar();
+
+ int type() const override;
+
+ static void updateData(QGraphicsItem *item);
+ static void invalidateBar(QGraphicsItem *item);
+ static void updateDataForTarget(QGraphicsItem *item, const ModelNode &target, bool *b);
+
+ void moveAllDurations(qreal offset);
+ void scaleAllDurations(qreal scale);
+ qreal firstFrame();
+ AbstractView *view() const;
+ bool isSelected() const;
+
+ ModelNode targetNode() const;
+ void updateData();
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
+ void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
+ void resizeEvent(QGraphicsSceneResizeEvent *event) override;
+ void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
+
+private:
+ void invalidateHeight();
+ void invalidateProperties();
+ bool collapsed() const;
+ qreal rulerWidth() const;
+ void toggleCollapsed();
+ void createPropertyItems();
+ const QList<QGraphicsItem *> propertyItems() const;
+
+ TransitionEditorSectionItem(TimelineItem *parent = nullptr);
+ ModelNode m_targetNode;
+ ModelNode m_animationNode;
+
+ TransitionEditorBarItem *m_barItem;
+ TimelineItem *m_dummyItem;
+
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp
new file mode 100644
index 0000000000..57993d7ac2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.cpp
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorsettingsdialog.h"
+#include "timelinesettingsdialog.h"
+#include "transitioneditorview.h"
+#include "ui_transitioneditorsettingsdialog.h"
+
+#include "timelineicons.h"
+#include "timelinesettingsmodel.h"
+#include "transitionform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QKeyEvent>
+#include <QToolBar>
+
+namespace QmlDesigner {
+
+static void deleteAllTabs(QTabWidget *tabWidget)
+{
+ while (tabWidget->count() > 0) {
+ QWidget *w = tabWidget->widget(0);
+ tabWidget->removeTab(0);
+ delete w;
+ }
+}
+
+static ModelNode getTransitionFromTabWidget(QTabWidget *tabWidget)
+{
+ QWidget *w = tabWidget->currentWidget();
+ if (w)
+ return qobject_cast<TransitionForm *>(w)->transition();
+ return QmlTimeline();
+}
+
+static void setTabForTransition(QTabWidget *tabWidget, const ModelNode &timeline)
+{
+ for (int i = 0; i < tabWidget->count(); ++i) {
+ QWidget *w = tabWidget->widget(i);
+ if (qobject_cast<TransitionForm *>(w)->transition() == timeline) {
+ tabWidget->setCurrentIndex(i);
+ return;
+ }
+ }
+}
+
+TransitionEditorSettingsDialog::TransitionEditorSettingsDialog(QWidget *parent,
+ class TransitionEditorView *view)
+ : QDialog(parent)
+ , ui(new Ui::TransitionEditorSettingsDialog)
+ , m_transitionEditorView(view)
+{
+ //m_timelineSettingsModel = new TimelineSettingsModel(this, view);
+
+ ui->setupUi(this);
+
+ auto *transitionCornerWidget = new QToolBar;
+
+ auto *transitionAddAction = new QAction(TimelineIcons::ADD_TIMELINE.icon(),
+ tr("Add Transition"));
+ auto *transitionRemoveAction = new QAction(TimelineIcons::REMOVE_TIMELINE.icon(),
+ tr("Remove Transition"));
+
+ connect(transitionAddAction, &QAction::triggered, this, [this]() {
+ setupTransitions(m_transitionEditorView->addNewTransition());
+ });
+
+ connect(transitionRemoveAction, &QAction::triggered, this, [this]() {
+ ModelNode transition = getTransitionFromTabWidget(ui->timelineTab);
+ if (transition.isValid()) {
+ transition.destroy();
+ setupTransitions({});
+ }
+ });
+
+ transitionCornerWidget->addAction(transitionAddAction);
+ transitionCornerWidget->addAction(transitionRemoveAction);
+
+ ui->timelineTab->setCornerWidget(transitionCornerWidget, Qt::TopRightCorner);
+
+ setupTransitions({});
+
+ connect(ui->timelineTab, &QTabWidget::currentChanged, this, [this]() {
+ m_currentTransition = getTransitionFromTabWidget(ui->timelineTab);
+ });
+}
+
+void TransitionEditorSettingsDialog::setCurrentTransition(const ModelNode &timeline)
+{
+ m_currentTransition = timeline;
+ setTabForTransition(ui->timelineTab, m_currentTransition);
+}
+
+TransitionEditorSettingsDialog::~TransitionEditorSettingsDialog()
+{
+ delete ui;
+}
+
+void TransitionEditorSettingsDialog::keyPressEvent(QKeyEvent *event)
+{
+ switch (event->key()) {
+ case Qt::Key_Return:
+ case Qt::Key_Enter:
+ /* ignore */
+ break;
+
+ default:
+ QDialog::keyPressEvent(event);
+ }
+}
+
+void TransitionEditorSettingsDialog::setupTransitions(const ModelNode &newTransition)
+{
+ deleteAllTabs(ui->timelineTab);
+
+ const QList<ModelNode> &transitions = m_transitionEditorView->allTransitions();
+
+ if (transitions.isEmpty()) {
+ m_currentTransition = {};
+ auto transitionForm = new TransitionForm(this);
+ transitionForm->setDisabled(true);
+ ui->timelineTab->addTab(transitionForm, tr("No Transition"));
+ return;
+ }
+
+ for (const auto &transition : transitions)
+ addTransitionTab(transition);
+
+ if (newTransition.isValid()) {
+ m_currentTransition = newTransition;
+ } else {
+ m_currentTransition = transitions.constFirst();
+ }
+
+ setTabForTransition(ui->timelineTab, m_currentTransition);
+}
+
+void TransitionEditorSettingsDialog::addTransitionTab(const QmlTimeline &node)
+{
+ auto transitionForm = new TransitionForm(this);
+ ui->timelineTab->addTab(transitionForm, node.modelNode().displayName());
+ transitionForm->setTransition(node);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h
new file mode 100644
index 0000000000..ef9c6ad0c4
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QDialog>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+class TransitionForm;
+class TransitionEditorView;
+
+namespace Ui {
+class TransitionEditorSettingsDialog;
+}
+
+class TransitionEditorSettingsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorSettingsDialog(QWidget *parent, class TransitionEditorView *view);
+ void setCurrentTransition(const ModelNode &timeline);
+ ~TransitionEditorSettingsDialog() override;
+
+protected:
+ void keyPressEvent(QKeyEvent *event) override;
+
+private:
+ void setupTransitions(const ModelNode &node);
+
+ void addTransitionTab(const QmlTimeline &node);
+
+ Ui::TransitionEditorSettingsDialog *ui;
+
+ TransitionEditorView *m_transitionEditorView;
+ ModelNode m_currentTransition;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui
new file mode 100644
index 0000000000..282c2c475e
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorsettingsdialog.ui
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TransitionEditorSettingsDialog</class>
+ <widget class="QDialog" name="QmlDesigner::TransitionEditorSettingsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>519</width>
+ <height>582</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Transition Settings</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTabWidget" name="timelineTab">
+ <property name="currentIndex">
+ <number>-1</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Close</set>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>QmlDesigner::TransitionEditorSettingsDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp
new file mode 100644
index 0000000000..af19629234
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.cpp
@@ -0,0 +1,342 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditortoolbar.h"
+#include "transitioneditorgraphicsscene.h"
+
+#include "timelineconstants.h"
+
+#include "timelineicons.h"
+
+#include "timelineview.h"
+#include "timelinewidget.h"
+
+#include <designeractionmanager.h>
+#include <nodelistproperty.h>
+#include <theme.h>
+#include <variantproperty.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/actionmanager/command.h>
+#include <coreplugin/icore.h>
+
+#include <utils/algorithm.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QIntValidator>
+#include <QLineEdit>
+#include <QResizeEvent>
+#include <QSlider>
+
+#include <cmath>
+
+namespace QmlDesigner {
+
+static bool isSpacer(QObject *object)
+{
+ return object->property("spacer_widget").toBool();
+}
+
+static QWidget *createSpacer()
+{
+ QWidget *spacer = new QWidget();
+ spacer->setProperty("spacer_widget", true);
+ return spacer;
+}
+
+static int controlWidth(QToolBar *bar, QObject *control)
+{
+ QWidget *widget = nullptr;
+
+ if (auto *action = qobject_cast<QAction *>(control))
+ widget = bar->widgetForAction(action);
+
+ if (widget == nullptr)
+ widget = qobject_cast<QWidget *>(control);
+
+ if (widget)
+ return widget->width();
+
+ return 0;
+}
+
+static QAction *createAction(const Core::Id &id,
+ const QIcon &icon,
+ const QString &name,
+ const QKeySequence &shortcut)
+{
+ QString text = QString("%1 (%2)").arg(name).arg(shortcut.toString());
+
+ Core::Context context(TimelineConstants::C_QMLTIMELINE);
+
+ auto *action = new QAction(icon, text);
+ auto *command = Core::ActionManager::registerAction(action, id, context);
+ command->setDefaultKeySequence(shortcut);
+
+ return action;
+}
+
+TransitionEditorToolBar::TransitionEditorToolBar(QWidget *parent)
+ : QToolBar(parent)
+ , m_grp()
+{
+ setContentsMargins(0, 0, 0, 0);
+ createLeftControls();
+ createCenterControls();
+ createRightControls();
+}
+
+void TransitionEditorToolBar::reset() {}
+
+int TransitionEditorToolBar::scaleFactor() const
+{
+ if (m_scale)
+ return m_scale->value();
+ return 0;
+}
+
+QString TransitionEditorToolBar::currentTransitionId() const
+{
+ return m_transitionComboBox->currentText();
+}
+
+void TransitionEditorToolBar::setBlockReflection(bool block)
+{
+ m_blockReflection = block;
+}
+
+void TransitionEditorToolBar::updateComboBox(const ModelNode &root)
+{
+ if (root.isValid() && root.hasProperty("transitions")) {
+ NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ for (const ModelNode &transition : transitions.directSubNodes())
+ m_transitionComboBox->addItem(transition.id());
+ }
+}
+
+void TransitionEditorToolBar::setCurrentTransition(const ModelNode &transition)
+{
+ if (m_blockReflection)
+ return;
+
+ if (transition.isValid()) {
+ m_transitionComboBox->clear();
+ const ModelNode root = transition.view()->rootModelNode();
+ updateComboBox(root);
+ m_transitionComboBox->setCurrentText(transition.id());
+ } else {
+ m_transitionComboBox->clear();
+ m_transitionComboBox->setCurrentText("");
+ }
+}
+
+void TransitionEditorToolBar::setDuration(qreal frame)
+{
+ auto text = QString::number(frame, 'f', 0);
+ m_duration->setText(text);
+}
+
+void TransitionEditorToolBar::setScaleFactor(int factor)
+{
+ const QSignalBlocker blocker(m_scale);
+ m_scale->setValue(factor);
+}
+
+void TransitionEditorToolBar::setActionEnabled(const QString &name, bool enabled)
+{
+ for (auto *action : actions())
+ if (action->objectName() == name)
+ action->setEnabled(enabled);
+}
+
+void TransitionEditorToolBar::createLeftControls()
+{
+ auto addActionToGroup = [&](QAction *action) {
+ addAction(action);
+ m_grp << action;
+ };
+
+ auto addWidgetToGroup = [&](QWidget *widget) {
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ auto addSpacingToGroup = [&](int width) {
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+ m_grp << widget;
+ };
+
+ addSpacingToGroup(5);
+
+ auto *settingsAction = createAction(TimelineConstants::C_SETTINGS,
+ TimelineIcons::ANIMATION.icon(),
+ tr("Transition Settings"),
+ QKeySequence(Qt::Key_S));
+ connect(settingsAction,
+ &QAction::triggered,
+ this,
+ &TransitionEditorToolBar::settingDialogClicked);
+
+ addActionToGroup(settingsAction);
+
+ addWidgetToGroup(createSpacer());
+
+ m_transitionComboBox = new QComboBox(this);
+ addWidgetToGroup(m_transitionComboBox);
+
+ connect(m_transitionComboBox, &QComboBox::currentTextChanged, this, [this]() {
+ emit currentTransitionChanged(m_transitionComboBox->currentText());
+ });
+}
+
+static QLineEdit *createToolBarLineEdit(QWidget *parent)
+{
+ auto lineEdit = new QLineEdit(parent);
+ lineEdit->setStyleSheet("* { background-color: rgba(0, 0, 0, 0); }");
+ lineEdit->setFixedWidth(48);
+ lineEdit->setAlignment(Qt::AlignCenter);
+
+ QPalette pal = parent->palette();
+ pal.setColor(QPalette::Text, Theme::instance()->color(Utils::Theme::PanelTextColorLight));
+ lineEdit->setPalette(pal);
+ QValidator *validator = new QIntValidator(-100000, 100000, lineEdit);
+ lineEdit->setValidator(validator);
+
+ return lineEdit;
+}
+
+void TransitionEditorToolBar::createCenterControls()
+{
+ addSpacing(10);
+
+ auto *curvePicker = createAction(TimelineConstants::C_CURVE_PICKER,
+ TimelineIcons::CURVE_EDITOR.icon(),
+ tr("Easing Curve Editor"),
+ QKeySequence(Qt::Key_C));
+
+ curvePicker->setObjectName("Easing Curve Editor");
+ connect(curvePicker, &QAction::triggered, this, &TransitionEditorToolBar::openEasingCurveEditor);
+ addAction(curvePicker);
+
+ addSpacing(10);
+
+#if 0
+ addSeparator();
+
+ addSpacing(10);
+
+ auto *curveEditor = new QAction(TimelineIcons::CURVE_PICKER.icon(), tr("Curve Editor"));
+ addAction(curveEditor);
+#endif
+}
+
+void TransitionEditorToolBar::createRightControls()
+{
+ auto *spacer = createSpacer();
+ spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ addWidget(spacer);
+
+ addSeparator();
+ addSpacing(10);
+
+ auto *zoomOut = createAction(TimelineConstants::C_ZOOM_OUT,
+ TimelineIcons::ZOOM_SMALL.icon(),
+ tr("Zoom Out"),
+ QKeySequence(QKeySequence::ZoomOut));
+
+ connect(zoomOut, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() - m_scale->pageStep());
+ });
+ addAction(zoomOut);
+
+ addSpacing(10);
+
+ m_scale = new QSlider(this);
+ m_scale->setOrientation(Qt::Horizontal);
+ m_scale->setMaximumWidth(200);
+ m_scale->setMinimumWidth(100);
+ m_scale->setMinimum(0);
+ m_scale->setMaximum(100);
+ m_scale->setValue(0);
+
+ connect(m_scale, &QSlider::valueChanged, this, &TransitionEditorToolBar::scaleFactorChanged);
+ addWidget(m_scale);
+
+ addSpacing(10);
+
+ auto *zoomIn = createAction(TimelineConstants::C_ZOOM_IN,
+ TimelineIcons::ZOOM_BIG.icon(),
+ tr("Zoom In"),
+ QKeySequence(QKeySequence::ZoomIn));
+
+ connect(zoomIn, &QAction::triggered, [this]() {
+ m_scale->setValue(m_scale->value() + m_scale->pageStep());
+ });
+ addAction(zoomIn);
+
+ addSpacing(10);
+
+ addSeparator();
+
+ m_duration = createToolBarLineEdit(this);
+ addWidget(m_duration);
+
+ auto emitEndChanged = [this]() { emit durationChanged(m_duration->text().toInt()); };
+ connect(m_duration, &QLineEdit::editingFinished, emitEndChanged);
+}
+
+void TransitionEditorToolBar::addSpacing(int width)
+{
+ auto *widget = new QWidget;
+ widget->setFixedWidth(width);
+ addWidget(widget);
+}
+
+void TransitionEditorToolBar::resizeEvent(QResizeEvent *event)
+{
+ Q_UNUSED(event)
+
+ int width = 0;
+ QWidget *spacer = nullptr;
+ for (auto *object : qAsConst(m_grp)) {
+ if (isSpacer(object))
+ spacer = qobject_cast<QWidget *>(object);
+ else
+ width += controlWidth(this, object);
+ }
+
+ if (spacer) {
+ int spacerWidth = TimelineConstants::sectionWidth - width - 12;
+ spacer->setFixedWidth(spacerWidth > 0 ? spacerWidth : 0);
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h
new file mode 100644
index 0000000000..eba74021ca
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditortoolbar.h
@@ -0,0 +1,92 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "animationcurvedialog.h"
+#include "animationcurveeditormodel.h"
+
+#include <QToolBar>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QLineEdit)
+QT_FORWARD_DECLARE_CLASS(QObject)
+QT_FORWARD_DECLARE_CLASS(QResizeEvent)
+QT_FORWARD_DECLARE_CLASS(QSlider)
+QT_FORWARD_DECLARE_CLASS(QWidget)
+
+namespace QmlDesigner {
+
+class TimelineWidget;
+
+class QmlTimeline;
+
+class TransitionEditorToolBar : public QToolBar
+{
+ Q_OBJECT
+
+signals:
+ void settingDialogClicked();
+
+ void scaleFactorChanged(int value);
+ void durationChanged(int value);
+ void currentTransitionChanged(const QString &name);
+ void openEasingCurveEditor();
+
+public:
+ explicit TransitionEditorToolBar(QWidget *parent = nullptr);
+
+ void reset();
+
+ int scaleFactor() const;
+ QString currentTransitionId() const;
+
+ void setBlockReflection(bool block);
+ void setCurrentTransition(const ModelNode &transition);
+ void setDuration(qreal frame);
+ void setScaleFactor(int factor);
+
+ void setActionEnabled(const QString &name, bool enabled);
+
+ void updateComboBox(const ModelNode &root);
+protected:
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void createLeftControls();
+ void createCenterControls();
+ void createRightControls();
+ void addSpacing(int width);
+
+ QList<QObject *> m_grp;
+
+ QComboBox *m_transitionComboBox = nullptr;
+ QSlider *m_scale = nullptr;
+ QLineEdit *m_duration = nullptr;
+
+ bool m_blockReflection = false;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp
new file mode 100644
index 0000000000..a7ead0afd2
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.cpp
@@ -0,0 +1,348 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorview.h"
+
+#include "transitioneditortoolbar.h"
+#include "transitioneditorwidget.h"
+
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorsettingsdialog.h"
+
+#include <bindingproperty.h>
+#include <exception.h>
+#include <modelnodecontextmenu_helper.h>
+#include <nodeabstractproperty.h>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+#include <viewmanager.h>
+#include <qmldesignericons.h>
+#include <qmldesignerplugin.h>
+#include <qmlitemnode.h>
+#include <qmlobjectnode.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+#include <qmltimelinekeyframegroup.h>
+
+#include <designmodecontext.h>
+
+#include <coreplugin/icore.h>
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+#include <QTimer>
+
+namespace QmlDesigner {
+
+TransitionEditorView::TransitionEditorView(QObject *parent)
+ : AbstractView(parent)
+ , m_transitionEditorWidget(nullptr)
+{
+
+}
+
+TransitionEditorView::~TransitionEditorView() = default;
+
+void TransitionEditorView::modelAttached(Model *model)
+{
+ AbstractView::modelAttached(model);
+ if (m_transitionEditorWidget)
+ m_transitionEditorWidget->init();
+}
+
+void TransitionEditorView::modelAboutToBeDetached(Model *model)
+{
+ m_transitionEditorWidget->reset();
+
+ AbstractView::modelAboutToBeDetached(model);
+}
+
+void TransitionEditorView::nodeCreated(const ModelNode & /*createdNode*/) {}
+
+void TransitionEditorView::nodeAboutToBeRemoved(const ModelNode & /*removedNode*/) {}
+
+void TransitionEditorView::nodeRemoved(const ModelNode & removedNode,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags /*propertyChange*/)
+{
+ if (parentProperty.name() == "transitions")
+ widget()->updateData(removedNode);
+}
+
+void TransitionEditorView::nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty & /*oldPropertyParent*/,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+ if (newPropertyParent.name() == "transitions")
+ asyncUpdate(node);
+
+ const ModelNode parent = newPropertyParent.parentModelNode();
+
+ qDebug() << Q_FUNC_INFO << parent;
+ if (parent.isValid() && parent.metaInfo().isValid()
+ && parent.metaInfo().isSubclassOf("QtQuick.Transition")) {
+ asyncUpdate(parent);
+ }
+}
+
+void TransitionEditorView::instancePropertyChanged(
+ const QList<QPair<ModelNode, PropertyName>> & /*propertyList*/)
+{
+
+}
+
+void TransitionEditorView::variantPropertiesChanged(
+ const QList<VariantProperty> & /* propertyList */,
+ AbstractView::PropertyChangeFlags /*propertyChange*/)
+{
+
+}
+
+void TransitionEditorView::bindingPropertiesChanged(
+ const QList<BindingProperty> & /*propertyList */,
+ AbstractView::PropertyChangeFlags /* propertyChange */)
+{
+
+}
+
+void TransitionEditorView::selectedNodesChanged(const QList<ModelNode> & /*selectedNodeList*/,
+ const QList<ModelNode> & /*lastSelectedNodeList*/)
+{
+
+}
+
+void TransitionEditorView::propertiesAboutToBeRemoved(
+ const QList<AbstractProperty> & /*propertyList */)
+{
+
+}
+
+void TransitionEditorView::propertiesRemoved(const QList<AbstractProperty> &propertyList)
+{
+ for (const AbstractProperty &property : propertyList) {
+ if (property.name() == "transitions")
+ widget()->init();
+ }
+}
+
+bool TransitionEditorView::hasWidget() const
+{
+ return true;
+}
+
+void TransitionEditorView::nodeIdChanged(const ModelNode &node, const QString &, const QString &)
+{
+ if (node.metaInfo().isValid() && node.metaInfo().isSubclassOf("QtQuick.Transition"))
+ widget()->init();
+}
+
+void TransitionEditorView::currentStateChanged(const ModelNode &)
+{
+
+}
+
+TransitionEditorWidget *TransitionEditorView::widget() const
+{
+ return m_transitionEditorWidget;
+}
+
+void TransitionEditorView::registerActions()
+{
+
+}
+
+ModelNode TransitionEditorView::addNewTransition()
+{
+ QList<QmlModelState> states;
+ const ModelNode root = rootModelNode();
+
+ if (QmlVisualNode::isValidQmlVisualNode(root)) {
+ states = QmlVisualNode(root).states().allStates();
+ }
+
+ if (states.isEmpty()) {
+ Core::AsynchronousMessageBox::warning(tr("No States Defined"),
+ tr("There are no states defined in this component."));
+ return {};
+ }
+
+ QHash<QString, QStringList> idPropertyList;
+
+ const QVector<TypeName> validProperties = {"int", "real", "double", "qreal", "color", "QColor"};
+
+ for (const QmlModelState &state : qAsConst(states)) {
+ for (const QmlPropertyChanges & change : state.propertyChanges()) {
+ QStringList locList;
+ const ModelNode target = change.target();
+ const QString targetId = target.id();
+ if (target.isValid() && target.hasMetaInfo()) {
+ for (const VariantProperty &property : change.modelNode().variantProperties()) {
+ TypeName typeName = target.metaInfo().propertyTypeName(property.name());
+
+ if (validProperties.contains(typeName))
+ locList.append(QString::fromUtf8(property.name()));
+ }
+ if (idPropertyList.contains(targetId)) {
+ QStringList newlist = idPropertyList.value(targetId);
+ for (const QString &str :locList)
+ if (!newlist.contains(str))
+ newlist.append(str);
+ idPropertyList.insert(targetId, newlist);
+ } else {
+ if (!locList.isEmpty())
+ idPropertyList.insert(targetId, locList);
+ }
+ }
+ }
+ }
+
+ ModelNode transition;
+
+ if (!idPropertyList.isEmpty()) {
+ executeInTransaction(
+ " TransitionEditorView::addNewTransition", [&transition, idPropertyList, root, this]() {
+ transition = createModelNode("QtQuick.Transition",
+ 2,
+ 0,
+ {{
+ "from",
+ "*",
+ },
+ {
+ "to",
+ "*",
+ }});
+ transition.setAuxiliaryData("transitionDuration", 2000);
+ transition.validId();
+ root.nodeListProperty("transitions").reparentHere(transition);
+
+ for (const QString &id : idPropertyList.keys()) {
+ ModelNode parallelAnimation = createModelNode("QtQuick.ParallelAnimation",
+ 2,
+ 12);
+ transition.defaultNodeAbstractProperty().reparentHere(parallelAnimation);
+ for (const QString &property : idPropertyList.value(id)) {
+ ModelNode sequentialAnimation
+ = createModelNode("QtQuick.SequentialAnimation", 2, 12);
+ parallelAnimation.defaultNodeAbstractProperty().reparentHere(
+ sequentialAnimation);
+
+ ModelNode pauseAnimation = createModelNode("QtQuick.PauseAnimation",
+ 2,
+ 12,
+ {{"duration", 50}});
+ sequentialAnimation.defaultNodeAbstractProperty().reparentHere(
+ pauseAnimation);
+
+ ModelNode propertyAnimation = createModelNode("QtQuick.PropertyAnimation",
+ 2,
+ 12,
+ {{"property", property},
+ {"duration", 150}});
+ propertyAnimation.bindingProperty("target").setExpression(id);
+ sequentialAnimation.defaultNodeAbstractProperty().reparentHere(
+ propertyAnimation);
+ }
+ }
+ });
+ }
+
+ if (m_transitionEditorWidget)
+ m_transitionEditorWidget->init();
+
+ return transition;
+}
+
+TransitionEditorWidget *TransitionEditorView::createWidget()
+{
+ if (!m_transitionEditorWidget)
+ m_transitionEditorWidget = new TransitionEditorWidget(this);
+
+ //auto *timelineContext = new TimelineContext(m_timelineWidget);
+ //Core::ICore::addContextObject(timelineContext);
+
+ return m_transitionEditorWidget;
+}
+
+WidgetInfo TransitionEditorView::widgetInfo()
+{
+ return createWidgetInfo(createWidget(),
+ nullptr,
+ "TransitionEditor",
+ WidgetInfo::BottomPane,
+ 0,
+ tr("Transition Editor"));
+}
+
+void TransitionEditorView::openSettingsDialog()
+{
+ auto dialog = new TransitionEditorSettingsDialog(Core::ICore::dialogParent(), this);
+
+ auto transition = widget()->graphicsScene()->transitionModelNode();
+ if (transition.isValid())
+ dialog->setCurrentTransition(transition);
+
+ QObject::connect(dialog, &TransitionEditorSettingsDialog::rejected, [this, dialog]() {
+ widget()->init();
+ dialog->deleteLater();
+ });
+
+ QObject::connect(dialog, &TransitionEditorSettingsDialog::accepted, [this, dialog]() {
+ widget()->init();
+ dialog->deleteLater();
+ });
+
+ dialog->show();
+}
+
+const QList<ModelNode> TransitionEditorView::allTransitions() const
+{
+ if (rootModelNode().isValid() && rootModelNode().hasProperty("transitions")) {
+ NodeAbstractProperty transitions = rootModelNode().nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ return transitions.directSubNodes();
+ }
+ return {};
+}
+
+void TransitionEditorView::asyncUpdate(const ModelNode &transition)
+{
+ static bool updateTriggered = false;
+
+ if (!updateTriggered && (transition.id() == widget()->toolBar()->currentTransitionId())) {
+ updateTriggered = true;
+ QTimer::singleShot(0, [this, transition]() {
+ widget()->updateData(transition);
+ updateTriggered = false;
+ });
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h
new file mode 100644
index 0000000000..faa383c621
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorview.h
@@ -0,0 +1,97 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "animationcurvedialog.h"
+#include "animationcurveeditormodel.h"
+#include "treeitem.h"
+
+#include <abstractview.h>
+
+#include <QPointer>
+
+namespace QmlDesigner {
+
+class TransitionEditorWidget;
+
+class TransitionEditorView : public AbstractView
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorView(QObject *parent = nullptr);
+ ~TransitionEditorView() override;
+ //Abstract View
+ WidgetInfo widgetInfo() override;
+ void modelAttached(Model *model) override;
+ void modelAboutToBeDetached(Model *model) override;
+ void nodeCreated(const ModelNode &createdNode) override;
+ void nodeAboutToBeRemoved(const ModelNode &removedNode) override;
+ void nodeRemoved(const ModelNode &removedNode,
+ const NodeAbstractProperty &parentProperty,
+ PropertyChangeFlags propertyChange) override;
+ void nodeReparented(const ModelNode &node,
+ const NodeAbstractProperty &newPropertyParent,
+ const NodeAbstractProperty &oldPropertyParent,
+ PropertyChangeFlags propertyChange) override;
+ void instancePropertyChanged(const QList<QPair<ModelNode, PropertyName>> &propertyList) override;
+ void variantPropertiesChanged(const QList<VariantProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void bindingPropertiesChanged(const QList<BindingProperty> &propertyList,
+ PropertyChangeFlags propertyChange) override;
+ void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
+ const QList<ModelNode> &lastSelectedNodeList) override;
+
+ void propertiesAboutToBeRemoved(const QList<AbstractProperty> &propertyList) override;
+ void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
+
+ bool hasWidget() const override;
+
+ void nodeIdChanged(const ModelNode &node, const QString &, const QString &) override;
+
+ void currentStateChanged(const ModelNode &node) override;
+
+ TransitionEditorWidget *widget() const;
+
+ void insertKeyframe(const ModelNode &target, const PropertyName &propertyName);
+
+ void registerActions();
+
+ ModelNode addNewTransition();
+
+ void openSettingsDialog();
+
+ const QList<ModelNode> allTransitions() const;
+
+ void asyncUpdate(const ModelNode &transition);
+
+private:
+ TransitionEditorWidget *createWidget();
+
+ TransitionEditorWidget *m_transitionEditorWidget = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp
new file mode 100644
index 0000000000..5d27b4c969
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.cpp
@@ -0,0 +1,414 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitioneditorwidget.h"
+
+#include "transitioneditorgraphicsscene.h"
+#include "transitioneditorpropertyitem.h"
+#include "transitioneditortoolbar.h"
+#include "transitioneditorview.h"
+
+#include <timelineeditor/easingcurvedialog.h>
+#include <timelineeditor/timelineconstants.h>
+#include <timelineeditor/timelineicons.h>
+
+#include <bindingproperty.h>
+#include <nodeabstractproperty.h>
+#include <nodemetainfo.h>
+
+#include <qmldesignerplugin.h>
+#include <qmlstate.h>
+#include <qmltimeline.h>
+
+#include <coreplugin/icore.h>
+
+#include <theme.h>
+#include <utils/algorithm.h>
+#include <utils/fileutils.h>
+
+#include <QApplication>
+#include <QComboBox>
+#include <QGraphicsView>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QMargins>
+#include <QPushButton>
+#include <QResizeEvent>
+#include <QScrollBar>
+#include <QShowEvent>
+#include <QSlider>
+#include <QSpacerItem>
+#include <QVBoxLayout>
+#include <QtGlobal>
+
+namespace QmlDesigner {
+
+class Eventfilter : public QObject
+{
+public:
+ Eventfilter(QObject *parent)
+ : QObject(parent)
+ {}
+
+ bool eventFilter(QObject *, QEvent *event) override
+ {
+ if (event->type() == QEvent::Wheel) {
+ event->accept();
+ return true;
+ }
+ return false;
+ }
+};
+
+TransitionEditorWidget::TransitionEditorWidget(TransitionEditorView *view)
+ : QWidget()
+ , m_toolbar(new TransitionEditorToolBar(this))
+ , m_rulerView(new QGraphicsView(this))
+ , m_graphicsView(new QGraphicsView(this))
+ , m_scrollbar(new QScrollBar(this))
+ , m_statusBar(new QLabel(this))
+ , m_transitionEditorView(view)
+ , m_graphicsScene(new TransitionEditorGraphicsScene(this))
+ , m_addButton(new QPushButton(this))
+ , m_onboardingContainer(new QWidget(this))
+{
+ setWindowTitle(tr("Transition", "Title of transition view"));
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
+
+ const QString css = Theme::replaceCssColors(QString::fromUtf8(
+ Utils::FileReader::fetchQrc(QLatin1String(":/qmldesigner/scrollbar.css"))));
+
+ m_scrollbar->setStyleSheet(css);
+ m_scrollbar->setOrientation(Qt::Horizontal);
+
+ QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
+ sizePolicy1.setHorizontalStretch(0);
+ sizePolicy1.setVerticalStretch(0);
+ sizePolicy1.setHeightForWidth(m_graphicsView->sizePolicy().hasHeightForWidth());
+
+ m_rulerView->setObjectName("RulerView");
+ m_rulerView->setFixedHeight(TimelineConstants::rulerHeight);
+ m_rulerView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_rulerView->viewport()->installEventFilter(new Eventfilter(this));
+ m_rulerView->viewport()->setFocusPolicy(Qt::NoFocus);
+ m_rulerView->setStyleSheet(css);
+ m_rulerView->setFrameShape(QFrame::NoFrame);
+ m_rulerView->setFrameShadow(QFrame::Plain);
+ m_rulerView->setLineWidth(0);
+ m_rulerView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ m_rulerView->setScene(graphicsScene());
+
+ m_graphicsView->setStyleSheet(css);
+ m_graphicsView->setObjectName("SceneView");
+ m_graphicsView->setFrameShape(QFrame::NoFrame);
+ m_graphicsView->setFrameShadow(QFrame::Plain);
+ m_graphicsView->setLineWidth(0);
+ m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+ m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ m_graphicsView->setSizePolicy(sizePolicy1);
+ m_graphicsView->setScene(graphicsScene());
+ m_graphicsView->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ m_graphicsView->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
+
+ auto *scrollBarLayout = new QHBoxLayout;
+ scrollBarLayout->addSpacing(TimelineConstants::sectionWidth);
+ scrollBarLayout->addWidget(m_scrollbar);
+
+ QMargins margins(0, 0, 0, QApplication::style()->pixelMetric(QStyle::PM_LayoutBottomMargin));
+
+ auto *contentLayout = new QVBoxLayout;
+ contentLayout->setContentsMargins(margins);
+ contentLayout->addWidget(m_rulerView);
+ contentLayout->addWidget(m_graphicsView);
+ contentLayout->addLayout(scrollBarLayout);
+ contentLayout->addWidget(m_statusBar);
+ m_statusBar->setIndent(2);
+ m_statusBar->setFixedHeight(TimelineConstants::rulerHeight);
+
+ auto *widgetLayout = new QVBoxLayout;
+ widgetLayout->setContentsMargins(0, 0, 0, 0);
+ widgetLayout->setSpacing(0);
+ widgetLayout->addWidget(m_toolbar);
+ widgetLayout->addWidget(m_addButton);
+
+ m_addButton->setIcon(TimelineIcons::ADD_TIMELINE_TOOLBAR.icon());
+ m_addButton->setToolTip(tr("Add Transition"));
+ m_addButton->setFlat(true);
+ m_addButton->setFixedSize(32, 32);
+
+ widgetLayout->addWidget(m_onboardingContainer);
+
+ auto *onboardingTopLabel = new QLabel(m_onboardingContainer);
+ auto *onboardingBottomLabel = new QLabel(m_onboardingContainer);
+ auto *onboardingBottomIcon = new QLabel(m_onboardingContainer);
+
+ auto *onboardingLayout = new QVBoxLayout;
+ auto *onboardingSublayout = new QHBoxLayout;
+ auto *leftSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ auto *rightSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
+ auto *topSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
+ auto *bottomSpacer = new QSpacerItem(40, 20, QSizePolicy::Minimum, QSizePolicy::Expanding);
+
+ QString labelText = tr("This file does not contain transitions. <br><br> \
+ To create an animation, add a transition by clicking the + button.");
+ onboardingTopLabel->setText(labelText);
+ onboardingTopLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
+
+ m_onboardingContainer->setLayout(onboardingLayout);
+ onboardingLayout->setContentsMargins(0, 0, 0, 0);
+ onboardingLayout->setSpacing(0);
+ onboardingLayout->addSpacerItem(topSpacer);
+ onboardingLayout->addWidget(onboardingTopLabel);
+ onboardingLayout->addLayout(onboardingSublayout);
+
+ onboardingSublayout->setContentsMargins(0, 0, 0, 0);
+ onboardingSublayout->setSpacing(0);
+ onboardingSublayout->addSpacerItem(leftSpacer);
+
+ onboardingBottomLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
+ onboardingBottomLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ onboardingSublayout->addWidget(onboardingBottomLabel);
+ onboardingBottomLabel->setText(tr("To edit the transition settings, click "));
+
+ onboardingBottomIcon->setAlignment(Qt::AlignLeft | Qt::AlignTop);
+ onboardingBottomIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
+ onboardingSublayout->addWidget(onboardingBottomIcon);
+ onboardingBottomIcon->setPixmap(TimelineIcons::ANIMATION.pixmap());
+
+ onboardingSublayout->addSpacerItem(rightSpacer);
+ onboardingLayout->addSpacerItem(bottomSpacer);
+
+ widgetLayout->addLayout(contentLayout);
+ this->setLayout(widgetLayout);
+
+ connectToolbar();
+
+ auto setScrollOffset = [this]() { graphicsScene()->setScrollOffset(m_scrollbar->value()); };
+ connect(m_scrollbar, &QSlider::valueChanged, this, setScrollOffset);
+
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::statusBarMessageChanged,
+ this,
+ [this](const QString &message) { m_statusBar->setText(message); });
+
+ connect(m_addButton, &QPushButton::clicked, this, [this]() {
+ m_transitionEditorView->addNewTransition();
+ });
+}
+
+void TransitionEditorWidget::setTransitionActive(bool b)
+{
+ if (b) {
+ m_toolbar->setVisible(true);
+ m_graphicsView->setVisible(true);
+ m_rulerView->setVisible(true);
+ m_scrollbar->setVisible(true);
+ m_addButton->setVisible(false);
+ m_onboardingContainer->setVisible(false);
+ m_graphicsView->update();
+ m_rulerView->update();
+ } else {
+ m_toolbar->setVisible(false);
+ m_graphicsView->setVisible(false);
+ m_rulerView->setVisible(false);
+ m_scrollbar->setVisible(false);
+ m_addButton->setVisible(true);
+ m_onboardingContainer->setVisible(true);
+ }
+}
+
+void TransitionEditorWidget::connectToolbar()
+{
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::selectionChanged,
+ this,
+ &TransitionEditorWidget::selectionChanged);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::openEasingCurveEditor,
+ this,
+ &TransitionEditorWidget::openEasingCurveEditor);
+
+ connect(graphicsScene(),
+ &TransitionEditorGraphicsScene::scroll,
+ this,
+ &TransitionEditorWidget::scroll);
+
+ auto setRulerScaling = [this](int val) { m_graphicsScene->setRulerScaling(val); };
+ connect(m_toolbar, &TransitionEditorToolBar::scaleFactorChanged, setRulerScaling);
+
+ auto setDuration = [this](int end) { graphicsScene()->setDuration(end); };
+ connect(m_toolbar, &TransitionEditorToolBar::durationChanged, setDuration);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::settingDialogClicked,
+ transitionEditorView(),
+ &TransitionEditorView::openSettingsDialog);
+
+ connect(m_toolbar,
+ &TransitionEditorToolBar::currentTransitionChanged,
+ this,
+ [this](const QString &transitionName) {
+ const ModelNode transition = transitionEditorView()->modelNodeForId(transitionName);
+ if (transition.isValid()) {
+ m_graphicsScene->setTransition(transition);
+ }
+ });
+}
+
+void TransitionEditorWidget::changeScaleFactor(int factor)
+{
+ m_toolbar->setScaleFactor(factor);
+}
+
+void TransitionEditorWidget::scroll(const TimelineUtils::Side &side)
+{
+ if (side == TimelineUtils::Side::Left)
+ m_scrollbar->setValue(m_scrollbar->value() - m_scrollbar->singleStep());
+ else if (side == TimelineUtils::Side::Right)
+ m_scrollbar->setValue(m_scrollbar->value() + m_scrollbar->singleStep());
+}
+
+void TransitionEditorWidget::selectionChanged()
+{
+ if (graphicsScene()->selectedPropertyItem() != nullptr)
+ m_toolbar->setActionEnabled("Curve Picker", true);
+ else
+ m_toolbar->setActionEnabled("Curve Picker", false);
+}
+
+void TransitionEditorWidget::contextHelp(const Core::IContext::HelpCallback &callback) const
+{
+ if (transitionEditorView())
+ transitionEditorView()->contextHelp(callback);
+ else
+ callback({});
+}
+
+void TransitionEditorWidget::init()
+{
+ ModelNode root = transitionEditorView()->rootModelNode();
+ ModelNode transition;
+
+ if (root.isValid() && root.hasProperty("transitions")) {
+ NodeAbstractProperty transitions = root.nodeAbstractProperty("transitions");
+ if (transitions.isValid())
+ transition = transitions.directSubNodes().first();
+ }
+
+ m_graphicsScene->setTransition(transition);
+ setTransitionActive(transition.isValid());
+
+ m_graphicsScene->setWidth(m_graphicsView->viewport()->width());
+
+ m_toolbar->setScaleFactor(0);
+
+ m_toolbar->setCurrentTransition(transition);
+
+ qreal duration = 2000;
+ if (transition.isValid() && transition.hasAuxiliaryData("transitionDuration"))
+ duration = transition.auxiliaryData("transitionDuration").toDouble();
+
+ m_toolbar->setDuration(duration);
+
+ m_graphicsScene->setRulerScaling(0);
+}
+
+void TransitionEditorWidget::updateData(const ModelNode &transition)
+{
+ if (!transition.isValid()) {
+ init();
+ return;
+ }
+
+ if (transition.metaInfo().isValid()
+ && transition.metaInfo().isSubclassOf("QtQuick.Transition")) {
+ if (transition.id() == m_toolbar->currentTransitionId()) {
+ m_graphicsScene->setTransition(transition);
+ } else {
+ m_toolbar->updateComboBox(transition.view()->rootModelNode());
+ }
+ }
+}
+
+void TransitionEditorWidget::reset()
+{
+ graphicsScene()->clearTransition();
+ m_toolbar->reset();
+ m_statusBar->clear();
+}
+
+TransitionEditorGraphicsScene *TransitionEditorWidget::graphicsScene() const
+{
+ return m_graphicsScene;
+}
+
+TransitionEditorToolBar *TransitionEditorWidget::toolBar() const
+{
+ return m_toolbar;
+}
+
+void TransitionEditorWidget::setupScrollbar(int min, int max, int current)
+{
+ bool b = m_scrollbar->blockSignals(true);
+ m_scrollbar->setMinimum(min);
+ m_scrollbar->setMaximum(max);
+ m_scrollbar->setValue(current);
+ m_scrollbar->setSingleStep((max - min) / 10);
+ m_scrollbar->blockSignals(b);
+}
+
+void TransitionEditorWidget::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event)
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+ graphicsScene()->invalidateLayout();
+ graphicsScene()->invalidate();
+ graphicsScene()->onShow();
+}
+
+void TransitionEditorWidget::resizeEvent(QResizeEvent *event)
+{
+ QWidget::resizeEvent(event);
+ graphicsScene()->setWidth(m_graphicsView->viewport()->width());
+}
+
+TransitionEditorView *TransitionEditorWidget::transitionEditorView() const
+{
+ return m_transitionEditorView;
+}
+
+void TransitionEditorWidget::openEasingCurveEditor()
+{
+ if (TransitionEditorPropertyItem *item = graphicsScene()->selectedPropertyItem()) {
+ QList<ModelNode> animations;
+ animations.append(item->propertyAnimation());
+ EasingCurveDialog::runDialog(animations, Core::ICore::dialogParent());
+ }
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h
new file mode 100644
index 0000000000..97f57f1c2f
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitioneditorwidget.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include "timelineeditor/timelineutils.h"
+
+#include <coreplugin/icontext.h>
+
+#include <QWidget>
+
+#include <functional>
+
+QT_FORWARD_DECLARE_CLASS(QComboBox)
+QT_FORWARD_DECLARE_CLASS(QGraphicsView)
+QT_FORWARD_DECLARE_CLASS(QLabel)
+QT_FORWARD_DECLARE_CLASS(QResizeEvent)
+QT_FORWARD_DECLARE_CLASS(QScrollBar)
+QT_FORWARD_DECLARE_CLASS(QShowEvent)
+QT_FORWARD_DECLARE_CLASS(QString)
+QT_FORWARD_DECLARE_CLASS(QPushButton)
+
+namespace QmlDesigner {
+
+class TransitionEditorView;
+class TransitionEditorToolBar;
+class TransitionEditorGraphicsScene;
+class ModelNode;
+
+class TransitionEditorWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionEditorWidget(TransitionEditorView *view);
+ void contextHelp(const Core::IContext::HelpCallback &callback) const;
+
+ TransitionEditorGraphicsScene *graphicsScene() const;
+ TransitionEditorView *transitionEditorView() const;
+ TransitionEditorToolBar *toolBar() const;
+
+ void init();
+ void reset();
+
+ void setupScrollbar(int min, int max, int current);
+ void changeScaleFactor(int factor);
+
+ void updateData(const ModelNode &transition);
+public slots:
+ void selectionChanged();
+ void scroll(const TimelineUtils::Side &side);
+
+protected:
+ void showEvent(QShowEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void connectToolbar();
+ void setTransitionActive(bool b);
+ void openEasingCurveEditor();
+
+ TransitionEditorToolBar *m_toolbar = nullptr;
+
+ QGraphicsView *m_rulerView = nullptr;
+
+ QGraphicsView *m_graphicsView = nullptr;
+
+ QScrollBar *m_scrollbar = nullptr;
+
+ QLabel *m_statusBar = nullptr;
+
+ TransitionEditorView *m_transitionEditorView = nullptr;
+
+ TransitionEditorGraphicsScene *m_graphicsScene;
+
+ QPushButton *m_addButton = nullptr;
+
+ QWidget *m_onboardingContainer = nullptr;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp
new file mode 100644
index 0000000000..239817197b
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.cpp
@@ -0,0 +1,211 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#include "transitionform.h"
+#include "timelineform.h"
+#include "ui_transitionform.h"
+
+#include <abstractview.h>
+#include <bindingproperty.h>
+#include <exception>
+#include <nodelistproperty.h>
+#include <nodemetainfo.h>
+#include <rewritertransaction.h>
+#include <variantproperty.h>
+#include <qmlitemnode.h>
+
+#include <coreplugin/messagebox.h>
+
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
+
+namespace QmlDesigner {
+
+TransitionForm::TransitionForm(QWidget *parent)
+ : QWidget(parent)
+ , ui(new Ui::TransitionForm)
+{
+ ui->setupUi(this);
+
+ connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+
+ static QString lastString;
+
+ const QString newId = ui->idLineEdit->text();
+
+ if (newId == lastString)
+ return;
+
+ lastString = newId;
+
+ if (newId == m_transition.id())
+ return;
+
+ bool error = false;
+
+ if (!ModelNode::isValidId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 is an invalid id.").arg(newId));
+ error = true;
+ } else if (m_transition.view()->hasId(newId)) {
+ Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
+ tr("%1 already exists.").arg(newId));
+ error = true;
+ } else {
+ m_transition.setIdWithRefactoring(newId);
+ }
+
+ if (error) {
+ lastString.clear();
+ ui->idLineEdit->setText(m_transition.id());
+ }
+ });
+
+ connect(ui->listWidgetTo, &QListWidget::itemChanged, this, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ QTC_ASSERT(root.isValid(), return );
+ const int stateCount = root.states().names().count();
+
+ QStringList stateNames;
+
+ for (const QListWidgetItem *item : ui->listWidgetTo->findItems("*", Qt::MatchWildcard)) {
+ if (item->checkState() == Qt::Checked)
+ stateNames.append(item->text());
+ }
+
+ QString toValue;
+ if (stateCount == stateNames.count())
+ toValue = "*";
+ else
+ toValue = stateNames.join(",");
+
+ m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, toValue]() {
+ m_transition.variantProperty("to").setValue(toValue);
+ });
+ });
+
+ connect(ui->listWidgetFrom, &QListWidget::itemChanged, this, [this]() {
+ QTC_ASSERT(m_transition.isValid(), return );
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ QTC_ASSERT(root.isValid(), return );
+ const int stateCount = root.states().names().count();
+
+ QStringList stateNames;
+
+ for (const QListWidgetItem *item : ui->listWidgetFrom->findItems("*", Qt::MatchWildcard)) {
+ if (item->checkState() == Qt::Checked)
+ stateNames.append(item->text());
+ }
+
+ QString fromValue;
+ if (stateCount == stateNames.count())
+ fromValue = "*";
+ else
+ fromValue = stateNames.join(",");
+
+ m_transition.view()->executeInTransaction("TransitionForm::Set To", [this, fromValue]() {
+ m_transition.variantProperty("from").setValue(fromValue);
+ });
+ });
+}
+
+TransitionForm::~TransitionForm()
+{
+ delete ui;
+}
+
+void TransitionForm::setTransition(const ModelNode &transition)
+{
+ m_transition = transition;
+
+ if (m_transition.isValid()) {
+ ui->idLineEdit->setText(m_transition.displayName());
+ }
+ setupStatesLists();
+}
+
+ModelNode TransitionForm::transition() const
+{
+ return m_transition;
+}
+
+void TransitionForm::setupStatesLists()
+{
+ bool bTo = ui->listWidgetTo->blockSignals(true);
+ bool bFrom = ui->listWidgetFrom->blockSignals(true);
+ QAbstractItemModel *modelTo = ui->listWidgetTo->model();
+ modelTo->removeRows(0, modelTo->rowCount());
+
+ QAbstractItemModel *modelFrom = ui->listWidgetFrom->model();
+ modelFrom->removeRows(0, modelFrom->rowCount());
+
+ bool starFrom = true;
+ bool starTo = true;
+
+ QStringList fromList;
+ QStringList toList;
+
+ if (m_transition.hasVariantProperty("from")
+ && m_transition.variantProperty("from").value().toString().trimmed() != "*") {
+ starFrom = false;
+ fromList = m_transition.variantProperty("from").value().toString().split(",");
+ }
+
+ if (m_transition.hasVariantProperty("to")
+ && m_transition.variantProperty("to").value().toString().trimmed() != "*") {
+ starTo = false;
+ toList = m_transition.variantProperty("to").value().toString().split(",");
+ }
+
+ if (m_transition.isValid()) {
+ const QmlItemNode root(m_transition.view()->rootModelNode());
+ if (root.isValid()) {
+ const QmlModelStateGroup states = root.states();
+ for (const QString &stateName : states.names()) {
+ auto itemTo = new QListWidgetItem(stateName, ui->listWidgetTo);
+ ui->listWidgetTo->addItem(itemTo);
+ itemTo->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
+ if (starTo || toList.contains(stateName))
+ itemTo->setCheckState(Qt::Checked);
+ else
+ itemTo->setCheckState(Qt::Unchecked);
+
+ auto itemFrom = new QListWidgetItem(stateName, ui->listWidgetFrom);
+ ui->listWidgetFrom->addItem(itemFrom);
+ itemFrom->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
+ if (starFrom || fromList.contains(stateName))
+ itemFrom->setCheckState(Qt::Checked);
+ else
+ itemFrom->setCheckState(Qt::Unchecked);
+ }
+ }
+ }
+ ui->listWidgetTo->blockSignals(bTo);
+ ui->listWidgetFrom->blockSignals(bFrom);
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.h b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h
new file mode 100644
index 0000000000..9999557769
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2020 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+****************************************************************************/
+
+#pragma once
+
+#include <qmltimeline.h>
+
+#include <QWidget>
+
+QT_FORWARD_DECLARE_CLASS(QSpinBox)
+
+namespace QmlDesigner {
+
+namespace Ui {
+class TransitionForm;
+}
+
+class TransitionForm : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit TransitionForm(QWidget *parent);
+ ~TransitionForm() override;
+ void setTransition(const ModelNode &transition);
+ ModelNode transition() const;
+
+private:
+ void setupStatesLists();
+
+ Ui::TransitionForm *ui;
+ ModelNode m_transition;
+};
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui
new file mode 100644
index 0000000000..560c2065af
--- /dev/null
+++ b/src/plugins/qmldesigner/components/transitioneditor/transitionform.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>QmlDesigner::TransitionForm</class>
+ <widget class="QWidget" name="QmlDesigner::TransitionForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>641</width>
+ <height>170</height>
+ </rect>
+ </property>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="minimumSize">
+ <size>
+ <width>160</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>Timeline Settings</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="2">
+ <widget class="QListWidget" name="listWidgetTo"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Transition ID:</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="2">
+ <spacer name="horizontalSpacer_11">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>49</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="3" column="0">
+ <widget class="QListWidget" name="listWidgetFrom"/>
+ </item>
+ <item row="1" column="1" colspan="2">
+ <widget class="QLineEdit" name="idLineEdit">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>From</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>To</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp
index e7d3e0a23d..f6fc86dacd 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>
@@ -243,6 +244,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);
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 c3db90d2e0..b1129a9bf3 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.qbs
+++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs
@@ -843,6 +843,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"
]
}