1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "timelineanimationform.h"
27 #include "ui_timelineanimationform.h"
28
29 #include <abstractview.h>
30 #include <bindingproperty.h>
31 #include <exception>
32 #include <nodelistproperty.h>
33 #include <nodemetainfo.h>
34 #include <rewritertransaction.h>
35 #include <signalhandlerproperty.h>
36 #include <variantproperty.h>
37 #include <qmlitemnode.h>
38 #include <qmlobjectnode.h>
39
40 #include <coreplugin/messagebox.h>
41
42 #include <utils/algorithm.h>
43 #include <utils/qtcassert.h>
44
45 namespace QmlDesigner {
46
TimelineAnimationForm(QWidget * parent)47 TimelineAnimationForm::TimelineAnimationForm(QWidget *parent)
48 : QWidget(parent)
49 , ui(new Ui::TimelineAnimationForm)
50 {
51 ui->setupUi(this);
52
53 connectSpinBox(ui->duration, "duration");
54 connectSpinBox(ui->loops, "loops");
55
56 connectSpinBox(ui->startFrame, "from");
57 connectSpinBox(ui->endFrame, "to");
58
59 connect(ui->loops, QOverload<int>::of(&QSpinBox::valueChanged), [this]() {
60 ui->continuous->setChecked(ui->loops->value() == -1);
61 });
62
63 connect(ui->continuous, &QCheckBox::toggled, [this](bool checked) {
64 if (checked) {
65 setProperty("loops", -1);
66 ui->loops->setValue(-1);
67 } else {
68 setProperty("loops", 1);
69 ui->loops->setValue(1);
70 }
71 });
72
73 connect(ui->idLineEdit, &QLineEdit::editingFinished, [this]() {
74 QTC_ASSERT(m_timeline.isValid(), return );
75
76 static QString lastString;
77
78 const QString newId = ui->idLineEdit->text();
79
80 if (lastString == newId)
81 return;
82
83 lastString = newId;
84
85 if (newId == animation().id())
86 return;
87
88 bool error = false;
89
90 if (!ModelNode::isValidId(newId)) {
91 Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
92 tr("%1 is an invalid id.").arg(newId));
93 error = true;
94 } else if (animation().view()->hasId(newId)) {
95 Core::AsynchronousMessageBox::warning(tr("Invalid Id"),
96 tr("%1 already exists.").arg(newId));
97 } else {
98 animation().setIdWithRefactoring(newId);
99 error = true;
100 }
101
102 if (error) {
103 lastString.clear();
104 ui->idLineEdit->setText(animation().id());
105 }
106 });
107
108 connect(ui->running, &QCheckBox::clicked, [this](bool checked) {
109 if (checked) {
110 setProperty("running", true);
111 } else {
112 setProperty("running", false);
113 }
114 });
115
116 connect(ui->pingPong, &QCheckBox::clicked, [this](bool checked) {
117 if (checked) {
118 setProperty("pingPong", true);
119 } else {
120 setProperty("pingPong", false);
121 }
122 });
123
124 connect(ui->transitionToState,
125 QOverload<int>::of(&QComboBox::activated),
126 [this](int index) {
127 if (!m_animation.isValid())
128 return;
129 if (!m_animation.view()->rootModelNode().hasId())
130 return;
131
132 ModelNode rootNode = m_animation.view()->rootModelNode();
133
134 if (index == 0) {
135 if (m_animation.signalHandlerProperty("onFinished").isValid())
136 m_animation.removeProperty("onFinished");
137 } else if (index == 1) {
138 m_animation.signalHandlerProperty("onFinished")
139 .setSource(rootNode.id() + ".state = \"" + "\"");
140 } else {
141 m_animation.signalHandlerProperty("onFinished")
142 .setSource(rootNode.id() + ".state = \""
143 + ui->transitionToState->currentText() + "\"");
144 }
145 });
146 }
147
~TimelineAnimationForm()148 TimelineAnimationForm::~TimelineAnimationForm()
149 {
150 delete ui;
151 }
152
setup(const ModelNode & animation)153 void TimelineAnimationForm::setup(const ModelNode &animation)
154 {
155 m_timeline = QmlTimeline(animation.parentProperty().parentModelNode());
156 setAnimation(animation);
157 setupAnimation();
158 }
159
animation() const160 ModelNode TimelineAnimationForm::animation() const
161 {
162 return m_animation;
163 }
164
setAnimation(const ModelNode & animation)165 void TimelineAnimationForm::setAnimation(const ModelNode &animation)
166 {
167 m_animation = animation;
168 }
169
setupAnimation()170 void TimelineAnimationForm::setupAnimation()
171 {
172 if (!m_animation.isValid())
173 setEnabled(false);
174
175 if (m_animation.isValid()) {
176 setEnabled(true);
177
178 ui->idLineEdit->setText(m_animation.id());
179
180 if (m_animation.hasVariantProperty("duration"))
181 ui->duration->setValue(m_animation.variantProperty("duration").value().toInt());
182 else
183 ui->duration->setValue(0);
184
185 ui->startFrame->setValue(m_animation.variantProperty("from").value().toInt());
186 ui->endFrame->setValue(m_animation.variantProperty("to").value().toInt());
187
188 if (m_animation.hasVariantProperty("loops"))
189 ui->loops->setValue(m_animation.variantProperty("loops").value().toInt());
190 else
191 ui->loops->setValue(0);
192
193 if (m_animation.hasVariantProperty("running"))
194 ui->running->setChecked(m_animation.variantProperty("running").value().toBool());
195 else
196 ui->running->setChecked(false);
197
198 if (m_animation.hasVariantProperty("pingPong"))
199 ui->pingPong->setChecked(m_animation.variantProperty("pingPong").value().toBool());
200 else
201 ui->pingPong->setChecked(false);
202
203 ui->continuous->setChecked(ui->loops->value() == -1);
204 }
205
206 populateStateComboBox();
207
208 ui->duration->setEnabled(m_animation.isValid());
209 ui->running->setEnabled(m_animation.isValid());
210 ui->continuous->setEnabled(m_animation.isValid());
211 ui->loops->setEnabled(m_animation.isValid());
212 }
213
setProperty(const PropertyName & propertyName,const QVariant & value)214 void TimelineAnimationForm::setProperty(const PropertyName &propertyName, const QVariant &value)
215 {
216 QTC_ASSERT(m_animation.isValid(), return );
217
218 try {
219 m_animation.variantProperty(propertyName).setValue(value);
220 } catch (const Exception &e) {
221 e.showException();
222 }
223 }
224
connectSpinBox(QSpinBox * spinBox,const PropertyName & propertyName)225 void TimelineAnimationForm::connectSpinBox(QSpinBox *spinBox, const PropertyName &propertyName)
226 {
227 connect(spinBox, &QSpinBox::editingFinished, [this, propertyName, spinBox]() {
228 setProperty(propertyName, spinBox->value());
229 });
230 }
231
populateStateComboBox()232 void TimelineAnimationForm::populateStateComboBox()
233 {
234 ui->transitionToState->clear();
235 ui->transitionToState->addItem(tr("none"));
236 ui->transitionToState->addItem(tr("Base State"));
237 if (!m_animation.isValid())
238 return;
239 QmlObjectNode rootNode = QmlObjectNode(m_animation.view()->rootModelNode());
240 if (rootNode.isValid() && rootNode.modelNode().hasId()) {
241 for (const QmlModelState &state : QmlVisualNode(rootNode).states().allStates()) {
242 ui->transitionToState
243 ->addItem(state.modelNode().variantProperty("name").value().toString(),
244 QVariant::fromValue<ModelNode>(state.modelNode()));
245 }
246 if (m_animation.signalHandlerProperty("onFinished").isValid()) {
247 const QString source = m_animation.signalHandlerProperty("onFinished").source();
248 const QStringList list = source.split("=");
249 if (list.count() == 2) {
250 QString name = list.last().trimmed();
251 name.chop(1);
252 name.remove(0, 1);
253 if (name.isEmpty())
254 ui->transitionToState->setCurrentIndex(1);
255 else
256 ui->transitionToState->setCurrentText(name);
257 }
258 }
259 }
260 }
261
262 } // namespace QmlDesigner
263