Krita Source Code Documentation
Loading...
Searching...
No Matches
KisAnimCurvesModel.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2016 Jouni Pentikäinen <joupent@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
8
9#include <QAbstractItemModel>
10
11#include "kis_global.h"
12#include "kis_image.h"
13#include "kis_node.h"
17#include "KisAnimUtils.h"
19#include "kis_command_utils.h"
20#include "KisImageBarrierLock.h"
21
34
36 : m_d(new Private(channel, color))
37{}
38
40{
41 return m_d->channel;
42}
43
45{
46 return m_d->color;
47}
48
50{
51 m_d->visible = visible;
52}
53
55{
56 return m_d->visible;
57}
58
60{
64
66 : nextColorHue(0)
67 , undoCommand(0)
68 {}
69
70 KisAnimationCurve *getCurveAt(const QModelIndex& index) {
71
72 if (!index.isValid()) return 0;
73
74 int row = index.row();
75
76 if (row < 0 || row >= curves.size()) {
77 return 0;
78 }
79
80 return curves.at(row);
81 }
82
84 return curves.indexOf(curve);
85 }
86
87 int rowForChannel(const KisKeyframeChannel *channel) {
88 for (int row = 0; row < curves.count(); row++) {
89 if (curves.at(row)->channel() == channel) return row;
90 }
91
92 return -1;
93 }
94
95 QColor chooseNextColor() {
96 if (curves.isEmpty()) nextColorHue = 0;
97
98 QColor color = QColor::fromHsv(nextColorHue, 255, 255);
99 nextColorHue += 94; // Value chosen experimentally for providing distinct colors
100 nextColorHue = nextColorHue & 0xff;
101 return color;
102 }
103};
104
106 : KisTimeBasedItemModel(parent)
107 , m_d(new Private())
108{}
109
111{
112 qDeleteAll(m_d->curves);
113}
114
115int KisAnimCurvesModel::rowCount(const QModelIndex &parent) const
116{
117 Q_UNUSED(parent);
118 return m_d->curves.size();
119}
120
121QVariant KisAnimCurvesModel::data(const QModelIndex &index, int role) const
122{
123 KisAnimationCurve *curve = m_d->getCurveAt(index);
124
125 if (curve) {
126 KisScalarKeyframeChannel *channel = curve->channel();
127 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(channel, QVariant());
128
129 const int time = index.column();
130 KisScalarKeyframeSP keyframe = channel->keyframeAt(time).dynamicCast<KisScalarKeyframe>();
131
132 switch (role) {
134 return !keyframe.isNull();
135 case ScalarValueRole:
136 return channel->valueAt(time);
137 case LeftTangentRole: {
138 if (keyframe.isNull())
139 return QVariant();
140
141 const int previousKeyTime = channel->previousKeyframeTime(time);
142 KisScalarKeyframeSP previousKeyframe = channel->keyframeAt(previousKeyTime).dynamicCast<KisScalarKeyframe>();
143
144 //Tangent null whenever there's no previous keyframe.
145 if (!previousKeyframe)
146 return QVariant();
147
148 //Tangent should be null if previous keyframe not set to bezier.
149 if (previousKeyframe->interpolationMode() != KisScalarKeyframe::Bezier)
150 return QVariant();
151
152 return keyframe->leftTangent();
153 }
154 case RightTangentRole: {
155 if (keyframe.isNull())
156 return QVariant();
157
158 if (keyframe->interpolationMode() != KisScalarKeyframe::Bezier)
159 return QVariant();
160
161 return keyframe->rightTangent();
162 }
164 return (keyframe.isNull()) ? QVariant() : keyframe->interpolationMode();
165 case TangentsModeRole:
166 return (keyframe.isNull()) ? QVariant() : keyframe->tangentsMode();
167 case CurveColorRole:
168 return curve->color();
169 case CurveVisibleRole:
170 return curve->visible();
172 {
173 int activeKeyframeIndex = channel->activeKeyframeTime(time);
174 if (!channel->keyframeAt(activeKeyframeIndex)) return QVariant();
175 if (activeKeyframeIndex < time) {
176 return activeKeyframeIndex;
177 }
178
179 int previousKeyframeIndex = channel->previousKeyframeTime(activeKeyframeIndex);
180 if (!channel->keyframeAt(previousKeyframeIndex)) return QVariant();
181 return previousKeyframeIndex;
182 }
183 case NextKeyframeTime:
184 {
185 int activeKeyframeIndex = channel->activeKeyframeTime(time);
186 if (!channel->keyframeAt(activeKeyframeIndex)) {
187 int firstKeyIndex = channel->firstKeyframeTime();
188 if (firstKeyIndex != -1 && firstKeyIndex > time) {
189 return firstKeyIndex;
190 }
191 return QVariant();
192 }
193
194 int nextKeyframeIndex = channel->nextKeyframeTime(activeKeyframeIndex);
195 if (!channel->keyframeAt(nextKeyframeIndex)) return QVariant();
196 return nextKeyframeIndex;
197 }
199 return channel->id();
200 case ChannelLimits:
201 {
203
204 if (!limits)
205 return QVariant();
206
207 return QVariant::fromValue<ChannelLimitsMetatype>( ChannelLimitsMetatype(limits->lower, limits->upper) );
208 }
209 default:
210 break;
211 }
212 }
213
214 return KisTimeBasedItemModel::data(index, role);
215}
216
217bool KisAnimCurvesModel::setData(const QModelIndex &index, const QVariant &value, int role)
218{
219 if (!index.isValid())
220 return false;
221
222 KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
223 KUndo2Command *command = m_d->undoCommand;
224
225 switch (role) {
226 case ScalarValueRole:
227 {
228 if (channel->keyframeAt(index.column())) {
229
230 if (!command) command = new KUndo2Command(kundo2_i18n("Adjust keyframe"));
231 channel->keyframeAt<KisScalarKeyframe>(index.column())->setValue(value.toReal(), command);
232 } else {
233
234 if (!command) command = new KUndo2Command(kundo2_i18n("Insert keyframe"));
235 channel->addScalarKeyframe(index.column(), value.toReal(), command);
236 }
237 dataChanged(index,index);
238 }
239 break;
240 case LeftTangentRole:
241 case RightTangentRole:
242 {
243 KisScalarKeyframeSP keyframe = channel->keyframeAt<KisScalarKeyframe>(index.column());
244 if (!keyframe) return false;
245
246 QPointF leftTangent = (role == LeftTangentRole ? value.toPointF() : keyframe->leftTangent());
247 QPointF rightTangent = (role == RightTangentRole ? value.toPointF() : keyframe->rightTangent());
248
249 if (!command) command = new KUndo2Command(kundo2_i18n("Adjust tangent"));
250 keyframe->setInterpolationTangents(leftTangent, rightTangent, command);
251 dataChanged(index, index);
252 }
253 break;
255 {
256 KisScalarKeyframeSP key = channel->keyframeAt<KisScalarKeyframe>(index.column());
257
258 if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode"));
259 key->setInterpolationMode((KisScalarKeyframe::InterpolationMode)value.toInt(), command);
260 dataChanged(index, index);
261 }
262 break;
263 case TangentsModeRole:
264 {
265 KisScalarKeyframeSP keyframe = channel->keyframeAt<KisScalarKeyframe>(index.column());
266 if (!keyframe) return false;
267
269
270 if (!command) command = new KUndo2Command(kundo2_i18n("Set interpolation mode"));
271 keyframe->setTangentsMode( mode, command );
272 dataChanged(index,index);
273 }
274 break;
275 default:
276 return KisTimeBasedItemModel::setData(index, value, role);
277 }
278
279 if (command && !m_d->undoCommand) {
281 }
282
283 return true;
284}
285
286QVariant KisAnimCurvesModel::headerData(int section, Qt::Orientation orientation, int role) const
287{
288 return KisTimeBasedItemModel::headerData(section, orientation, role);
289}
290
291bool KisAnimCurvesModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
292{
293 return KisTimeBasedItemModel::setHeaderData(section, orientation, value, role);
294}
295
297{
298 KIS_ASSERT_RECOVER_RETURN(!m_d->undoCommand);
299 m_d->undoCommand = new KUndo2Command(text);
300}
301
303{
304 KIS_ASSERT_RECOVER_RETURN(m_d->undoCommand);
306
307 m_d->undoCommand = 0;
308}
309
310
311bool KisAnimCurvesModel::adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset)
312{
313 QScopedPointer<KUndo2Command> command(
314 new KUndo2Command(
315 kundo2_i18np("Adjust Keyframe",
316 "Adjust %1 Keyframes",
317 indexes.size())));
318
319 {
320 KisImageBarrierLock lock(image());
321
322 if (timeOffset != 0) {
323 bool ok = createOffsetFramesCommand(indexes, QPoint(timeOffset, 0), false, false, command.data());
324 if (!ok) return false;
325 }
326
329 FrameItemList frameItems;
330
331 Q_FOREACH(QModelIndex index, indexes) {
332 KisScalarKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
333 KIS_ASSERT_RECOVER_RETURN_VALUE(channel, false);
334
335 frameItems << FrameItem(channel->node(),
336 channel->id(),
337 index.column() + timeOffset);
338 };
339
341 command.data(),
342 [frameItems, valueOffset] () -> KUndo2Command* {
343
344 QScopedPointer<KUndo2Command> cmd(new KUndo2Command());
345
346 bool result = false;
347
348 Q_FOREACH (const FrameItem &item, frameItems) {
349 const int time = item.time;
350 KisNodeSP node = item.node;
351
352 KisKeyframeChannel *channel = node->getKeyframeChannel(item.channel);
353
354 if (!channel) continue;
355
356 KisScalarKeyframeSP scalarKeyframe = channel->keyframeAt(time).dynamicCast<KisScalarKeyframe>();
357
358 if (!scalarKeyframe) continue;
359
360 const qreal currentValue = scalarKeyframe->value();
361 //TODO Undo considerations.
362 scalarKeyframe->setValue(currentValue + valueOffset, cmd.data());
363 result = true;
364 }
365
366 return result ? new KisCommandUtils::SkipFirstRedoWrapper(cmd.take()) : 0;
367 });
368 }
369
373
374 return true;
375}
376
378{
379 beginInsertRows(QModelIndex(), m_d->curves.size(), m_d->curves.size());
380
381 KisAnimationCurve *curve = new KisAnimationCurve(channel, m_d->chooseNextColor());
382 m_d->curves.append(curve);
383
384 endInsertRows();
385
388
391
393 this, [this](const KisKeyframeChannel* channel, int time) {
394 this->slotKeyframeChanged(channel, time);
395 });
396
397 connect(channel, SIGNAL(sigKeyframeChanged(const KisKeyframeChannel*,int)),
398 this, SLOT(slotKeyframeChanged(const KisKeyframeChannel*,int)));
399
400 return curve;
401}
402
404{
405 int index = m_d->curves.indexOf(curve);
406 if (index < 0) return;
407
408 curve->channel()->disconnect(this);
409
410 beginRemoveRows(QModelIndex(), index, index);
411
412 m_d->curves.removeAt(index);
413 delete curve;
414
415 endRemoveRows();
416}
417
419{
420 curve->setVisible(visible);
421
422 int row = m_d->rowForCurve(curve);
423 Q_EMIT dataChanged(index(row, 0), index(row, columnCount()));
424}
425
426KisNodeSP KisAnimCurvesModel::nodeAt(QModelIndex index) const
427{
428 KisAnimationCurve *curve = m_d->getCurveAt(index);
429 if (curve && curve->channel() && curve->channel()->node()) {
430 return KisNodeSP(curve->channel()->node());
431 }
432 return 0;
433}
434
435QMap<QString, KisKeyframeChannel *> KisAnimCurvesModel::channelsAt(QModelIndex index) const
436{
437 KisKeyframeChannel *channel = m_d->getCurveAt(index)->channel();
438 QMap<QString, KisKeyframeChannel*> list;
439 list[""] = channel;
440 return list;
441}
442
443KisKeyframeChannel *KisAnimCurvesModel::channelByID(QModelIndex index, const QString &id) const
444{
445 return nodeAt(index)->getKeyframeChannel(id);
446}
447
449{
450 int row = m_d->rowForChannel(channel);
451 QModelIndex changedIndex = index(row, time);
452 Q_EMIT dataChanged(changedIndex, changedIndex);
453}
454
456{
457 Q_EMIT dataAdded(index(m_d->rowForChannel(channel), time));
458}
459
460
float value(const T *src, size_t ch)
QPair< qreal, qreal > ChannelLimitsMetatype
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
KisKeyframeChannel * channelByID(QModelIndex index, const QString &id) const override
KisAnimationCurve * addCurve(KisScalarKeyframeChannel *channel)
const QScopedPointer< Private > m_d
QMap< QString, KisKeyframeChannel * > channelsAt(QModelIndex index) const override
void beginCommand(const KUndo2MagicString &text)
void slotKeyframeAdded(const KisKeyframeChannel *channel, int time)
QVariant data(const QModelIndex &index, int role) const override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
void setCurveVisible(KisAnimationCurve *curve, bool visible)
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void removeCurve(KisAnimationCurve *curve)
KisNodeSP nodeAt(QModelIndex index) const override
bool adjustKeyframes(const QModelIndexList &indexes, int timeOffset, qreal valueOffset)
KisAnimCurvesModel(QObject *parent)
void dataAdded(const QModelIndex &index)
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
void slotKeyframeChanged(const KisKeyframeChannel *channel, int time)
void setVisible(bool visible)
const QScopedPointer< Private > m_d
KisAnimationCurve(KisScalarKeyframeChannel *channel, QColor color)
KisScalarKeyframeChannel * channel() const
KisPostExecutionUndoAdapter * postExecutionUndoAdapter() const override
KisKeyframeChannel stores and manages KisKeyframes. Maps units of time to virtual keyframe values....
int previousKeyframeTime(const int time) const
KisKeyframeSP keyframeAt(int time) const
Get a keyframe at specified time. Used primarily when the value of a given keyframe is needed.
void sigKeyframeHasBeenRemoved(const KisKeyframeChannel *channel, int time)
This signal is emitted just AFTER a keyframe is removed from the channel.
int activeKeyframeTime(int time) const
Get the time of the active keyframe. Useful for snapping any time to that of the most recent keyframe...
int nextKeyframeTime(const int time) const
void sigAddedKeyframe(const KisKeyframeChannel *channel, int time)
This signal is emitted just AFTER a keyframe was added to the channel.
static void runSingleCommandStroke(KisImageSP image, KUndo2Command *cmd, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
runSingleCommandStroke creates a stroke and runs cmd in it. The text() field of cmd is used as a titl...
The KisScalarKeyframeChannel is a concrete KisKeyframeChannel subclass that stores and manages KisSca...
qreal valueAt(int time) const
Quickly get the interpolated value at the given time.
void addScalarKeyframe(int time, qreal value, KUndo2Command *parentUndoCmd=nullptr)
QSharedPointer< ScalarKeyframeLimits > limits() const
The KisScalarKeyframe class is a concrete subclass of KisKeyframe that wraps a scalar value and inter...
InterpolationMode
Controls the type of interpolation between this KisScalarKeyframe and the next.
TangentsMode
Controls the behavior of the left and right tangents on a given keyframe for different curve shapes.
KUndo2Command * createOffsetFramesCommand(QModelIndexList srcIndexes, const QPoint &offset, bool copyFrames, bool moveEmptyFrames, KUndo2Command *parentCommand=0)
bool setData(const QModelIndex &index, const QVariant &value, int role) override
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
QVariant data(const QModelIndex &index, int role) const override
int columnCount(const QModelIndex &parent=QModelIndex()) const override
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) override
#define KIS_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:85
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:75
QSharedPointer< T > toQShared(T *ptr)
KisSharedPtr< KisNode > KisNodeSP
Definition kis_types.h:86
KUndo2MagicString kundo2_i18n(const char *text)
KUndo2MagicString kundo2_i18np(const char *sing, const char *plur, const A1 &a1)
int rowForChannel(const KisKeyframeChannel *channel)
QList< KisAnimationCurve * > curves
int rowForCurve(KisAnimationCurve *curve)
KisAnimationCurve * getCurveAt(const QModelIndex &index)
Private(KisScalarKeyframeChannel *channel, QColor color)
KisScalarKeyframeChannel * channel
KisKeyframeChannel * getKeyframeChannel(const QString &id, bool create)
The LambdaCommand struct is a shorthand for creation of AggregateCommand commands using C++ lambda fe...