diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp
index c878d5312..022a6ba8c 100644
--- a/src/assets/keyframes/model/keyframemodel.cpp
+++ b/src/assets/keyframes/model/keyframemodel.cpp
@@ -1,1238 +1,1215 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "keyframemodel.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "macros.hpp"
#include "profiles/profilemodel.hpp"
#include "rotoscoping/bpoint.h"
#include "rotoscoping/rotohelper.hpp"
#include
#include
#include
#include
#include
#include
KeyframeModel::KeyframeModel(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent)
: QAbstractListModel(parent)
, m_model(std::move(model))
, m_undoStack(std::move(undo_stack))
, m_index(index)
, m_lastData()
, m_lock(QReadWriteLock::Recursive)
{
qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired();
if (auto ptr = m_model.lock()) {
m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value();
}
setup();
refresh();
}
void KeyframeModel::setup()
{
// We connect the signals of the abstractitemmodel to a more generic one.
connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::dataChanged, this, &KeyframeModel::modelChanged);
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo)
{
qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
QWriteLocker locker(&m_lock);
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
if (m_keyframeList.count(pos) > 0) {
qDebug() << "already there";
if (std::pair({type, value}) == m_keyframeList.at(pos)) {
qDebug() << "nothing to do";
return true; // nothing to do
}
// In this case we simply change the type and value
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
local_redo = updateKeyframe_lambda(pos, type, value, notify);
} else {
local_redo = addKeyframe_lambda(pos, type, value, notify);
local_undo = deleteKeyframe_lambda(pos, notify);
}
if (local_redo()) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
bool KeyframeModel::addKeyframe(int frame, double normalizedValue)
{
QVariant result = getNormalizedValue(normalizedValue);
if (result.isValid()) {
// TODO: Use default configurable kf type
return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, result);
}
return false;
}
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool update = (m_keyframeList.count(pos) > 0);
bool res = addKeyframe(pos, type, std::move(value), true, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
return res;
}
bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify)
{
qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
qDebug() << "before" << getAnimProperty();
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
Fun local_redo = deleteKeyframe_lambda(pos, notify);
if (local_redo()) {
qDebug() << "after" << getAnimProperty();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
bool KeyframeModel::removeKeyframe(int frame)
{
GenTime pos(frame, pCore->getCurrentFps());
return removeKeyframe(pos);
}
bool KeyframeModel::removeKeyframe(GenTime pos)
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) {
return false; // initial point must stay
}
bool res = removeKeyframe(pos, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
}
return res;
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo)
{
qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
if (oldPos == pos) {
if (!newVal.isValid()) {
// no change
return true;
}
if (m_paramType == ParamType::AnimatedRect) {
return updateKeyframe(pos, newVal);
}
// Calculate real value from normalized
QVariant result = getNormalizedValue(newVal.toDouble());
return updateKeyframe(pos, result);
}
if (oldPos != pos && hasKeyframe(pos)) {
return false;
}
KeyframeType oldType = m_keyframeList[oldPos].first;
QVariant oldValue = m_keyframeList[oldPos].second;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
qDebug() << getAnimProperty();
// TODO: use the new Animation::key_set_frame to move a keyframe
bool res = removeKeyframe(oldPos, local_undo, local_redo);
qDebug() << "Move keyframe finished deletion:" << res;
qDebug() << getAnimProperty();
if (res) {
if (m_paramType == ParamType::AnimatedRect) {
if (!newVal.isValid()) {
newVal = oldValue;
}
res = addKeyframe(pos, oldType, newVal, true, local_undo, local_redo);
} else if (newVal.isValid()) {
QVariant result = getNormalizedValue(newVal.toDouble());
if (result.isValid()) {
res = addKeyframe(pos, oldType, result, true, local_undo, local_redo);
}
} else {
res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo);
}
qDebug() << "Move keyframe finished insertion:" << res;
qDebug() << getAnimProperty();
}
if (res) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
} else {
bool undone = local_undo();
Q_ASSERT(undone);
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, QVariant(), logUndo);
}
bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
{
if (oldPos == pos) return true;
GenTime oldFrame(oldPos, pCore->getCurrentFps());
Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
GenTime diff(pos - oldPos, pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QList times;
for (const auto &m : m_keyframeList) {
if (m.first < oldFrame) continue;
times << m.first;
}
bool res = true;
for (const auto &t : times) {
res &= moveKeyframe(t, t + diff, QVariant(), undo, redo);
}
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframes"));
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, std::move(newVal), true);
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
if (oldPos == pos) return true;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = moveKeyframe(oldPos, pos, std::move(newVal), undo, redo);
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframe"));
}
return res;
}
bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType type = m_keyframeList[pos].first;
auto operation = updateKeyframe_lambda(pos, type, std::move(value), true);
return operation();
}
bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType type = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
// Check if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true;
}
auto operation = updateKeyframe_lambda(pos, type, value, update);
auto reverse = updateKeyframe_lambda(pos, type, oldValue, update);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
bool KeyframeModel::updateKeyframe(int pos, double newVal)
{
GenTime Pos(pos, pCore->getCurrentFps());
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return updateKeyframe(Pos, realValue);
}
return false;
}
bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = updateKeyframe(pos, std::move(value), undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
KeyframeType convertFromMltType(mlt_keyframe_type type)
{
switch (type) {
case mlt_keyframe_linear:
return KeyframeType::Linear;
case mlt_keyframe_discrete:
return KeyframeType::Discrete;
case mlt_keyframe_smooth:
return KeyframeType::Curve;
}
return KeyframeType::Linear;
}
bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
KeyframeType newType = convertFromMltType((mlt_keyframe_type)type);
QVariant value = m_keyframeList[pos].second;
// Check if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (oldType == newType) return true;
}
auto operation = updateKeyframe_lambda(pos, newType, value, true);
auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, type, value, notify]() {
qDebug() << "update lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
Q_ASSERT(m_keyframeList.count(pos) > 0);
int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
m_keyframeList[pos].first = type;
m_keyframeList[pos].second = value;
if (notify) emit dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
return true;
};
}
Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, notify, pos, type, value]() {
qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
Q_ASSERT(m_keyframeList.count(pos) == 0);
// We determine the row of the newly added marker
auto insertionIt = m_keyframeList.lower_bound(pos);
int insertionRow = static_cast(m_keyframeList.size());
if (insertionIt != m_keyframeList.end()) {
insertionRow = static_cast(std::distance(m_keyframeList.begin(), insertionIt));
}
if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow);
m_keyframeList[pos].first = type;
m_keyframeList[pos].second = value;
if (notify) endInsertRows();
return true;
};
}
Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, notify]() {
qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify;
qDebug() << "before" << getAnimProperty();
Q_ASSERT(m_keyframeList.count(pos) > 0);
//Q_ASSERT(pos != GenTime()); // cannot delete initial point
int row = static_cast(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
if (notify) beginRemoveRows(QModelIndex(), row, row);
m_keyframeList.erase(pos);
if (notify) endRemoveRows();
qDebug() << "after" << getAnimProperty();
return true;
};
}
QHash KeyframeModel::roleNames() const
{
QHash roles;
roles[PosRole] = "position";
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
roles[NormalizedValueRole] = "normalizedValue";
return roles;
}
QVariant KeyframeModel::data(const QModelIndex &index, int role) const
{
READ_LOCK();
if (index.row() < 0 || index.row() >= static_cast(m_keyframeList.size()) || !index.isValid()) {
return QVariant();
}
auto it = m_keyframeList.begin();
std::advance(it, index.row());
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
case ValueRole:
return it->second.second;
case NormalizedValueRole: {
if (m_paramType == ParamType::AnimatedRect) {
const QString &data = it->second.second.toString();
QLocale locale;
return locale.toDouble(data.section(QLatin1Char(' '), -1));
}
double val = it->second.second.toDouble();
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double linear = val * factor;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (linear >= norm) {
return 0.5 + (linear - norm) / (max * factor - norm) * 0.5;
}
// transform current value to 0..1 scale
double scaled = (linear - norm) / (min * factor - norm);
// Log scale
return 0.5 - pow(scaled, 0.6) * 0.5;
}
return (linear - min) / (max - min);
} else {
qDebug() << "// CANNOT LOCK effect MODEL";
}
return 1;
}
case PosRole:
return it->first.seconds();
case FrameRole:
case Qt::UserRole:
return it->first.frames(pCore->getCurrentFps());
case TypeRole:
return QVariant::fromValue(it->second.first);
}
return QVariant();
}
int KeyframeModel::rowCount(const QModelIndex &parent) const
{
READ_LOCK();
if (parent.isValid()) return 0;
return static_cast(m_keyframeList.size());
}
bool KeyframeModel::singleKeyframe() const
{
READ_LOCK();
return m_keyframeList.size() <= 1;
}
Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
if (m_keyframeList.count(pos) <= 0) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {pos, m_keyframeList.at(pos).first};
}
Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
{
auto it = m_keyframeList.upper_bound(pos);
if (it == m_keyframeList.end()) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
auto it = m_keyframeList.lower_bound(pos);
if (it == m_keyframeList.begin()) {
// return empty marker
*ok = false;
return {GenTime(), KeyframeType::Linear};
}
--it;
*ok = true;
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
if (m_keyframeList.count(pos) > 0) {
return getKeyframe(pos, ok);
}
bool ok1, ok2;
auto next = getNextKeyframe(pos, &ok1);
auto prev = getPrevKeyframe(pos, &ok2);
*ok = ok1 || ok2;
if (ok1 && ok2) {
double fps = pCore->getCurrentFps();
if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) {
return next;
}
return prev;
} else if (ok1) {
return next;
} else if (ok2) {
return prev;
}
// return empty marker
return {GenTime(), KeyframeType::Linear};
}
bool KeyframeModel::hasKeyframe(int frame) const
{
return hasKeyframe(GenTime(frame, pCore->getCurrentFps()));
}
bool KeyframeModel::hasKeyframe(const GenTime &pos) const
{
READ_LOCK();
return m_keyframeList.count(pos) > 0;
}
bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::vector all_pos;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
int kfrCount = (int)m_keyframeList.size() - 1;
if (kfrCount <= 0) {
// Nothing to do
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
// we trigger only one global remove/insertrow event
Fun update_redo_start = [this, kfrCount]() {
beginRemoveRows(QModelIndex(), 1, kfrCount);
return true;
};
Fun update_redo_end = [this]() {
endRemoveRows();
return true;
};
Fun update_undo_start = [this, kfrCount]() {
beginInsertRows(QModelIndex(), 1, kfrCount);
return true;
};
Fun update_undo_end = [this]() {
endInsertRows();
return true;
};
PUSH_LAMBDA(update_redo_start, local_redo);
PUSH_LAMBDA(update_undo_start, local_undo);
for (const auto &m : m_keyframeList) {
all_pos.push_back(m.first);
}
update_redo_start();
bool res = true;
bool first = true;
for (const auto &p : all_pos) {
if (first) { // skip first point
first = false;
continue;
}
res = removeKeyframe(p, local_undo, local_redo, false);
if (!res) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
update_redo_end();
PUSH_LAMBDA(update_redo_end, local_redo);
PUSH_LAMBDA(update_undo_end, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
bool KeyframeModel::removeAllKeyframes()
{
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = removeAllKeyframes(undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Delete all keyframes"));
}
return res;
}
mlt_keyframe_type convertToMltType(KeyframeType type)
{
switch (type) {
case KeyframeType::Linear:
return mlt_keyframe_linear;
case KeyframeType::Discrete:
return mlt_keyframe_discrete;
case KeyframeType::Curve:
return mlt_keyframe_smooth;
}
return mlt_keyframe_linear;
}
QString KeyframeModel::getAnimProperty() const
{
if (m_paramType == ParamType::Roto_spline) {
return getRotoProperty();
}
Mlt::Properties mlt_prop;
if (auto ptr = m_model.lock()) {
ptr->passProperties(mlt_prop);
}
int ix = 0;
bool first = true;
std::shared_ptr anim(nullptr);
for (const auto &keyframe : m_keyframeList) {
if (first) {
switch (m_paramType) {
case ParamType::AnimatedRect:
mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
break;
default:
mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
break;
}
anim.reset(mlt_prop.get_anim("key"));
anim->key_set_type(ix, convertToMltType(keyframe.second.first));
first = false;
ix++;
continue;
}
switch (m_paramType) {
case ParamType::AnimatedRect:
mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
break;
default:
mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
break;
}
anim->key_set_type(ix, convertToMltType(keyframe.second.first));
ix++;
}
QString ret;
if (anim) {
char *cut = anim->serialize_cut();
ret = QString(cut);
free(cut);
}
return ret;
}
QString KeyframeModel::getRotoProperty() const
{
QJsonDocument doc;
if (auto ptr = m_model.lock()) {
int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
int out = in + ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
QVariantMap map;
for (const auto &keyframe : m_keyframeList) {
map.insert(QString::number(keyframe.first.frames(pCore->getCurrentFps())).rightJustified(log10((double)out) + 1, '0'), keyframe.second.second);
}
doc = QJsonDocument::fromVariant(map);
}
return doc.toJson();
}
void KeyframeModel::parseAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QLocale locale;
disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
removeAllKeyframes(undo, redo);
int in = 0;
int out = 0;
bool useOpacity = true;
Mlt::Properties mlt_prop;
if (auto ptr = m_model.lock()) {
in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
ptr->passProperties(mlt_prop);
useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
} else {
qDebug()<<"###################\n\n/// ERROR LOCKING MODEL!!! ";
}
mlt_prop.set("key", prop.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_double("key", 0, out);
Mlt::Animation anim = mlt_prop.get_animation("key");
qDebug() << "Found" << anim.key_count() << ", OUT: " << out << ", animation properties: " << prop;
bool useDefaultType = !prop.contains(QLatin1Char('='));
for (int i = 0; i < anim.key_count(); ++i) {
int frame;
mlt_keyframe_type type;
anim.key_get(i, frame, type);
if (useDefaultType) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
if (useOpacity) {
value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
} else {
value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
}
break;
}
default:
value = QVariant(mlt_prop.anim_get_double("key", frame));
break;
}
if (i == 0 && frame > in) {
// Always add a keyframe at start pos
addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
} else if (frame == in && hasKeyframe(GenTime(in))) {
// First keyframe already exists, adjust its value
updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true);
continue;
}
addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
}
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
void KeyframeModel::resetAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// Delete all existing keyframes
disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
removeAllKeyframes(undo, redo);
Mlt::Properties mlt_prop;
QLocale locale;
int in = 0;
bool useOpacity = true;
if (auto ptr = m_model.lock()) {
in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
ptr->passProperties(mlt_prop);
if (m_paramType == ParamType::AnimatedRect) {
useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
}
}
mlt_prop.set("key", prop.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_int("key", 0, 0);
Mlt::Animation anim = mlt_prop.get_animation("key");
qDebug() << "Found" << anim.key_count() << "animation properties";
for (int i = 0; i < anim.key_count(); ++i) {
int frame;
mlt_keyframe_type type;
anim.key_get(i, frame, type);
if (!prop.contains(QLatin1Char('='))) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
if (useOpacity) {
value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
} else {
value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
}
break;
}
default:
value = QVariant(mlt_prop.anim_get_double("key", frame));
break;
}
if (i == 0 && frame > in) {
// Always add a keyframe at start pos
addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
} else if (frame == in && hasKeyframe(GenTime(in))) {
// First keyframe already exists, adjust its value
updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false);
continue;
}
addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
}
QString effectName;
if (auto ptr = m_model.lock()) {
effectName = ptr->data(m_index, Qt::DisplayRole).toString();
} else {
effectName = i18n("effect");
}
Fun update_local = [this]() {
emit dataChanged(index(0), index((int)m_keyframeList.size()), {});
return true;
};
update_local();
PUSH_LAMBDA(update_local, undo);
PUSH_LAMBDA(update_local, redo);
PUSH_UNDO(undo, redo, i18n("Reset %1", effectName));
connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
}
void KeyframeModel::parseRotoProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QJsonParseError jsonError;
QJsonDocument doc = QJsonDocument::fromJson(prop.toLatin1(), &jsonError);
QVariant data = doc.toVariant();
if (data.canConvert(QVariant::Map)) {
QList keyframes;
QMap map = data.toMap();
QMap::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
++i;
}
}
}
QVariant KeyframeModel::getInterpolatedValue(int p) const
{
auto pos = GenTime(p, pCore->getCurrentFps());
return getInterpolatedValue(pos);
}
QVariant KeyframeModel::updateInterpolated(const QVariant &interpValue, double val)
{
QStringList vals = interpValue.toString().split(QLatin1Char(' '));
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
if (!vals.isEmpty()) {
vals[vals.size() - 1] = locale.toString(val);
}
return vals.join(QLatin1Char(' '));
}
QVariant KeyframeModel::getNormalizedValue(double newVal) const
{
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return QVariant(realValue);
}
return QVariant();
}
QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
{
if (m_keyframeList.count(pos) > 0) {
return m_keyframeList.at(pos).second;
}
if (m_keyframeList.size() == 0) {
return QVariant();
}
- auto next = m_keyframeList.upper_bound(pos);
- if (next == m_keyframeList.cbegin()) {
- return (m_keyframeList.cbegin())->second.second;
- } else if (next == m_keyframeList.cend()) {
- auto it = m_keyframeList.cend();
- --it;
- return it->second.second;
- }
- auto prev = next;
- --prev;
- // We now have surrounding keyframes, we use mlt to compute the value
- Mlt::Properties prop;
- bool useOpacity = true;
+ Mlt::Properties mlt_prop;
+ QString animData;
+ int in = 0;
+ int out = 0;
+ bool useOpacity = false;
if (auto ptr = m_model.lock()) {
- ptr->passProperties(prop);
- if (m_paramType == ParamType::AnimatedRect) {
- useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
- }
+ ptr->passProperties(mlt_prop);
+ in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
+ out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
+ useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
+ animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
}
- QLocale locale;
- int p = pos.frames(pCore->getCurrentFps());
if (m_paramType == ParamType::KeyframeParam) {
- prop.anim_set("keyframe", prev->second.second.toDouble(), prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
- convertToMltType(prev->second.first));
- prop.anim_set("keyframe", next->second.second.toDouble(), next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
- convertToMltType(next->second.first));
- return QVariant(prop.anim_get_double("keyframe", p));
- } else if (m_paramType == ParamType::AnimatedRect) {
- QStringList vals = prev->second.second.toString().split(QLatin1Char(' '));
- if (vals.count() >= 4) {
- mlt_rect rect;
- rect.x = vals.at(0).toInt();
- rect.y = vals.at(1).toInt();
- rect.w = vals.at(2).toInt();
- rect.h = vals.at(3).toInt();
- if (useOpacity) {
- if (vals.count()) {
- rect.o = locale.toDouble(vals.at(4));
- } else {
- rect.o = 1;
- }
- }
- prop.anim_set("keyframe", rect, prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
- convertToMltType(prev->second.first));
+ if (!animData.isEmpty()) {
+ mlt_prop.set("key", animData.toUtf8().constData());
+ // This is a fake query to force the animation to be parsed
+ (void)mlt_prop.anim_get_double("key", 0, out);
+ return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps())));
}
- vals = next->second.second.toString().split(QLatin1Char(' '));
- if (vals.count() >= 4) {
- mlt_rect rect;
- rect.x = vals.at(0).toInt();
- rect.y = vals.at(1).toInt();
- rect.w = vals.at(2).toInt();
- rect.h = vals.at(3).toInt();
+ return QVariant();
+ } else if (m_paramType == ParamType::AnimatedRect) {
+ if (!animData.isEmpty()) {
+ QLocale locale;
+ mlt_prop.set("key", animData.toUtf8().constData());
+ // This is a fake query to force the animation to be parsed
+ (void)mlt_prop.anim_get_double("key", 0, out);
+ mlt_rect rect = mlt_prop.anim_get_rect("key", pos.frames(pCore->getCurrentFps()));
+ QString res = QStringLiteral("%1 %2 %3 %4").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h);
if (useOpacity) {
- if (vals.count() > 4) {
- rect.o = locale.toDouble(vals.at(4));
- } else {
- rect.o = 1;
- }
+ res.append(QStringLiteral(" %1").arg(locale.toString(rect.o)));
}
- prop.anim_set("keyframe", rect, next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
- convertToMltType(next->second.first));
- }
- mlt_rect rect = prop.anim_get_rect("keyframe", p);
- QString res = QStringLiteral("%1 %2 %3 %4").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h);
- if (useOpacity) {
- res.append(QStringLiteral(" %1").arg(locale.toString(rect.o)));
+ return QVariant(res);
}
- return QVariant(res);
+ return QVariant();
} else if (m_paramType == ParamType::Roto_spline) {
// interpolate
+ auto next = m_keyframeList.upper_bound(pos);
+ if (next == m_keyframeList.cbegin()) {
+ return (m_keyframeList.cbegin())->second.second;
+ } else if (next == m_keyframeList.cend()) {
+ auto it = m_keyframeList.cend();
+ --it;
+ return it->second.second;
+ }
+ auto prev = next;
+ --prev;
+
QSize frame = pCore->getCurrentFrameSize();
QList p1 = RotoHelper::getPoints(prev->second.second, frame);
QList p2 = RotoHelper::getPoints(next->second.second, frame);
// relPos should be in [0,1]:
// - equal to 0 on prev keyframe
// - equal to 1 on next keyframe
qreal relPos = 0;
if (next->first != prev->first) {
- relPos = (p - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps())));
+ relPos = (pos.frames(pCore->getCurrentFps()) - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps())));
}
int count = qMin(p1.count(), p2.count());
QList vlist;
for (int i = 0; i < count; ++i) {
BPoint bp;
QList pl;
for (int j = 0; j < 3; ++j) {
if (p1.at(i)[j] != p2.at(i)[j]) {
bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
} else {
bp[j] = p1.at(i)[j];
}
pl << QVariant(QList() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
}
vlist << QVariant(pl);
}
return vlist;
}
return QVariant();
}
void KeyframeModel::sendModification()
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::Roto_spline) {
m_lastData = getAnimProperty();
ptr->setParameter(name, m_lastData, false);
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
}
void KeyframeModel::refresh()
{
Q_ASSERT(m_index.isValid());
QString animData;
if (auto ptr = m_model.lock()) {
animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
} else {
qDebug() << "WARNING : unable to access keyframe's model";
return;
}
if (animData == m_lastData) {
// nothing to do
qDebug() << "// DATA WAS ALREADY PARSED, ABORTING REFRESH\n_________________";
return;
}
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
parseAnimProperty(animData);
} else if (m_paramType == ParamType::Roto_spline) {
parseRotoProperty(animData);
} else {
// first, try to convert to double
bool ok = false;
double value = animData.toDouble(&ok);
if (ok) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
m_lastData = animData;
}
void KeyframeModel::reset()
{
Q_ASSERT(m_index.isValid());
QString animData;
if (auto ptr = m_model.lock()) {
animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
} else {
qDebug() << "WARNING : unable to access keyframe's model";
return;
}
if (animData == m_lastData) {
// nothing to do
qDebug() << "// DATA WAS ALREADY PARSED, ABORTING\n_________________";
return;
}
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
qDebug() << "parsing keyframe" << animData;
resetAnimProperty(animData);
} else if (m_paramType == ParamType::Roto_spline) {
// TODO: resetRotoProperty(animData);
} else {
// first, try to convert to double
bool ok = false;
double value = animData.toDouble(&ok);
if (ok) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
PUSH_UNDO(undo, redo, i18n("Reset effect"));
qDebug() << "KEYFRAME ADDED" << value;
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
m_lastData = animData;
}
QList KeyframeModel::getRanges(const QString &animData, const std::shared_ptr &model)
{
Mlt::Properties mlt_prop;
model->passProperties(mlt_prop);
mlt_prop.set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_int("key", 0, 0);
Mlt::Animation anim = mlt_prop.get_animation("key");
int frame;
mlt_keyframe_type type;
anim.key_get(0, frame, type);
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
QPoint pX(rect.x, rect.x);
QPoint pY(rect.y, rect.y);
QPoint pW(rect.w, rect.w);
QPoint pH(rect.h, rect.h);
QPoint pO(rect.o, rect.o);
for (int i = 1; i < anim.key_count(); ++i) {
anim.key_get(i, frame, type);
if (!animData.contains(QLatin1Char('='))) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
rect = mlt_prop.anim_get_rect("key", frame);
pX.setX(qMin((int)rect.x, pX.x()));
pX.setY(qMax((int)rect.x, pX.y()));
pY.setX(qMin((int)rect.y, pY.x()));
pY.setY(qMax((int)rect.y, pY.y()));
pW.setX(qMin((int)rect.w, pW.x()));
pW.setY(qMax((int)rect.w, pW.y()));
pH.setX(qMin((int)rect.h, pH.x()));
pH.setY(qMax((int)rect.h, pH.y()));
pO.setX(qMin((int)rect.o, pO.x()));
pO.setY(qMax((int)rect.o, pO.y()));
// value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
}
QList result{pX, pY, pW, pH, pO};
return result;
}
std::shared_ptr KeyframeModel::getAnimation(std::shared_ptr model, const QString &animData, int duration)
{
std::shared_ptr mlt_prop(new Mlt::Properties());
model->passProperties(*mlt_prop.get());
mlt_prop->set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop->anim_get_rect("key", 0, duration);
return mlt_prop;
}
const QString KeyframeModel::getAnimationStringWithOffset(std::shared_ptr model, const QString &animData, int offset)
{
Mlt::Properties mlt_prop;
model->passProperties(mlt_prop);
mlt_prop.set("key", animData.toUtf8().constData());
// This is a fake query to force the animation to be parsed
(void)mlt_prop.anim_get_rect("key", 0);
Mlt::Animation anim = mlt_prop.get_animation("key");
if (offset > 0) {
for (int i = anim.key_count() - 1; i >= 0; --i) {
int pos = anim.key_get_frame(i) + offset;
anim.key_set_frame(i, pos);
}
} else {
for (int i = 0; i < anim.key_count(); ++i) {
int pos = anim.key_get_frame(i) + offset;
if (pos > 0) {
anim.key_set_frame(i, pos);
}
}
}
return qstrdup(anim.serialize_cut());
}
QList KeyframeModel::getKeyframePos() const
{
QList all_pos;
for (const auto &m : m_keyframeList) {
all_pos.push_back(m.first);
}
return all_pos;
}
bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::vector all_pos;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
int firstPos = 0;
for (const auto &m : m_keyframeList) {
if (m.first <= pos) {
firstPos++;
continue;
}
all_pos.push_back(m.first);
}
int kfrCount = (int)all_pos.size();
// we trigger only one global remove/insertrow event
Fun update_redo_start = [this, firstPos, kfrCount]() {
beginRemoveRows(QModelIndex(), firstPos, kfrCount);
return true;
};
Fun update_redo_end = [this]() {
endRemoveRows();
return true;
};
Fun update_undo_start = [this, firstPos, kfrCount]() {
beginInsertRows(QModelIndex(), firstPos, kfrCount);
return true;
};
Fun update_undo_end = [this]() {
endInsertRows();
return true;
};
PUSH_LAMBDA(update_redo_start, local_redo);
PUSH_LAMBDA(update_undo_start, local_undo);
update_redo_start();
bool res = true;
for (const auto &p : all_pos) {
res = removeKeyframe(p, local_undo, local_redo, false);
if (!res) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
update_redo_end();
PUSH_LAMBDA(update_redo_end, local_redo);
PUSH_LAMBDA(update_undo_end, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
diff --git a/src/assets/keyframes/model/keyframemodellist.cpp b/src/assets/keyframes/model/keyframemodellist.cpp
index 017a9fe4c..1fe8b05f9 100644
--- a/src/assets/keyframes/model/keyframemodellist.cpp
+++ b/src/assets/keyframes/model/keyframemodellist.cpp
@@ -1,487 +1,505 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "keyframemodellist.hpp"
#include "assets/model/assetcommand.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "keyframemodel.hpp"
#include "klocalizedstring.h"
#include "macros.hpp"
#include
#include
#include
KeyframeModelList::KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack)
: m_model(std::move(model))
, m_undoStack(std::move(undo_stack))
, m_lock(QReadWriteLock::Recursive)
{
qDebug() << "Construct keyframemodellist. Checking model:" << m_model.expired();
addParameter(index);
connect(m_parameters.begin()->second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged);
}
ObjectId KeyframeModelList::getOwnerId() const
{
if (auto ptr = m_model.lock()) {
return ptr->getOwnerId();
}
return {};
}
void KeyframeModelList::addParameter(const QModelIndex &index)
{
std::shared_ptr parameter(new KeyframeModel(m_model, index, m_undoStack));
m_parameters.insert({index, std::move(parameter)});
}
bool KeyframeModelList::applyOperation(const std::function, Fun &, Fun &)> &op, const QString &undoString)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = true;
for (const auto ¶m : m_parameters) {
res = op(param.second, undo, redo);
if (!res) {
bool undone = undo();
Q_ASSERT(undone);
return res;
}
}
if (res && !undoString.isEmpty()) {
PUSH_UNDO(undo, redo, undoString);
}
return res;
}
bool KeyframeModelList::addKeyframe(GenTime pos, KeyframeType type)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
auto op = [pos, type](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value = param->getInterpolatedValue(pos);
return param->addKeyframe(pos, type, value, true, undo, redo);
};
return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
bool KeyframeModelList::addKeyframe(int frame, double val)
{
QWriteLocker locker(&m_lock);
GenTime pos(frame, pCore->getCurrentFps());
Q_ASSERT(m_parameters.size() > 0);
bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
bool isRectParam = false;
if (m_inTimelineIndex.isValid()) {
if (auto ptr = m_model.lock()) {
auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value();
if (tp == ParamType::AnimatedRect) {
isRectParam = true;
}
}
}
auto op = [this, pos, val, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value;
if (m_inTimelineIndex.isValid()) {
if (m_parameters.at(m_inTimelineIndex) == param) {
if (isRectParam) {
value = param->getInterpolatedValue(pos);
value = param->updateInterpolated(value, val);
} else {
value = param->getNormalizedValue(val);
}
} else {
value = param->getInterpolatedValue(pos);
}
} else if (m_parameters.begin()->second == param) {
value = param->getNormalizedValue(val);
} else {
value = param->getInterpolatedValue(pos);
}
return param->addKeyframe(pos, (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), value, true, undo, redo);
};
return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}
bool KeyframeModelList::removeKeyframe(GenTime pos)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeKeyframe(pos, undo, redo); };
return applyOperation(op, i18n("Delete keyframe"));
}
bool KeyframeModelList::removeAllKeyframes()
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeAllKeyframes(undo, redo); };
return applyOperation(op, i18n("Delete all keyframes"));
}
bool KeyframeModelList::removeNextKeyframes(GenTime pos)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->removeNextKeyframes(pos, undo, redo); };
return applyOperation(op, i18n("Delete keyframes"));
}
bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [oldPos, pos](std::shared_ptr param, Fun &undo, Fun &redo) { return param->moveKeyframe(oldPos, pos, QVariant(), undo, redo); };
return applyOperation(op, logUndo ? i18n("Move keyframe") : QString());
}
bool KeyframeModelList::updateKeyframe(GenTime oldPos, GenTime pos, const QVariant &normalizedVal, bool logUndo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
bool isRectParam = false;
if (m_inTimelineIndex.isValid()) {
if (auto ptr = m_model.lock()) {
auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value();
if (tp == ParamType::AnimatedRect) {
isRectParam = true;
}
}
}
auto op = [this, oldPos, pos, normalizedVal, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) {
QVariant value;
if (m_inTimelineIndex.isValid()) {
if (m_parameters.at(m_inTimelineIndex) == param) {
if (isRectParam) {
if (normalizedVal.isValid()) {
value = param->getInterpolatedValue(oldPos);
value = param->updateInterpolated(value, normalizedVal.toDouble());
}
} else {
value = normalizedVal;
}
}
} else if (m_parameters.begin()->second == param) {
value = normalizedVal;
}
return param->moveKeyframe(oldPos, pos, value, undo, redo);
};
return applyOperation(op, logUndo ? i18n("Move keyframe") : QString());
}
bool KeyframeModelList::updateKeyframe(GenTime pos, const QVariant &value, const QPersistentModelIndex &index)
{
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
pos = kf.first;
}
if (auto ptr = m_model.lock()) {
auto *command = new AssetKeyframeCommand(ptr, index, value, pos);
pCore->pushUndo(command);
}
return true;
}
bool KeyframeModelList::updateKeyframeType(GenTime pos, int type, const QPersistentModelIndex &index)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.count(index) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
pos = kf.first;
}
// Update kf type in all parameters
bool res = true;
for (const auto ¶m : m_parameters) {
res = res && param.second->updateKeyframeType(pos, type, undo, redo);
}
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
KeyframeType KeyframeModelList::keyframeType(GenTime pos) const
{
QWriteLocker locker(&m_lock);
if (singleKeyframe()) {
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
return kf.second;
}
bool ok = false;
Keyframe kf = m_parameters.begin()->second->getKeyframe(pos, &ok);
return kf.second;
}
Keyframe KeyframeModelList::getKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getKeyframe(pos, ok);
}
bool KeyframeModelList::singleKeyframe() const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->singleKeyframe();
}
bool KeyframeModelList::isEmpty() const
{
READ_LOCK();
return (m_parameters.size() == 0 || m_parameters.begin()->second->rowCount() == 0);
}
+int KeyframeModelList::count() const
+{
+ READ_LOCK();
+ if (m_parameters.size() > 0)
+ return m_parameters.begin()->second->rowCount();
+ return 0;
+}
+
Keyframe KeyframeModelList::getNextKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getNextKeyframe(pos, ok);
}
Keyframe KeyframeModelList::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getPrevKeyframe(pos, ok);
}
Keyframe KeyframeModelList::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->getClosestKeyframe(pos, ok);
}
bool KeyframeModelList::hasKeyframe(int frame) const
{
READ_LOCK();
Q_ASSERT(m_parameters.size() > 0);
return m_parameters.begin()->second->hasKeyframe(frame);
}
void KeyframeModelList::refresh()
{
QWriteLocker locker(&m_lock);
for (const auto ¶m : m_parameters) {
param.second->refresh();
}
}
void KeyframeModelList::reset()
{
QWriteLocker locker(&m_lock);
for (const auto ¶m : m_parameters) {
param.second->reset();
}
}
QVariant KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex &index) const
{
READ_LOCK();
Q_ASSERT(m_parameters.count(index) > 0);
return m_parameters.at(index)->getInterpolatedValue(pos);
}
KeyframeModel *KeyframeModelList::getKeyModel()
{
if (m_inTimelineIndex.isValid()) {
return m_parameters.at(m_inTimelineIndex).get();
}
if (auto ptr = m_model.lock()) {
for (const auto ¶m : m_parameters) {
if (ptr->data(param.first, AssetParameterModel::ShowInTimelineRole) == true) {
m_inTimelineIndex = param.first;
return param.second.get();
}
}
}
return nullptr;
}
KeyframeModel *KeyframeModelList::getKeyModel(const QPersistentModelIndex &index)
{
if (m_parameters.size() > 0 && m_parameters.find(index) != m_parameters.end()) {
return m_parameters.at(index).get();
}
return nullptr;
}
void KeyframeModelList::resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo)
{
bool ok;
bool ok2;
QList positions;
if (!adjustFromEnd) {
if (offset != 0) {
// this is an endless resize clip
GenTime old_in(oldIn, pCore->getCurrentFps());
GenTime new_in(in + offset, pCore->getCurrentFps());
getKeyframe(new_in, &ok2);
positions = m_parameters.begin()->second->getKeyframePos();
std::sort(positions.begin(), positions.end());
for (const auto ¶m : m_parameters) {
if (offset > 0) {
QVariant value = param.second->getInterpolatedValue(new_in);
param.second->updateKeyframe(old_in, value, undo, redo);
}
for (auto frame : positions) {
if (new_in > GenTime()) {
if (frame > new_in) {
param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
continue;
}
} else if (frame > GenTime()) {
param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
continue;
}
if (frame != GenTime()) {
param.second->removeKeyframe(frame, undo, redo);
}
}
}
} else if (oldIn != in) {
GenTime old_in(oldIn, pCore->getCurrentFps());
GenTime new_in(in, pCore->getCurrentFps());
Keyframe kf = getKeyframe(old_in, &ok);
KeyframeType type = kf.second;
getKeyframe(new_in, &ok2);
if (!ok2) {
// Add new in point
for (const auto ¶m : m_parameters) {
QVariant value = param.second->getInterpolatedValue(new_in);
param.second->addKeyframe(new_in, type, value, true, undo, redo);
}
}
if (ok) {
// Remove previous in point
for (const auto ¶m : m_parameters) {
param.second->removeKeyframe(old_in, undo, redo);
}
}
// Remove all keyframes before in
bool nextOk = false;
kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &nextOk);
GenTime pos;
while (nextOk) {
pos = kf.first;
if (pos < new_in) {
for (const auto ¶m : m_parameters) {
param.second->removeKeyframe(pos, undo, redo);
}
kf = m_parameters.begin()->second->getNextKeyframe(pos, &nextOk);
} else {
break;
}
}
// qDebug()<<"/// \n\nKEYS TO DELETE: "<getCurrentFps());
GenTime new_out(out, pCore->getCurrentFps());
Keyframe kf = getKeyframe(old_out, &ok);
KeyframeType type = kf.second;
getKeyframe(new_out, &ok2);
// Check keyframes after last position
bool ok3;
Keyframe toDel = getNextKeyframe(new_out, &ok3);
if (ok && !ok2) {
// Check if we have only 2 keyframes (in/out), in which case we move the out keyframe to new position
bool ok4;
kf = getPrevKeyframe(old_out, &ok4);
if (ok4) {
GenTime current_in(oldIn, pCore->getCurrentFps());
qDebug()<<" = = = = = = = \n\nGOT 2 KF SITUATION: "<moveKeyframe(old_out, new_out, QVariant(), undo, redo);
}
return;
}
}
positions << old_out;
}
if (toDel.first == GenTime()) {
// No keyframes
return;
}
while (ok3) {
if (!positions.contains(toDel.first)) {
positions << toDel.first;
}
toDel = getNextKeyframe(toDel.first, &ok3);
}
if ((ok || positions.size() > 0) && !ok2) {
for (const auto ¶m : m_parameters) {
QVariant value = param.second->getInterpolatedValue(new_out);
param.second->addKeyframe(new_out, type, value, true, undo, redo);
for (auto frame : positions) {
param.second->removeKeyframe(frame, undo, redo);
}
}
}
}
}
void KeyframeModelList::checkConsistency()
{
if (m_parameters.size() < 2) {
return;
}
// Check keyframes in all parameters
QList fullList;
for (const auto ¶m : m_parameters) {
QList list = param.second->getKeyframePos();
for (auto &time : list) {
if (!fullList.contains(time)) {
fullList << time;
}
}
}
Fun local_update = []() { return true; };
KeyframeType type = (KeyframeType)KdenliveSettings::defaultkeyframeinterp();
for (const auto ¶m : m_parameters) {
QList list = param.second->getKeyframePos();
for (auto &time : fullList) {
if (!list.contains(time)) {
qDebug()<<" = = = \n\n = = = = \n\nWARNING; MISSING KF DETECTED AT: "<displayMessage(i18n("Missing keyframe detected at %1, automatically re-added", time.seconds()), ErrorMessage);
QVariant missingVal = param.second->getInterpolatedValue(time);
local_update = param.second->addKeyframe_lambda(time, type, missingVal, false);
local_update();
}
}
}
}
+
+GenTime KeyframeModelList::getPosAtIndex(int ix)
+{
+ QList positions = m_parameters.begin()->second->getKeyframePos();
+ std::sort(positions.begin(), positions.end());
+ if (ix < 0 || ix >= positions.count()) {
+ return GenTime();
+ }
+ return positions.at(ix);
+}
diff --git a/src/assets/keyframes/model/keyframemodellist.hpp b/src/assets/keyframes/model/keyframemodellist.hpp
index 31ed36543..3c4afe9a5 100644
--- a/src/assets/keyframes/model/keyframemodellist.hpp
+++ b/src/assets/keyframes/model/keyframemodellist.hpp
@@ -1,164 +1,170 @@
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#ifndef KEYFRAMELISTMODELLIST_H
#define KEYFRAMELISTMODELLIST_H
#include "definitions.h"
#include "gentime.h"
#include "keyframemodel.hpp"
#include "undohelper.hpp"
#include
#include
#include