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