1 /*
2  *  Copyright (c) 2016 Jouni Pentikäinen <joupent@gmail.com>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_animation_curves_model.h"
20 
21 #include <QAbstractItemModel>
22 
23 #include "kis_global.h"
24 #include "kis_image.h"
25 #include "kis_node.h"
26 #include "kis_keyframe_channel.h"
27 #include "kis_scalar_keyframe_channel.h"
28 #include "kis_post_execution_undo_adapter.h"
29 #include "kis_animation_utils.h"
30 #include "kis_processing_applicator.h"
31 #include "kis_command_utils.h"
32 #include "KisImageBarrierLockerWithFeedback.h"
33 
34 struct KisAnimationCurve::Private
35 {
PrivateKisAnimationCurve::Private36     Private(KisScalarKeyframeChannel *channel, QColor color)
37         : channel(channel)
38         , color(color)
39         , visible(true)
40     {}
41 
42     KisScalarKeyframeChannel *channel;
43     QColor color;
44     bool visible;
45 };
46 
KisAnimationCurve(KisScalarKeyframeChannel * channel,QColor color)47 KisAnimationCurve::KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color)
48     : m_d(new Private(channel, color))
49 {}
50 
channel() const51 KisScalarKeyframeChannel *KisAnimationCurve::channel() const
52 {
53     return m_d->channel;
54 }
55 
color() const56 QColor KisAnimationCurve::color() const
57 {
58     return m_d->color;
59 }
60 
setVisible(bool visible)61 void KisAnimationCurve::setVisible(bool visible)
62 {
63     m_d->visible = visible;
64 }
65 
visible() const66 bool KisAnimationCurve::visible() const
67 {
68     return m_d->visible;
69 }
70 
71 struct KisAnimationCurvesModel::Private
72 {
73     QList<KisAnimationCurve*> curves;
74     int nextColorHue;
75     KUndo2Command *undoCommand;
76 
PrivateKisAnimationCurvesModel::Private77     Private()
78         : nextColorHue(0)
79         , undoCommand(0)
80     {}
81 
getCurveAtKisAnimationCurvesModel::Private82     KisAnimationCurve *getCurveAt(const QModelIndex& index) {
83 
84         if (!index.isValid()) return 0;
85 
86         int row = index.row();
87 
88         if (row < 0 || row >= curves.size()) {
89             return 0;
90         }
91 
92         return curves.at(row);
93     }
94 
rowForCurveKisAnimationCurvesModel::Private95     int rowForCurve(KisAnimationCurve *curve) {
96         return curves.indexOf(curve);
97     }
98 
rowForChannelKisAnimationCurvesModel::Private99     int rowForChannel(KisKeyframeChannel *channel) {
100         for (int row = 0; row < curves.count(); row++) {
101             if (curves.at(row)->channel() == channel) return row;
102         }
103 
104         return -1;
105     }
106 
chooseNextColorKisAnimationCurvesModel::Private107     QColor chooseNextColor() {
108         if (curves.isEmpty()) nextColorHue = 0;
109 
110         QColor color = QColor::fromHsv(nextColorHue, 255, 255);
111         nextColorHue += 94; // Value chosen experimentally for providing distinct colors
112         nextColorHue = nextColorHue & 0xff;
113         return color;
114     }
115 };
116 
KisAnimationCurvesModel(QObject * parent)117 KisAnimationCurvesModel::KisAnimationCurvesModel(QObject *parent)
118     : KisTimeBasedItemModel(parent)
119     , m_d(new Private())
120 {}
121 
~KisAnimationCurvesModel()122 KisAnimationCurvesModel::~KisAnimationCurvesModel()
123 {
124     qDeleteAll(m_d->curves);
125 }
126 
rowCount(const QModelIndex & parent) const127 int KisAnimationCurvesModel::rowCount(const QModelIndex &parent) const
128 {
129     Q_UNUSED(parent);
130     return m_d->curves.size();
131 }
132 
data(const QModelIndex & index,int role) const133 QVariant KisAnimationCurvesModel::data(const QModelIndex &index, int role) const
134 {
135     KisAnimationCurve *curve = m_d->getCurveAt(index);
136 
137     if (curve) {
138         KisScalarKeyframeChannel *channel = curve->channel();
139 
140         int time = index.column();
141         KisKeyframeSP keyframe = channel->keyframeAt(time);
142 
143         switch (role) {
144         case SpecialKeyframeExists:
145             return !keyframe.isNull();
146         case ScalarValueRole:
147             return channel->interpolatedValue(time);
148         case LeftTangentRole:
149             return (keyframe.isNull()) ? QVariant() : keyframe->leftTangent();
150         case RightTangentRole:
151             return (keyframe.isNull()) ? QVariant() : keyframe->rightTangent();
152         case InterpolationModeRole:
153             return (keyframe.isNull()) ? QVariant() : keyframe->interpolationMode();
154         case TangentsModeRole:
155             return (keyframe.isNull()) ? QVariant() : keyframe->tangentsMode();
156         case CurveColorRole:
157             return curve->color();
158         case CurveVisibleRole:
159             return curve->visible();
160         case PreviousKeyframeTime:
161         {
162             KisKeyframeSP active = channel->activeKeyframeAt(time);
163             if (active.isNull()) return QVariant();
164             if (active->time() < time) {
165                 return active->time();
166             }
167             KisKeyframeSP previous = channel->previousKeyframe(active);
168             if (previous.isNull()) return QVariant();
169             return previous->time();
170         }
171         case NextKeyframeTime:
172         {
173             KisKeyframeSP active = channel->activeKeyframeAt(time);
174             if (active.isNull()) {
175                 KisKeyframeSP first = channel->firstKeyframe();
176                 if (!first.isNull() && first->time() > time) {
177                     return first->time();
178                 }
179                 return QVariant();
180             }
181             KisKeyframeSP next = channel->nextKeyframe(active);
182             if (next.isNull()) return QVariant();
183             return next->time();
184         }
185         default:
186             break;
187         }
188     }
189 
190     return KisTimeBasedItemModel::data(index, role);
191 }
192 
setData(const QModelIndex & index,const QVariant & value,int role)193 bool KisAnimationCurvesModel::setData(const QModelIndex &index, const QVariant &value, int role)
194 {
195     if (!index.isValid()) return false;
196     KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
197     KUndo2Command *command = m_d->undoCommand;
198 
199     switch (role) {
200     case ScalarValueRole:
201     {
202         KisKeyframeSP keyframe = channel->keyframeAt(index.column());
203         if (keyframe) {
204             if (!command) command = new KUndo2Command(kundo2_i18n("Adjust keyframe"));
205             channel->setScalarValue(keyframe, value.toReal(), command);
206         } else {
207             if (!command) command = new KUndo2Command(kundo2_i18n("Insert keyframe"));
208             auto *addKeyframeCommand = new KisScalarKeyframeChannel::AddKeyframeCommand(
209                 channel, index.column(), value.toReal(), command);
210             addKeyframeCommand->redo();
211         }
212     }
213         break;
214     case LeftTangentRole:
215     case RightTangentRole:
216     {
217         KisKeyframeSP keyframe = channel->keyframeAt(index.column());
218         if (!keyframe) return false;
219 
220         QPointF leftTangent = (role == LeftTangentRole ? value.toPointF() : keyframe->leftTangent());
221         QPointF rightTangent = (role == RightTangentRole ? value.toPointF() : keyframe->rightTangent());
222 
223         if (!command) command = new KUndo2Command(kundo2_i18n("Adjust tangent"));
224         channel->setInterpolationTangents(keyframe, keyframe->tangentsMode(), leftTangent, rightTangent, command);
225     }
226         break;
227     case InterpolationModeRole:
228     {
229         KisKeyframeSP keyframe = channel->keyframeAt(index.column());
230         if (!keyframe) return false;
231 
232         if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode"));
233         channel->setInterpolationMode(keyframe, (KisKeyframe::InterpolationMode)value.toInt(), command);
234     }
235         break;
236     case TangentsModeRole:
237     {
238         KisKeyframeSP keyframe = channel->keyframeAt(index.column());
239         if (!keyframe) return false;
240 
241         KisKeyframe::InterpolationTangentsMode mode = (KisKeyframe::InterpolationTangentsMode)value.toInt();
242         QPointF leftTangent = keyframe->leftTangent();
243         QPointF rightTangent = keyframe->rightTangent();
244 
245         if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode"));
246         channel->setInterpolationTangents(keyframe, mode, leftTangent, rightTangent, command);
247     }
248         break;
249     default:
250         return KisTimeBasedItemModel::setData(index, value, role);
251     }
252 
253     if (command && !m_d->undoCommand) {
254         image()->postExecutionUndoAdapter()->addCommand(toQShared(command));
255     }
256 
257     return true;
258 }
259 
headerData(int section,Qt::Orientation orientation,int role) const260 QVariant KisAnimationCurvesModel::headerData(int section, Qt::Orientation orientation, int role) const
261 {
262     // TODO
263     return KisTimeBasedItemModel::headerData(section, orientation, role);
264 }
265 
beginCommand(const KUndo2MagicString & text)266 void KisAnimationCurvesModel::beginCommand(const KUndo2MagicString &text)
267 {
268     KIS_ASSERT_RECOVER_RETURN(!m_d->undoCommand);
269     m_d->undoCommand = new KUndo2Command(text);
270 }
271 
endCommand()272 void KisAnimationCurvesModel::endCommand()
273 {
274     KIS_ASSERT_RECOVER_RETURN(m_d->undoCommand);
275     image()->postExecutionUndoAdapter()->addCommand(toQShared(m_d->undoCommand));
276 
277     m_d->undoCommand = 0;
278 }
279 
280 
adjustKeyframes(const QModelIndexList & indexes,int timeOffset,qreal valueOffset)281 bool KisAnimationCurvesModel::adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset)
282 {
283     QScopedPointer<KUndo2Command> command(
284         new KUndo2Command(
285             kundo2_i18np("Adjust Keyframe",
286                          "Adjust %1 Keyframes",
287                          indexes.size())));
288 
289     {
290         KisImageBarrierLockerWithFeedback locker(image());
291 
292         if (timeOffset != 0) {
293             bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, false, command.data());
294             if (!ok) return false;
295         }
296 
297         using KisAnimationUtils::FrameItem;
298         using KisAnimationUtils::FrameItemList;
299         FrameItemList frameItems;
300 
301         Q_FOREACH(QModelIndex index, indexes) {
302             KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
303             KIS_ASSERT_RECOVER_RETURN_VALUE(channel, false);
304 
305             frameItems << FrameItem(channel->node(),
306                                     channel->id(),
307                                     index.column() + timeOffset);
308         };
309 
310         new KisCommandUtils::LambdaCommand(
311             command.data(),
312             [frameItems, valueOffset] () -> KUndo2Command* {
313 
314                 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
315 
316                 bool result = false;
317 
318                 Q_FOREACH (const FrameItem &item, frameItems) {
319                     const int time = item.time;
320                     KisNodeSP node = item.node;
321 
322                     KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel);
323 
324                     if (!channel) continue;
325 
326                     KisKeyframeSP keyframe = channel->keyframeAt(time);
327                     if (!keyframe) continue;
328 
329                     const qreal currentValue = channel->scalarValue(keyframe);
330                     channel->setScalarValue(keyframe, currentValue + valueOffset, cmd.data());
331                     result = true;
332                 }
333 
334                 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0;
335         });
336     }
337 
338     KisProcessingApplicator::runSingleCommandStroke(image(), command.take(),
339                                                     KisStrokeJobData::BARRIER,
340                                                     KisStrokeJobData::EXCLUSIVE);
341 
342     return true;
343 }
344 
addCurve(KisScalarKeyframeChannel * channel)345 KisAnimationCurve *KisAnimationCurvesModel::addCurve(KisScalarKeyframeChannel *channel)
346 {
347     beginInsertRows(QModelIndex(), m_d->curves.size(), m_d->curves.size());
348 
349     KisAnimationCurve *curve = new KisAnimationCurve(channel, m_d->chooseNextColor());
350     m_d->curves.append(curve);
351 
352     endInsertRows();
353 
354     connect(channel, &KisScalarKeyframeChannel::sigKeyframeAdded,
355             this, &KisAnimationCurvesModel::slotKeyframeChanged);
356 
357     connect(channel, &KisScalarKeyframeChannel::sigKeyframeMoved,
358             this, &KisAnimationCurvesModel::slotKeyframeChanged);
359 
360     connect(channel, &KisScalarKeyframeChannel::sigKeyframeRemoved,
361             this, &KisAnimationCurvesModel::slotKeyframeChanged);
362 
363     connect(channel, &KisScalarKeyframeChannel::sigKeyframeChanged,
364             this, &KisAnimationCurvesModel::slotKeyframeChanged);
365 
366     return curve;
367 }
368 
removeCurve(KisAnimationCurve * curve)369 void KisAnimationCurvesModel::removeCurve(KisAnimationCurve *curve)
370 {
371     int index = m_d->curves.indexOf(curve);
372     if (index < 0) return;
373 
374     curve->channel()->disconnect(this);
375 
376     beginRemoveRows(QModelIndex(), index, index);
377 
378     m_d->curves.removeAt(index);
379     delete curve;
380 
381     endRemoveRows();
382 }
383 
setCurveVisible(KisAnimationCurve * curve,bool visible)384 void KisAnimationCurvesModel::setCurveVisible(KisAnimationCurve *curve, bool visible)
385 {
386     curve->setVisible(visible);
387 
388     int row = m_d->rowForCurve(curve);
389     emit dataChanged(index(row, 0), index(row, columnCount()));
390 }
391 
nodeAt(QModelIndex index) const392 KisNodeSP KisAnimationCurvesModel::nodeAt(QModelIndex index) const
393 {
394     KisAnimationCurve *curve = m_d->getCurveAt(index);
395     if (curve && curve->channel() && curve->channel()->node()) {
396         return KisNodeSP(curve->channel()->node());
397     }
398     return 0;
399 }
400 
channelsAt(QModelIndex index) const401 QMap<QString, KisKeyframeChannel *> KisAnimationCurvesModel::channelsAt(QModelIndex index) const
402 {
403     KisKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
404     QMap<QString, KisKeyframeChannel*> list;
405     list[""] = channel;
406     return list;
407 }
408 
slotKeyframeChanged(KisKeyframeSP keyframe)409 void KisAnimationCurvesModel::slotKeyframeChanged(KisKeyframeSP keyframe)
410 {
411     int row = m_d->rowForChannel(keyframe->channel());
412     QModelIndex changedIndex = index(row, keyframe->time());
413     emit dataChanged(changedIndex, changedIndex);
414 }
415