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