diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt index 5040db7f3..72f2df542 100644 --- a/src/effects/CMakeLists.txt +++ b/src/effects/CMakeLists.txt @@ -1,18 +1,19 @@ set(kdenlive_SRCS ${kdenlive_SRCS} effects/effectlist/model/effectfilter.cpp effects/effectlist/model/effecttreemodel.cpp effects/effectlist/view/effectlistwidget.cpp effects/effectlist/view/effectlistwidget.cpp effects/effectsrepository.cpp effects/effectstack/model/abstracteffectitem.cpp effects/effectstack/model/effectgroupmodel.cpp effects/effectstack/model/effectitemmodel.cpp effects/effectstack/model/effectstackmodel.cpp effects/effectstack/view/abstractcollapsiblewidget.cpp 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) diff --git a/src/effects/keyframes/keyframemodel.cpp b/src/effects/keyframes/keyframemodel.cpp index 1145a9205..5556cdb84 100644 --- a/src/effects/keyframes/keyframemodel.cpp +++ b/src/effects/keyframes/keyframemodel.cpp @@ -1,432 +1,470 @@ /*************************************************************************** * 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 KeyframeModel::KeyframeModel(double init_value, 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_lock(QReadWriteLock::Recursive) { m_keyframeList.insert({GenTime(), {KeyframeType::Linear, init_value}}); setup(); } 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, double value, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; if (m_keyframeList.count(pos) > 0) { if (std::pair({type, value}) == m_keyframeList.at(pos)) { 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); local_redo = updateKeyframe_lambda(pos, type, value); } else { local_redo = addKeyframe_lambda(pos, type, value); local_undo = deleteKeyframe_lambda(pos); } 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, 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) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType oldType = m_keyframeList[pos].first; double oldValue = m_keyframeList[pos].second; Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue); Fun local_redo = deleteKeyframe_lambda(pos); if (local_redo()) { UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } return false; } 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, 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")); } return res; } Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, double value) { QWriteLocker locker(&m_lock); return [this, pos, type, value]() { 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; emit dataChanged(index(row), index(row), QVector() << TypeRole << ValueRole); return true; }; } Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, double value) { QWriteLocker locker(&m_lock); return [this, pos, type, value]() { 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)); } beginInsertRows(QModelIndex(), insertionRow, insertionRow); m_keyframeList[pos].first = type; m_keyframeList[pos].second = value; endInsertRows(); return true; }; } Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos) { QWriteLocker locker(&m_lock); return [this, pos]() { 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))); beginRemoveRows(QModelIndex(), row, row); m_keyframeList.erase(pos); endRemoveRows(); return true; }; } QHash 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, 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 { auto it = m_keyframeList.upper_bound(pos); 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 { auto it = m_keyframeList.lower_bound(pos); 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 { 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, 0}}; + 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() +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; } } - 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; 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; } double KeyframeModel::getInterpolatedValue(int p) const { auto pos = GenTime(p, pCore->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() const { if (auto ptr = m_model.lock()) { Q_ASSERT(m_index.isValid()); QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString(); ptr->setParameter(name, getAnimProperty()); ptr->dataChanged(m_index, m_index); } } diff --git a/src/effects/keyframes/keyframemodel.hpp b/src/effects/keyframes/keyframemodel.hpp index b126ca111..bb3b525fe 100644 --- a/src/effects/keyframes/keyframemodel.hpp +++ b/src/effects/keyframes/keyframemodel.hpp @@ -1,183 +1,186 @@ /*************************************************************************** * 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 KEYFRAMELISTMODEL_H #define KEYFRAMELISTMODEL_H #include "definitions.h" #include "gentime.h" #include "undohelper.hpp" #include #include #include #include class AssetParameterModel; class DocUndoStack; class EffectItemModel; /* @brief This class is the model for a list of keyframes. A keyframe is defined by a time, a type and a value We store them in a sorted fashion using a std::map */ enum class KeyframeType { Linear, Discrete, Curve }; Q_DECLARE_METATYPE(KeyframeType) -using Keyframe = std::pair>; +using Keyframe = std::pair; class KeyframeModel : public QAbstractListModel { Q_OBJECT public: /* @brief Construct a keyframe list bound to the given effect @param init_value is the value taken by the param at time 0. @param model is the asset this parameter belong to @param index is the index of this parameter in its model */ explicit KeyframeModel(double init_value, std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent = nullptr); enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole}; friend class KeyframeModelList; /* @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, double value); protected: /* @brief Same function but accumulates undo/redo */ bool addKeyframe(GenTime pos, KeyframeType type, double value, Fun &undo, Fun &redo); public: /* @brief Removes the keyframe at the given position. */ 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 */ bool removeKeyframe(GenTime pos, Fun &undo, Fun &redo); public: /* @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); + 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 */ 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; Q_INVOKABLE bool hasKeyframe(const GenTime &pos) const; /** @brief returns the keyframes as a Mlt Anim Property string. It is defined as pairs of frame and value, separated by ; Example : "0|=50; 50|=100; 100=200; 200~=60;" Spaces are ignored by Mlt. |= represents a discrete keyframe, = a linear one and ~= a Catmull-Rom spline */ QString getAnimProperty() const; /* @brief Return the interpolated value at given pos */ double getInterpolatedValue(int pos) const; double getInterpolatedValue(const GenTime &pos) const; // Mandatory overloads QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: /** @brief Helper function that generate a lambda to change type / value of given keyframe */ Fun updateKeyframe_lambda(GenTime pos, KeyframeType type, double value); /** @brief Helper function that generate a lambda to add given keyframe */ Fun addKeyframe_lambda(GenTime pos, KeyframeType type, double value); /** @brief Helper function that generate a lambda to remove given keyframe */ Fun deleteKeyframe_lambda(GenTime pos); /* @brief Connects the signals of this object */ void setup(); /* @brief Commit the modification to the model */ void sendModification() const; private: std::weak_ptr m_model; std::weak_ptr m_undoStack; QPersistentModelIndex m_index; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access std::map> m_keyframeList; signals: void modelChanged(); public: // this is to enable for range loops auto begin() -> decltype(m_keyframeList.begin()) { return m_keyframeList.begin(); } auto end() -> decltype(m_keyframeList.end()) { return m_keyframeList.end(); } }; //Q_DECLARE_METATYPE(KeyframeModel *) #endif diff --git a/src/effects/keyframes/keyframemodellist.cpp b/src/effects/keyframes/keyframemodellist.cpp new file mode 100644 index 000000000..2481baeaa --- /dev/null +++ b/src/effects/keyframes/keyframemodellist.cpp @@ -0,0 +1,152 @@ +/*************************************************************************** + * 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 "doc/docundostack.hpp" +#include "core.h" +#include "macros.hpp" +#include "klocalizedstring.h" +#include "keyframemodel.hpp" +#include "assets/model/assetparametermodel.hpp" + +#include + + +KeyframeModelList::KeyframeModelList(double init_value, std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent) + : m_model(model) + , m_undoStack(undo_stack) + , m_lock(QReadWriteLock::Recursive) +{ + std::shared_ptr parameter (new KeyframeModel(init_value, model, index, undo_stack, parent)); + 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, 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) +{ + 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")); +} + + +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); +} + diff --git a/src/effects/keyframes/keyframemodel.hpp b/src/effects/keyframes/keyframemodellist.hpp similarity index 56% copy from src/effects/keyframes/keyframemodel.hpp copy to src/effects/keyframes/keyframemodellist.hpp index b126ca111..4fe2e6c13 100644 --- a/src/effects/keyframes/keyframemodel.hpp +++ b/src/effects/keyframes/keyframemodellist.hpp @@ -1,183 +1,125 @@ /*************************************************************************** * 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 KEYFRAMELISTMODEL_H -#define KEYFRAMELISTMODEL_H +#ifndef KEYFRAMELISTMODELLIST_H +#define KEYFRAMELISTMODELLIST_H -#include "definitions.h" #include "gentime.h" +#include "definitions.h" +#include "keyframemodel.hpp" #include "undohelper.hpp" #include #include #include #include +#include class AssetParameterModel; class DocUndoStack; -class EffectItemModel; -/* @brief This class is the model for a list of keyframes. - A keyframe is defined by a time, a type and a value - We store them in a sorted fashion using a std::map +/* @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. */ -enum class KeyframeType -{ - Linear, - Discrete, - Curve -}; -Q_DECLARE_METATYPE(KeyframeType) -using Keyframe = std::pair>; - -class KeyframeModel : public QAbstractListModel +class KeyframeModelList { - Q_OBJECT public: - /* @brief Construct a keyframe list bound to the given effect - @param init_value is the value taken by the param at time 0. - @param model is the asset this parameter belong to - @param index is the index of this parameter in its model + /* @brief Construct a keyframe list bound to the given asset + @param init_value and index correspond to the first parameter */ - explicit KeyframeModel(double init_value, std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent = nullptr); + explicit KeyframeModelList(double init_value, std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack, QObject *parent = nullptr); - enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole, ValueRole}; - friend class KeyframeModelList; /* @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, double value); + bool addKeyframe(GenTime pos, KeyframeType type); -protected: - /* @brief Same function but accumulates undo/redo */ - bool addKeyframe(GenTime pos, KeyframeType type, double value, Fun &undo, Fun &redo); - -public: /* @brief Removes the keyframe at the given position. */ bool removeKeyframe(GenTime pos); - /* @brief Delete all the keyframes of the model */ + /* @brief Delete all the keyframes of the model (except first) */ bool removeAllKeyframes(); -protected: - /* @brief Same function but accumulates undo/redo */ - bool removeKeyframe(GenTime pos, Fun &undo, Fun &redo); - -public: /* @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; - Q_INVOKABLE bool hasKeyframe(const GenTime &pos) const; - - /** @brief returns the keyframes as a Mlt Anim Property string. - It is defined as pairs of frame and value, separated by ; - Example : "0|=50; 50|=100; 100=200; 200~=60;" - Spaces are ignored by Mlt. - |= represents a discrete keyframe, = a linear one and ~= a Catmull-Rom spline - */ - QString getAnimProperty() const; - - /* @brief Return the interpolated value at given pos */ - double getInterpolatedValue(int pos) const; - double getInterpolatedValue(const GenTime &pos) const; - - // Mandatory overloads - QVariant data(const QModelIndex &index, int role) const override; - QHash roleNames() const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override; protected: - /** @brief Helper function that generate a lambda to change type / value of given keyframe */ - Fun updateKeyframe_lambda(GenTime pos, KeyframeType type, double value); + /** @brief Helper function to apply a given operation on all parameters */ + bool applyOperation(const std::function, Fun&, Fun&)> &op, const QString &undoString); - /** @brief Helper function that generate a lambda to add given keyframe */ - Fun addKeyframe_lambda(GenTime pos, KeyframeType type, double value); - - /** @brief Helper function that generate a lambda to remove given keyframe */ - Fun deleteKeyframe_lambda(GenTime pos); - - /* @brief Connects the signals of this object */ - void setup(); - - /* @brief Commit the modification to the model */ - void sendModification() const; private: std::weak_ptr m_model; std::weak_ptr m_undoStack; - QPersistentModelIndex m_index; + std::unordered_map> m_parameters; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access - std::map> m_keyframeList; - -signals: - void modelChanged(); - public: // this is to enable for range loops - auto begin() -> decltype(m_keyframeList.begin()) { return m_keyframeList.begin(); } - auto end() -> decltype(m_keyframeList.end()) { return m_keyframeList.end(); } + 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(); } }; -//Q_DECLARE_METATYPE(KeyframeModel *) #endif