[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