[prev in list] [next in list] [prev in thread] [next in thread]
List: kde-commits
Subject: [kdenlive/refactoring_timeline] src: Preliminary model for keyframes
From: Nicolas Carion <null () kde ! org>
Date: 2017-08-31 18:47:23
Message-ID: E1dnUUt-0008H8-0G () code ! kde ! org
[Download RAW message or body]
Git commit 10365c58bf025c266729436c5a48d854382a2364 by Nicolas Carion.
Committed on 31/08/2017 at 18:30.
Pushed by alcinos into branch 'refactoring_timeline'.
Preliminary model for keyframes
M +1 -1 src/assets/model/assetparametermodel.cpp
M +1 -1 src/assets/model/assetparametermodel.hpp
M +1 -1 src/assets/view/widgets/abstractparamwidget.cpp
M +1 -0 src/effects/CMakeLists.txt
A +260 -0 src/effects/keyframes/keyframemodel.cpp [License: GPL (v2/3)]
A +137 -0 src/effects/keyframes/keyframemodel.hpp [License: GPL (v2/3)]
https://commits.kde.org/kdenlive/10365c58bf025c266729436c5a48d854382a2364
diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp
index 454dba812..45a967c44 100644
--- a/src/assets/model/assetparametermodel.cpp
+++ b/src/assets/model/assetparametermodel.cpp
@@ -220,7 +220,7 @@ ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
} else if (type == QLatin1String("addedgeometry")) {
return ParamType::Addedgeometry;
} else if (type == QLatin1String("keyframe")) {
- return ParamType::Keyframe;
+ return ParamType::KeyframeParam;
} else if (type == QLatin1String("color")) {
return ParamType::Color;
} else if (type == QLatin1String("position")) {
diff --git a/src/assets/model/assetparametermodel.hpp b/src/assets/model/assetparametermodel.hpp
index c35aa97ed..5fc79d9b6 100644
--- a/src/assets/model/assetparametermodel.hpp
+++ b/src/assets/model/assetparametermodel.hpp
@@ -49,7 +49,7 @@ enum class ParamType {
AnimatedRect,
Geometry,
Addedgeometry,
- Keyframe,
+ KeyframeParam,
Color,
Position,
Curve,
diff --git a/src/assets/view/widgets/abstractparamwidget.cpp \
b/src/assets/view/widgets/abstractparamwidget.cpp index 3056c7bac..26b113a7b 100644
--- a/src/assets/view/widgets/abstractparamwidget.cpp
+++ b/src/assets/view/widgets/abstractparamwidget.cpp
@@ -84,7 +84,7 @@ AbstractParamWidget *AbstractParamWidget::construct(const std::shared_ptr<AssetP
case ParamType::AnimatedRect:
widget = new AnimationWidget(model, index, range, parent);
break;
- case ParamType::Keyframe:
+ case ParamType::KeyframeParam:
widget = new KeyframeEdit(model, index, parent);
break;
case ParamType::Position:
diff --git a/src/effects/CMakeLists.txt b/src/effects/CMakeLists.txt
index b32ba7e5c..62b6d203e 100644
--- a/src/effects/CMakeLists.txt
+++ b/src/effects/CMakeLists.txt
@@ -11,5 +11,6 @@ set(kdenlive_SRCS
effects/effectstack/model/effectstackmodel.cpp
effects/effectstack/view/collapsibleeffectview.cpp
effects/effectstack/view/effectstackview.cpp
+ effects/keyframes/keyframemodel.cpp
PARENT_SCOPE)
diff --git a/src/effects/keyframes/keyframemodel.cpp b/src/effects/keyframes/keyframemodel.cpp
new file mode 100644
index 000000000..745fceb3a
--- /dev/null
+++ b/src/effects/keyframes/keyframemodel.cpp
@@ -0,0 +1,260 @@
+/***************************************************************************
+ * 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 "keyframemodel.hpp"
+#include "doc/docundostack.hpp"
+#include "core.h"
+#include "effects/effectstack/model/effectitemmodel.hpp"
+#include "macros.hpp"
+
+#include <QDebug>
+
+
+KeyframeModel::KeyframeModel(std::weak_ptr<EffectItemModel> effect, std::weak_ptr<DocUndoStack> \
undo_stack, QObject *parent) + : QAbstractListModel(parent)
+ , m_effect(std::move(effect))
+ , m_undoStack(std::move(undo_stack))
+ , m_lock(QReadWriteLock::Recursive)
+{
+ 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);
+}
+
+bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, 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 (type == m_keyframeList.at(pos)) {
+ return true; // nothing to do
+ }
+ // In this case we simply change the type
+ KeyframeType oldType = m_keyframeList[pos];
+ local_undo = changeType_lambda(pos, oldType);
+ local_redo = changeType_lambda(pos, type);
+ } else {
+ local_redo = addKeyframe_lambda(pos, type);
+ 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)
+{
+ QWriteLocker locker(&m_lock);
+ Fun undo = []() { return true; };
+ Fun redo = []() { return true; };
+
+ bool changeType = (m_keyframeList.count(pos) > 0);
+ bool res = addKeyframe(pos, type, undo, redo);
+ if (res) {
+ PUSH_UNDO(undo, redo, changeType ? 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];
+ Fun local_undo = addKeyframe_lambda(pos, oldType);
+ 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; };
+
+ bool res = removeKeyframe(pos, undo, redo);
+ if (res) {
+ PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
+ }
+ return res;
+}
+
+bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos)
+{
+ QWriteLocker locker(&m_lock);
+ Q_ASSERT(m_keyframeList.count(oldPos) > 0);
+ KeyframeType oldType = m_keyframeList[oldPos];
+ if (oldPos == pos ) return true;
+ Fun undo = []() { return true; };
+ Fun redo = []() { return true; };
+ bool res = removeKeyframe(oldPos, undo, redo);
+ if (res) {
+ res = addKeyframe(pos, oldType, undo, redo);
+ }
+ if (res) {
+ PUSH_UNDO(undo, redo, i18n("Move keyframe"));
+ } else {
+ bool undone = undo();
+ Q_ASSERT(undone);
+ }
+ return res;
+}
+
+Fun KeyframeModel::changeType_lambda(GenTime pos, KeyframeType type)
+{
+ QWriteLocker locker(&m_lock);
+ return [this, pos, type]() {
+ Q_ASSERT(m_keyframeList.count(pos) > 0);
+ int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
+ m_keyframeList[pos] = type;
+ emit dataChanged(index(row), index(row), QVector<int>() << TypeRole);
+ return true;
+ };
+}
+
+Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type)
+{
+ QWriteLocker locker(&m_lock);
+ return [this, pos, type]() {
+ 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<int>(m_keyframeList.size());
+ if (insertionIt != m_keyframeList.end()) {
+ insertionRow = static_cast<int>(std::distance(m_keyframeList.begin(), insertionIt));
+ }
+ beginInsertRows(QModelIndex(), insertionRow, insertionRow);
+ m_keyframeList[pos] = type;
+ endInsertRows();
+ return true;
+ };
+}
+
+Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos)
+{
+ QWriteLocker locker(&m_lock);
+ return [this, pos]() {
+ Q_ASSERT(m_keyframeList.count(pos) > 0);
+ int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
+ beginRemoveRows(QModelIndex(), row, row);
+ m_keyframeList.erase(pos);
+ endRemoveRows();
+ return true;
+ };
+}
+
+QHash<int, QByteArray> KeyframeModel::roleNames() const
+{
+ QHash<int, QByteArray> roles;
+ roles[PosRole] = "position";
+ roles[FrameRole] = "frame";
+ roles[TypeRole] = "type";
+ return roles;
+}
+
+
+QVariant KeyframeModel::data(const QModelIndex &index, int role) const
+{
+ READ_LOCK();
+ if (index.row() < 0 || index.row() >= static_cast<int>(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 PosRole:
+ return it->first.seconds();
+ case FrameRole:
+ case Qt::UserRole:
+ return it->first.frames(pCore->getCurrentFps());
+ case TypeRole:
+ return QVariant::fromValue<KeyframeType>(it->second);
+ }
+ return QVariant();
+}
+
+int KeyframeModel::rowCount(const QModelIndex &parent) const
+{
+ READ_LOCK();
+ if (parent.isValid()) return 0;
+ return static_cast<int>(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)};
+}
+
+bool KeyframeModel::hasKeyframe(int frame) const
+{
+ READ_LOCK();
+ return m_keyframeList.count(GenTime(frame, pCore->getCurrentFps())) > 0;
+}
+
+bool KeyframeModel::removeAllKeyframes()
+{
+ QWriteLocker locker(&m_lock);
+ std::vector<GenTime> 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;
+ for (const auto& p : all_pos) {
+ 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"));
+ return true;
+}
diff --git a/src/effects/keyframes/keyframemodel.hpp b/src/effects/keyframes/keyframemodel.hpp
new file mode 100644
index 000000000..44de7bca1
--- /dev/null
+++ b/src/effects/keyframes/keyframemodel.hpp
@@ -0,0 +1,137 @@
+/***************************************************************************
+ * 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 KEYFRAMELISTMODEL_H
+#define KEYFRAMELISTMODEL_H
+
+#include "definitions.h"
+#include "gentime.h"
+#include "undohelper.hpp"
+
+#include <QAbstractListModel>
+#include <QReadWriteLock>
+
+#include <map>
+#include <memory>
+
+class DocUndoStack;
+class EffectItemModel;
+
+/* @brief This class is the model for a list of keyframes.
+ A keyframe is defined by a time, and a type;
+ 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<GenTime, KeyframeType>;
+
+class KeyframeModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ /* @brief Construct a keyframe list bound to the given effect*/
+ explicit KeyframeModel(std::weak_ptr<EffectItemModel> effect, std::weak_ptr<DocUndoStack> \
undo_stack, QObject *parent = nullptr); +
+ enum { TypeRole = Qt::UserRole + 1, PosRole, FrameRole};
+
+ /* @brief Adds a keyframe at the given position. If there is already one then we update its type.
+ @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);
+
+protected:
+ /* @brief Same function but accumulates undo/redo */
+ bool addKeyframe(GenTime pos, KeyframeType type, 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();
+
+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
+ */
+ bool moveKeyframe(GenTime oldPos, GenTime pos);
+
+ /* @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 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;
+
+
+ // Mandatory overloads
+ QVariant data(const QModelIndex &index, int role) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+
+protected:
+
+ /** @brief Helper function that generate a lambda to change comment / type of given keyframe */
+ Fun changeType_lambda(GenTime pos, KeyframeType type);
+
+ /** @brief Helper function that generate a lambda to add given keyframe */
+ Fun addKeyframe_lambda(GenTime pos, KeyframeType type);
+
+ /** @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();
+private:
+
+ std::weak_ptr<EffectItemModel> m_effect;
+ std::weak_ptr<DocUndoStack> m_undoStack;
+
+ mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access
+
+ std::map<GenTime, KeyframeType> 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
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic