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