diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp
index a6e7c4a08..8f99b568c 100644
--- a/src/assets/keyframes/model/keyframemodel.cpp
+++ b/src/assets/keyframes/model/keyframemodel.cpp
@@ -1,599 +1,599 @@
/***************************************************************************
* 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 "doc/docundostack.hpp"
#include "core.h"
#include "assets/model/assetparametermodel.hpp"
#include "macros.hpp"
#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:"<getCurrentFps())< 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;
double oldValue = m_keyframeList[pos].second;
local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
local_redo = updateKeyframe_lambda(pos, type, value, notify);
} else {
qDebug() << "True addittion";
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(GenTime pos, KeyframeType type, double 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, 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)
{
qDebug() << "Going to remove keyframe at "<getCurrentFps());
qDebug() << "before"< 0);
KeyframeType oldType = m_keyframeList[pos].first;
double oldValue = m_keyframeList[pos].second;
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, true);
Fun local_redo = deleteKeyframe_lambda(pos, true);
qDebug() << "before2"< 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, Fun &undo, Fun &redo)
{
qDebug() << "starting to move keyframe"<getCurrentFps())<getCurrentFps());
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
KeyframeType oldType = m_keyframeList[oldPos].first;
double oldValue = m_keyframeList[oldPos].second;
if (oldPos == pos ) return true;
if ( hasKeyframe(pos) ) return false;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
qDebug() << getAnimProperty();
bool res = removeKeyframe(oldPos, local_undo, local_redo);
qDebug() << "Move keyframe finished deletion:"< 0);
if (oldPos == pos ) return true;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = moveKeyframe(oldPos, pos, undo, redo);
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframe"));
}
return res;
}
bool KeyframeModel::updateKeyframe(GenTime pos, double value, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
- KeyframeType oldType = m_keyframeList[pos].first;
+ KeyframeType type = m_keyframeList[pos].first;
double oldValue = m_keyframeList[pos].second;
if (qAbs(oldValue - value) < 1e-6) return true;
- auto operation = updateKeyframe_lambda(pos, oldType, oldValue, true);
- auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
+ auto operation = updateKeyframe_lambda(pos, type, value, true);
+ auto reverse = updateKeyframe_lambda(pos, type, oldValue, true);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
bool KeyframeModel::updateKeyframe(GenTime pos, double value)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = updateKeyframe(pos, value, undo, redo);
if (res) {
PUSH_UNDO(undo, redo, i18n("Update keyframe"));
}
return res;
}
Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, double value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, type, value, notify]() {
qDebug() << "udpate lambda"<getCurrentFps())< 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), QVector() << TypeRole << ValueRole);
return true;
};
}
Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, double value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, notify, pos, type, value]() {
qDebug() << "add lambda"<getCurrentFps())<(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"<getCurrentFps())< 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"< KeyframeModel::roleNames() const
{
QHash roles;
roles[PosRole] = "position";
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
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 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());
}
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; };
for (const auto& m : m_keyframeList) {
all_pos.push_back(m.first);
}
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);
if (!res) {
bool undone = local_undo();
Q_ASSERT(undone);
return false;
}
}
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;
}
QString KeyframeModel::getAnimProperty() const
{
QString prop;
bool first = true;
for (const auto keyframe : m_keyframeList) {
if (first) {
first = false;
} else {
prop += QStringLiteral(";");
}
prop += QString::number(keyframe.first.frames(pCore->getCurrentFps()));
switch (keyframe.second.first) {
case KeyframeType::Linear:
prop += QStringLiteral("=");
break;
case KeyframeType::Discrete:
prop += QStringLiteral("|=");
break;
case KeyframeType::Curve:
prop += QStringLiteral("~=");
break;
}
prop += QString::number(keyframe.second.second);
}
return prop;
}
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;
}
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;
}
void KeyframeModel::parseAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Mlt::Properties mlt_prop;
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_anim("key");
qDebug() << "Found"<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);
double value = mlt_prop.anim_get_double("key", frame);
addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
}
delete anim;
/*
std::vector > separators({QStringLiteral("="), QStringLiteral("|="), QStringLiteral("~=")});
QStringList list = prop.split(';', QString::SkipEmptyParts);
for (const auto& k : list) {
bool found = false;
KeyframeType type;
QStringList values;
for (const auto &sep : separators) {
if (k.contains(sep.first)) {
found = true;
type = sep.second;
values = k.split(sep.first);
break;
}
}
if (!found || values.size() != 2) {
qDebug() << "ERROR while parsing value of keyframe"<getCurrentFps());
return getInterpolatedValue(pos);
}
double KeyframeModel::getInterpolatedValue(const GenTime &pos) const
{
int p = pos.frames(pCore->getCurrentFps());
if (m_keyframeList.count(pos) > 0) {
return m_keyframeList.at(pos).second;
}
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;
prop.anim_set("keyframe", prev->second.second, prev->first.frames(pCore->getCurrentFps()), 0, convertToMltType(prev->second.first) );
prop.anim_set("keyframe", next->second.second, next->first.frames(pCore->getCurrentFps()), 0, convertToMltType(next->second.first) );
return prop.anim_get_double("keyframe", p);
}
void KeyframeModel::sendModification()
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
auto type = ptr->data(m_index, AssetParameterModel::TypeRole).value();
QString data;
if (type == ParamType::KeyframeParam) {
data = getAnimProperty();
ptr->setParameter(name, data);
} else {
Q_ASSERT(false); //Not implemented, TODO
}
m_lastData = data;
ptr->dataChanged(m_index, m_index);
}
}
void KeyframeModel::refresh()
{
qDebug() << "REFRESHING KEYFRAME";
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
auto type = ptr->data(m_index, AssetParameterModel::TypeRole).value();
QString data = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
qDebug() << "FOUND DATA KEYFRAME" << data;
if (data == m_lastData) {
// nothing to do
return;
}
// first, try to convert to double
bool ok = false;
double value = data.toDouble(&ok);
if (ok) {
Fun undo = []() { return true; };
Fun redo = []() { return true; };
addKeyframe(GenTime(), KeyframeType::Linear, value, false, undo, redo);
qDebug() << "KEYFRAME ADDED"<. *
***************************************************************************/
#include "keyframemodellist.hpp"
#include "doc/docundostack.hpp"
#include "core.h"
#include "macros.hpp"
#include "klocalizedstring.h"
#include "keyframemodel.hpp"
#include "assets/model/assetparametermodel.hpp"
#include
KeyframeModelList::KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack)
: m_model(model)
, m_undoStack(undo_stack)
, m_lock(QReadWriteLock::Recursive)
{
qDebug() <<"Construct keyframemodellist. Checking model:"<second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged);
}
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& param : 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){
double 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::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::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, undo, redo);
};
return applyOperation(op, logUndo ? i18n("Move keyframe") : QString());
}
-bool KeyframeModelList::updateKeyframe(GenTime pos, double value)
+bool KeyframeModelList::updateKeyframe(GenTime pos, double value, const QPersistentModelIndex &index)
{
QWriteLocker locker(&m_lock);
- Q_ASSERT(m_parameters.size() > 0);
- auto op = [value, pos](std::shared_ptr param, Fun &undo, Fun &redo){
- return param->updateKeyframe(pos, value, undo, redo);
- };
- return applyOperation(op, i18n("Update keyframe"));
+ Q_ASSERT(m_parameters.count(index) > 0);
+ Fun undo = []() { return true; };
+ Fun redo = []() { return true; };
+ bool res = m_parameters.at(index)->updateKeyframe(pos, value, undo, redo);
+ if (res) {
+ PUSH_UNDO(undo, redo, i18n("Update keyframe"));
+ }
+ return res;
}
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);
}
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& param : m_parameters) {
param.second->refresh();
}
}
+
+double KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex& index) const
+{
+ READ_LOCK();
+ Q_ASSERT(m_parameters.count(index) > 0);
+ return m_parameters.at(index)->getInterpolatedValue(pos);
+}
diff --git a/src/assets/keyframes/model/keyframemodellist.hpp b/src/assets/keyframes/model/keyframemodellist.hpp
index 35722465c..1abbc1a8a 100644
--- a/src/assets/keyframes/model/keyframemodellist.hpp
+++ b/src/assets/keyframes/model/keyframemodellist.hpp
@@ -1,135 +1,141 @@
/***************************************************************************
* 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 "gentime.h"
#include "definitions.h"
#include "keyframemodel.hpp"
#include "undohelper.hpp"
#include
#include
#include