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 "timelinesettingsmodel.h"
27
28 #include "timelineview.h"
29
30 #include <modelnode.h>
31 #include <variantproperty.h>
32 #include <qmlvisualnode.h>
33
34 #include <utils/optional.h>
35 #include <utils/qtcassert.h>
36
37 #include <QComboBox>
38 #include <QItemEditorFactory>
39 #include <QMessageBox>
40 #include <QSpinBox>
41 #include <QStyledItemDelegate>
42 #include <QTimer>
43
44 namespace QmlDesigner {
45
setDataForFixedFrame(QStandardItem * item,Utils::optional<int> fixedValue)46 static void setDataForFixedFrame(QStandardItem *item, Utils::optional<int> fixedValue)
47 {
48 if (fixedValue)
49 item->setData(fixedValue.value(), Qt::EditRole);
50 else
51 item->setData(TimelineSettingsModel::tr("None"), Qt::EditRole);
52 }
53
54 class CustomDelegate : public QStyledItemDelegate
55 {
56 public:
57 explicit CustomDelegate(QWidget *parent = nullptr);
58 void paint(QPainter *painter,
59 const QStyleOptionViewItem &option,
60 const QModelIndex &index) const override;
61 };
62
CustomDelegate(QWidget * parent)63 CustomDelegate::CustomDelegate(QWidget *parent)
64 : QStyledItemDelegate(parent)
65 {}
66
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const67 void CustomDelegate::paint(QPainter *painter,
68 const QStyleOptionViewItem &option,
69 const QModelIndex &index) const
70 {
71 QStyleOptionViewItem opt = option;
72 opt.state &= ~QStyle::State_HasFocus;
73 QStyledItemDelegate::paint(painter, opt, index);
74 }
75
76 class TimelineEditorDelegate : public CustomDelegate
77 {
78 public:
79 TimelineEditorDelegate(QWidget *parent = nullptr);
80 QWidget *createEditor(QWidget *parent,
81 const QStyleOptionViewItem &option,
82 const QModelIndex &index) const override;
83 };
84
TimelineEditorDelegate(QWidget * parent)85 TimelineEditorDelegate::TimelineEditorDelegate(QWidget *parent)
86 : CustomDelegate(parent)
87 {
88 static QItemEditorFactory *factory = nullptr;
89 if (factory == nullptr) {
90 factory = new QItemEditorFactory;
91 QItemEditorCreatorBase *creator = new QItemEditorCreator<QComboBox>("currentText");
92 factory->registerEditor(QVariant::String, creator);
93 }
94
95 setItemEditorFactory(factory);
96 }
97
createSpinBox(QWidget * parent)98 QSpinBox *createSpinBox(QWidget *parent)
99 {
100 auto spinBox = new QSpinBox(parent);
101 spinBox->setRange(-10000, 10000);
102 return spinBox;
103 }
104
createEditor(QWidget * parent,const QStyleOptionViewItem & option,const QModelIndex & index) const105 QWidget *TimelineEditorDelegate::createEditor(QWidget *parent,
106 const QStyleOptionViewItem &option,
107 const QModelIndex &index) const
108 {
109 QWidget *widget = nullptr;
110
111 if (index.column() == TimelineSettingsModel::FixedFrameRow)
112 widget = createSpinBox(parent);
113 else
114 widget = QStyledItemDelegate::createEditor(parent, option, index);
115
116 const auto timelineSettingsModel = qobject_cast<const TimelineSettingsModel *>(index.model());
117
118 auto comboBox = qobject_cast<QComboBox *>(widget);
119
120 QTC_ASSERT(timelineSettingsModel, return widget);
121 QTC_ASSERT(timelineSettingsModel->timelineView(), return widget);
122
123 QmlTimeline qmlTimeline = timelineSettingsModel->timelineForRow(index.row());
124
125 switch (index.column()) {
126 case TimelineSettingsModel::TimelineRow: {
127 QTC_ASSERT(comboBox, return widget);
128 comboBox->addItem(TimelineSettingsModel::tr("None"));
129 for (const auto &timeline : timelineSettingsModel->timelineView()->getTimelines()) {
130 if (!timeline.modelNode().id().isEmpty())
131 comboBox->addItem(timeline.modelNode().id());
132 }
133 } break;
134 case TimelineSettingsModel::AnimationRow: {
135 QTC_ASSERT(comboBox, return widget);
136 comboBox->addItem(TimelineSettingsModel::tr("None"));
137 for (const auto &animation :
138 timelineSettingsModel->timelineView()->getAnimations(qmlTimeline)) {
139 if (!animation.id().isEmpty())
140 comboBox->addItem(animation.id());
141 }
142 } break;
143 case TimelineSettingsModel::FixedFrameRow: {
144 } break;
145
146 default:
147 qWarning() << "TimelineEditorDelegate::createEditor column" << index.column();
148 }
149
150 if (comboBox) {
151 connect(comboBox, QOverload<int>::of(&QComboBox::activated), this, [=]() {
152 auto delegate = const_cast<TimelineEditorDelegate *>(this);
153 emit delegate->commitData(comboBox);
154 });
155 }
156
157 return widget;
158 }
159
TimelineSettingsModel(QObject * parent,TimelineView * view)160 TimelineSettingsModel::TimelineSettingsModel(QObject *parent, TimelineView *view)
161 : QStandardItemModel(parent)
162 , m_timelineView(view)
163 {
164 connect(this, &QStandardItemModel::dataChanged, this, &TimelineSettingsModel::handleDataChanged);
165 }
166
resetModel()167 void TimelineSettingsModel::resetModel()
168 {
169 beginResetModel();
170 clear();
171 setHorizontalHeaderLabels(
172 QStringList({tr("State"), tr("Timeline"), tr("Animation"), tr("Fixed Frame")}));
173
174 if (timelineView()->isAttached()) {
175 addState(ModelNode());
176 QmlVisualNode visNode(timelineView()->rootModelNode());
177 if (visNode.isValid()) {
178 const auto allStates = visNode.states().allStates();
179 for (const QmlModelState &state : allStates)
180 addState(state);
181 }
182 }
183
184 endResetModel();
185 }
186
timelineView() const187 TimelineView *TimelineSettingsModel::timelineView() const
188 {
189 return m_timelineView;
190 }
191
setupDelegates(QAbstractItemView * view)192 void TimelineSettingsModel::setupDelegates(QAbstractItemView *view)
193 {
194 view->setItemDelegate(new TimelineEditorDelegate);
195 }
196
propertyValueForState(const ModelNode & modelNode,QmlModelState state,const PropertyName & propertyName)197 static Utils::optional<int> propertyValueForState(const ModelNode &modelNode,
198 QmlModelState state,
199 const PropertyName &propertyName)
200 {
201 if (!modelNode.isValid())
202 return {};
203
204 if (state.isBaseState()) {
205 if (modelNode.hasVariantProperty(propertyName))
206 return modelNode.variantProperty(propertyName).value().toInt();
207 return {};
208 }
209
210 if (state.hasPropertyChanges(modelNode)) {
211 QmlPropertyChanges propertyChanges(state.propertyChanges(modelNode));
212 if (propertyChanges.modelNode().hasVariantProperty(propertyName))
213 return propertyChanges.modelNode().variantProperty(propertyName).value().toInt();
214 }
215
216 return {};
217 }
218
createStateItem(const ModelNode & state)219 static QStandardItem *createStateItem(const ModelNode &state)
220 {
221 if (state.isValid())
222 return new QStandardItem(state.variantProperty("name").value().toString());
223 else
224 return new QStandardItem(TimelineSettingsModel::tr("Base State"));
225 }
226
addState(const ModelNode & state)227 void TimelineSettingsModel::addState(const ModelNode &state)
228 {
229 QList<QStandardItem *> items;
230
231 QmlTimeline timeline = timelineView()->timelineForState(state);
232 const QString timelineId = timeline.isValid() ? timeline.modelNode().id() : QString("");
233 ModelNode animation = animationForTimelineAndState(timeline, state);
234 const QString animationId = animation.isValid() ? animation.id() : QString("");
235
236 QStandardItem *stateItem = createStateItem(state);
237 auto *timelinelItem = new QStandardItem(timelineId);
238 auto *animationItem = new QStandardItem(animationId);
239 auto *fixedFrameItem = new QStandardItem("");
240
241 stateItem->setData(state.internalId());
242 stateItem->setFlags(Qt::ItemIsEnabled);
243
244 auto fixedValue = propertyValueForState(timeline, state, "currentFrame");
245 setDataForFixedFrame(fixedFrameItem, fixedValue);
246
247 items.append(stateItem);
248 items.append(timelinelItem);
249 items.append(animationItem);
250 items.append(fixedFrameItem);
251
252 appendRow(items);
253 }
254
handleException()255 void TimelineSettingsModel::handleException()
256 {
257 QMessageBox::warning(nullptr, tr("Error"), m_exceptionError);
258 resetModel();
259 }
260
animationForTimelineAndState(const QmlTimeline & timeline,const ModelNode & state)261 ModelNode TimelineSettingsModel::animationForTimelineAndState(const QmlTimeline &timeline,
262 const ModelNode &state)
263 {
264 QmlModelState modelState(state);
265
266 if (!timeline.isValid())
267 return ModelNode();
268
269 const QList<ModelNode> &animations = timelineView()->getAnimations(timeline);
270
271 if (modelState.isBaseState()) {
272 for (const auto &animation : animations) {
273 if (animation.hasVariantProperty("running")
274 && animation.variantProperty("running").value().toBool())
275 return animation;
276 }
277 return ModelNode();
278 }
279
280 for (const auto &animation : animations) {
281 if (modelState.affectsModelNode(animation)) {
282 QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
283
284 if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running")
285 && propertyChanges.modelNode().variantProperty("running").value().toBool())
286 return animation;
287 }
288 }
289 return ModelNode();
290 }
291
updateTimeline(int row)292 void TimelineSettingsModel::updateTimeline(int row)
293 {
294
295 timelineView()->executeInTransaction("TimelineSettingsModel::updateTimeline", [this, row](){
296 QmlModelState modelState(stateForRow(row));
297 QmlTimeline timeline(timelineForRow(row));
298 ModelNode animation(animationForRow(row));
299 QmlTimeline oldTimeline = timelineView()->timelineForState(modelState);
300
301 if (modelState.isBaseState()) {
302 if (oldTimeline.isValid())
303 oldTimeline.modelNode().variantProperty("enabled").setValue(false);
304 if (timeline.isValid())
305 timeline.modelNode().variantProperty("enabled").setValue(true);
306 } else {
307 if (oldTimeline.isValid() && modelState.affectsModelNode(oldTimeline)) {
308 QmlPropertyChanges propertyChanges(modelState.propertyChanges(oldTimeline));
309 if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("enabled"))
310 propertyChanges.modelNode().removeProperty("enabled");
311 }
312
313 QmlTimeline baseTimeline(timelineForRow(0));
314
315 if (baseTimeline.isValid()) {
316 QmlPropertyChanges propertyChanges(modelState.propertyChanges(baseTimeline));
317 if (propertyChanges.isValid())
318 propertyChanges.modelNode().variantProperty("enabled").setValue(false);
319 }
320
321 if (timeline.isValid()) { /* If timeline is invalid 'none' was selected */
322 QmlPropertyChanges propertyChanges(modelState.propertyChanges(timeline));
323 if (propertyChanges.isValid())
324 propertyChanges.modelNode().variantProperty("enabled").setValue(true);
325 }
326 }
327 });
328
329 resetRow(row);
330 }
331
updateAnimation(int row)332 void TimelineSettingsModel::updateAnimation(int row)
333 {
334 timelineView()->executeInTransaction("TimelineSettingsModel::updateAnimation", [this, row](){
335 QmlModelState modelState(stateForRow(row));
336 QmlTimeline timeline(timelineForRow(row));
337 ModelNode animation(animationForRow(row));
338 QmlTimeline oldTimeline = timelineView()->timelineForState(modelState);
339 ModelNode oldAnimation = animationForTimelineAndState(oldTimeline, modelState);
340
341 if (modelState.isBaseState()) {
342 if (oldAnimation.isValid())
343 oldAnimation.variantProperty("running").setValue(false);
344 if (animation.isValid())
345 animation.variantProperty("running").setValue(true);
346 if (timeline.isValid() && timeline.modelNode().hasProperty("currentFrame"))
347 timeline.modelNode().removeProperty("currentFrame");
348 } else {
349 if (oldAnimation.isValid() && modelState.affectsModelNode(oldAnimation)) {
350 QmlPropertyChanges propertyChanges(modelState.propertyChanges(oldAnimation));
351 if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running"))
352 propertyChanges.modelNode().removeProperty("running");
353 }
354
355 ModelNode baseAnimation(animationForRow(0));
356
357 if (baseAnimation.isValid()) {
358 QmlPropertyChanges propertyChanges(modelState.propertyChanges(baseAnimation));
359 if (propertyChanges.isValid()) {
360 propertyChanges.modelNode().variantProperty("running").setValue(false);
361 if (propertyChanges.modelNode().hasProperty("currentFrame"))
362 propertyChanges.modelNode().removeProperty("currentFrame");
363 }
364 }
365
366 if (animation.isValid()) { /* If animation is invalid 'none' was selected */
367 QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
368 if (propertyChanges.isValid())
369 propertyChanges.modelNode().variantProperty("running").setValue(true);
370 }
371 }
372 });
373 resetRow(row);
374 }
375
updateFixedFrameRow(int row)376 void TimelineSettingsModel::updateFixedFrameRow(int row)
377 {
378 timelineView()->executeInTransaction("TimelineSettingsModel::updateFixedFrameRow", [this, row](){
379 QmlModelState modelState(stateForRow(row));
380 QmlTimeline timeline(timelineForRow(row));
381
382 ModelNode animation = animationForTimelineAndState(timeline, modelState);
383
384 int fixedFrame = fixedFrameForRow(row);
385
386 if (modelState.isBaseState()) {
387 if (animation.isValid())
388 animation.variantProperty("running").setValue(false);
389 if (timeline.isValid())
390 timeline.modelNode().variantProperty("currentFrame").setValue(fixedFrame);
391 } else {
392 if (animation.isValid() && modelState.affectsModelNode(animation)) {
393 QmlPropertyChanges propertyChanges(modelState.propertyChanges(animation));
394 if (propertyChanges.isValid() && propertyChanges.modelNode().hasProperty("running"))
395 propertyChanges.modelNode().removeProperty("running");
396 }
397
398 QmlPropertyChanges propertyChanges(modelState.propertyChanges(timeline));
399 if (propertyChanges.isValid())
400 propertyChanges.modelNode().variantProperty("currentFrame").setValue(fixedFrame);
401 }
402
403 });
404
405 resetRow(row);
406 }
407
resetRow(int row)408 void TimelineSettingsModel::resetRow(int row)
409 {
410 m_lock = true;
411 QStandardItem *animationItem = item(row, AnimationRow);
412 QStandardItem *fixedFrameItem = item(row, FixedFrameRow);
413
414 QmlModelState modelState(stateForRow(row));
415 QmlTimeline timeline(timelineForRow(row));
416 ModelNode animation = animationForTimelineAndState(timeline, modelState);
417
418 if (animationItem) {
419 const QString animationId = animation.isValid() ? animation.id() : QString();
420 animationItem->setText(animationId);
421 }
422
423 if (fixedFrameItem) {
424 auto fixedValue = propertyValueForState(timeline, modelState, "currentFrame");
425 if (fixedFrameItem->data(Qt::EditRole).toInt() != fixedValue)
426 setDataForFixedFrame(fixedFrameItem, fixedValue);
427 }
428
429 m_lock = false;
430 }
431
timelineForRow(int row) const432 QmlTimeline TimelineSettingsModel::timelineForRow(int row) const
433 {
434 QStandardItem *standardItem = item(row, TimelineRow);
435
436 if (standardItem)
437 return QmlTimeline(timelineView()->modelNodeForId(standardItem->text()));
438
439 return QmlTimeline();
440 }
441
animationForRow(int row) const442 ModelNode TimelineSettingsModel::animationForRow(int row) const
443 {
444 QStandardItem *standardItem = item(row, AnimationRow);
445
446 if (standardItem)
447 return timelineView()->modelNodeForId(standardItem->text());
448
449 return ModelNode();
450 }
451
stateForRow(int row) const452 ModelNode TimelineSettingsModel::stateForRow(int row) const
453 {
454 QStandardItem *standardItem = item(row, StateRow);
455
456 if (standardItem)
457 return timelineView()->modelNodeForInternalId(standardItem->data().toInt());
458
459 return ModelNode();
460 }
461
fixedFrameForRow(int row) const462 int TimelineSettingsModel::fixedFrameForRow(int row) const
463 {
464 QStandardItem *standardItem = item(row, FixedFrameRow);
465
466 if (standardItem)
467 return standardItem->data(Qt::EditRole).toInt();
468
469 return -1;
470 }
471
handleDataChanged(const QModelIndex & topLeft,const QModelIndex & bottomRight)472 void TimelineSettingsModel::handleDataChanged(const QModelIndex &topLeft,
473 const QModelIndex &bottomRight)
474 {
475 if (topLeft != bottomRight) {
476 qWarning() << "TimelineSettingsModel::handleDataChanged multi edit?";
477 return;
478 }
479
480 if (m_lock)
481 return;
482
483 m_lock = true;
484
485 int currentColumn = topLeft.column();
486 int currentRow = topLeft.row();
487
488 switch (currentColumn) {
489 case StateRow: {
490 /* read only */
491 } break;
492 case TimelineRow: {
493 updateTimeline(currentRow);
494 } break;
495 case AnimationRow: {
496 updateAnimation(currentRow);
497 } break;
498 case FixedFrameRow: {
499 updateFixedFrameRow(currentRow);
500 } break;
501
502 default:
503 qWarning() << "ConnectionModel::handleDataChanged column" << currentColumn;
504 }
505
506 m_lock = false;
507 }
508
509 } // namespace QmlDesigner
510