Commit aeaafd4d authored by Nicolas Carion's avatar Nicolas Carion

Adding a merge model for keyframes (to handle keyframes of several parameters in the same effect)

parent 2b762ee5
......@@ -13,6 +13,7 @@ set(kdenlive_SRCS
effects/effectstack/view/collapsibleeffectview.cpp
effects/effectstack/view/effectstackview.cpp
effects/keyframes/keyframemodel.cpp
effects/keyframes/keyframemodellist.cpp
effects/keyframes/view/keyframeview.cpp
PARENT_SCOPE)
......@@ -125,40 +125,66 @@ bool KeyframeModel::removeKeyframe(GenTime pos)
return res;
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
KeyframeType oldType = m_keyframeList[oldPos].first;
double oldValue = m_keyframeList[pos].second;
if (oldPos == pos ) return true;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
bool res = removeKeyframe(oldPos, undo, redo);
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
bool res = removeKeyframe(oldPos, local_undo, local_redo);
if (res) {
res = addKeyframe(pos, oldType, oldValue, undo, redo);
res = addKeyframe(pos, oldType, oldValue, local_undo, local_redo);
}
if (res) {
if (logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframe"));
}
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
} else {
bool undone = undo();
bool undone = local_undo();
Q_ASSERT(undone);
}
return res;
}
bool KeyframeModel::updateKeyframe(GenTime pos, double value)
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, 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, 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;
double oldValue = m_keyframeList[pos].second;
if (qAbs(oldValue - value) < 1e-6) return true;
Fun undo = updateKeyframe_lambda(pos, oldType, oldValue);
Fun redo = updateKeyframe_lambda(pos, oldType, value);
bool res = redo();
auto operation = updateKeyframe_lambda(pos, oldType, oldValue);
auto reverse = updateKeyframe_lambda(pos, oldType, value);
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"));
}
......@@ -259,10 +285,10 @@ Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
if (m_keyframeList.count(pos) <= 0) {
// return empty marker
*ok = false;
return {GenTime(), {KeyframeType::Linear, 0}};
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {pos, m_keyframeList.at(pos)};
return {pos, m_keyframeList.at(pos).first};
}
Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
......@@ -271,10 +297,10 @@ Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
if (it == m_keyframeList.end()) {
// return empty marker
*ok = false;
return {GenTime(), {KeyframeType::Linear, 0}};
return {GenTime(), KeyframeType::Linear};
}
*ok = true;
return {(*it).first, (*it).second};
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
......@@ -283,11 +309,11 @@ Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
if (it == m_keyframeList.begin()) {
// return empty marker
*ok = false;
return {GenTime(), {KeyframeType::Linear, 0}};
return {GenTime(), KeyframeType::Linear};
}
--it;
*ok = true;
return {(*it).first, (*it).second};
return {(*it).first, (*it).second.first};
}
Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
......@@ -311,7 +337,7 @@ Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
return prev;
}
// return empty marker
return {GenTime(), {KeyframeType::Linear, 0}};
return {GenTime(), KeyframeType::Linear};
}
......@@ -325,7 +351,7 @@ bool KeyframeModel::hasKeyframe(const GenTime &pos) const
return m_keyframeList.count(pos) > 0;
}
bool KeyframeModel::removeAllKeyframes()
bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
std::vector<GenTime> all_pos;
......@@ -348,10 +374,22 @@ bool KeyframeModel::removeAllKeyframes()
return false;
}
}
PUSH_UNDO(local_undo, local_redo, i18n("Delete all keyframes"));
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;
......
......@@ -48,7 +48,7 @@ enum class KeyframeType
Curve
};
Q_DECLARE_METATYPE(KeyframeType)
using Keyframe = std::pair<GenTime, std::pair<KeyframeType, double>>;
using Keyframe = std::pair<GenTime, KeyframeType>;
class KeyframeModel : public QAbstractListModel
{
......@@ -80,6 +80,7 @@ public:
bool removeKeyframe(GenTime pos);
/* @brief Delete all the keyframes of the model */
bool removeAllKeyframes();
bool removeAllKeyframes(Fun &undo, Fun &redo);
protected:
/* @brief Same function but accumulates undo/redo */
......@@ -92,12 +93,14 @@ public:
@param logUndo if true, then an undo object is created
*/
bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo);
bool moveKeyframe(GenTime oldPos, GenTime pos, Fun &undo, Fun &redo);
/* @brief updates the value of a keyframe
@param old is the position of the keyframe
@param value is the new value of the param
*/
bool updateKeyframe(GenTime pos, double value);
bool updateKeyframe(GenTime pos, double value, Fun &undo, Fun &redo);
/* @brief Returns a keyframe data at given pos
ok is a return parameter, set to true if everything went good
......
/***************************************************************************
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#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 <QDebug>
KeyframeModelList::KeyframeModelList(double init_value, std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent)
: m_model(model)
, m_undoStack(undo_stack)
, m_lock(QReadWriteLock::Recursive)
{
std::shared_ptr<KeyframeModel> parameter (new KeyframeModel(init_value, model, index, undo_stack, parent));
m_parameters.insert({index, std::move(parameter)});
}
bool KeyframeModelList::applyOperation(const std::function<bool(std::shared_ptr<KeyframeModel>, 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<KeyframeModel> param, Fun &undo, Fun &redo){
double value = param->getInterpolatedValue(pos);
return param->addKeyframe(pos, type, value, 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<KeyframeModel> 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<KeyframeModel> 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<KeyframeModel> 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)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_parameters.size() > 0);
auto op = [value, pos](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo){
return param->updateKeyframe(pos, value, undo, redo);
};
return applyOperation(op, i18n("Update keyframe"));
}
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);
}
/***************************************************************************
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifndef KEYFRAMELISTMODELLIST_H
#define KEYFRAMELISTMODELLIST_H
#include "gentime.h"
#include "definitions.h"
#include "keyframemodel.hpp"
#include "undohelper.hpp"
#include <QAbstractListModel>
#include <QReadWriteLock>
#include <map>
#include <memory>
#include <unordered_map>
class AssetParameterModel;
class DocUndoStack;
/* @brief This class is a container for the keyframe models.
If an asset has several keyframable parameters, each one has its own keyframeModel,
but we regroup all of these in a common class to provide unified access.
*/
class KeyframeModelList
{
public:
/* @brief Construct a keyframe list bound to the given asset
@param init_value and index correspond to the first parameter
*/
explicit KeyframeModelList(double init_value, std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, QObject *parent = nullptr);
/* @brief Adds a keyframe at the given position. If there is already one then we update it.
@param pos defines the position of the keyframe, relative to the clip
@param type is the type of the keyframe.
*/
bool addKeyframe(GenTime pos, KeyframeType type);
/* @brief Removes the keyframe at the given position. */
bool removeKeyframe(GenTime pos);
/* @brief Delete all the keyframes of the model (except first) */
bool removeAllKeyframes();
/* @brief moves a keyframe
@param oldPos is the old position of the keyframe
@param pos defines the new position of the keyframe, relative to the clip
@param logUndo if true, then an undo object is created
*/
bool moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo);
/* @brief updates the value of a keyframe
@param old is the position of the keyframe
@param value is the new value of the param
*/
bool updateKeyframe(GenTime pos, double value);
/* @brief Returns a keyframe data at given pos
ok is a return parameter, set to true if everything went good
*/
Keyframe getKeyframe(const GenTime &pos, bool *ok) const;
/* @brief Returns the keyframe located after given position.
If there is a keyframe at given position it is ignored.
@param ok is a return parameter to tell if a keyframe was found.
*/
Keyframe getNextKeyframe(const GenTime &pos, bool *ok) const;
/* @brief Returns the keyframe located before given position.
If there is a keyframe at given position it is ignored.
@param ok is a return parameter to tell if a keyframe was found.
*/
Keyframe getPrevKeyframe(const GenTime &pos, bool *ok) const;
/* @brief Returns the closest keyframe from given position.
@param ok is a return parameter to tell if a keyframe was found.
*/
Keyframe getClosestKeyframe(const GenTime &pos, bool *ok) const;
/* @brief Returns true if a keyframe exists at given pos
Notice that add/remove queries are done in real time (gentime), but this request is made in frame
*/
Q_INVOKABLE bool hasKeyframe(int frame) const;
protected:
/** @brief Helper function to apply a given operation on all parameters */
bool applyOperation(const std::function<bool(std::shared_ptr<KeyframeModel>, Fun&, Fun&)> &op, const QString &undoString);
private:
std::weak_ptr<AssetParameterModel> m_model;
std::weak_ptr<DocUndoStack> m_undoStack;
std::unordered_map<QPersistentModelIndex, std::shared_ptr<KeyframeModel>> m_parameters;
mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
public:
// this is to enable for range loops
auto begin() -> decltype(m_parameters.begin()->second->begin()) { return m_parameters.begin()->second->begin(); }
auto end() -> decltype(m_parameters.begin()->second->end()) { return m_parameters.begin()->second->end(); }
};
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment