diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp
index 07e320136..e95bb23d5 100644
--- a/src/assets/keyframes/model/keyframemodel.cpp
+++ b/src/assets/keyframes/model/keyframemodel.cpp
@@ -1,923 +1,922 @@
/***************************************************************************
* 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 "rotoscoping/bpoint.h"
#include "rotoscoping/rotohelper.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "macros.hpp"
#include
#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:" << m_model.expired();
if (auto ptr = m_model.lock()) {
m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value();
}
setup();
refresh();
}
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, QVariant value, bool notify, Fun &undo, Fun &redo)
{
qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
QWriteLocker locker(&m_lock);
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
if (m_keyframeList.count(pos) > 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;
QVariant 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(int frame, double normalizedValue)
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (normalizedValue >= 0.5) {
realValue = norm + (2 * (normalizedValue - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - normalizedValue), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (normalizedValue * (max - min) + min) / factor;
}
// TODO: Use default configurable kf type
return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, realValue);
}
return false;
}
bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant 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 " << pos.frames(pCore->getCurrentFps());
qDebug() << "before" << getAnimProperty();
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, true);
Fun local_redo = deleteKeyframe_lambda(pos, true);
qDebug() << "before2" << getAnimProperty();
if (local_redo()) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
return true;
}
return false;
}
bool KeyframeModel::removeKeyframe(int frame)
{
GenTime pos(frame, pCore->getCurrentFps());
return removeKeyframe(pos);
}
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, double newVal, Fun &undo, Fun &redo)
{
qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(oldPos) > 0);
KeyframeType oldType = m_keyframeList[oldPos].first;
QVariant oldValue = m_keyframeList[oldPos].second;
- if (oldPos == pos) return true;
- if (hasKeyframe(pos)) return false;
+ if (oldPos != pos && hasKeyframe(pos)) return false;
Fun local_undo = []() { return true; };
Fun local_redo = []() { return true; };
qDebug() << getAnimProperty();
//TODO: use the new Animation::key_set_frame to move a keyframe
bool res = removeKeyframe(oldPos, local_undo, local_redo);
qDebug() << "Move keyframe finished deletion:" << res;
qDebug() << getAnimProperty();
if (res) {
if (newVal > -1) {
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
res = addKeyframe(pos, oldType, realValue, true, local_undo, local_redo);
}
} else {
res = addKeyframe(pos, oldType, oldValue, true, local_undo, local_redo);
}
qDebug() << "Move keyframe finished insertion:" << res;
qDebug() << getAnimProperty();
}
if (res) {
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
} else {
bool undone = local_undo();
Q_ASSERT(undone);
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, -1, logUndo);
}
bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
{
if (oldPos == pos) return true;
GenTime oldFrame(oldPos, pCore->getCurrentFps());
Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
GenTime diff(pos - oldPos, pCore->getCurrentFps());
QWriteLocker locker(&m_lock);
Fun undo = []() { return true; };
Fun redo = []() { return true; };
QList times;
for (const auto &m : m_keyframeList) {
if (m.first < oldFrame) continue;
times << m.first;
}
bool res = true;
for (const auto &t : times) {
res &= moveKeyframe(t, t + diff, -1, undo, redo);
}
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframes"));
}
return res;
}
bool KeyframeModel::moveKeyframe(int oldPos, int pos, double newVal)
{
GenTime oPos(oldPos, pCore->getCurrentFps());
GenTime nPos(pos, pCore->getCurrentFps());
return moveKeyframe(oPos, nPos, newVal, true);
}
bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, double newVal, 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, newVal, undo, redo);
if (res && logUndo) {
PUSH_UNDO(undo, redo, i18n("Move keyframe"));
}
return res;
}
bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType type = m_keyframeList[pos].first;
QVariant oldValue = m_keyframeList[pos].second;
// Chek if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return 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(int pos, double newVal)
{
GenTime Pos(pos, pCore->getCurrentFps());
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return updateKeyframe(Pos, realValue);
}
return false;
}
bool KeyframeModel::updateKeyframe(GenTime pos, QVariant 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;
}
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;
}
bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
Q_ASSERT(m_keyframeList.count(pos) > 0);
KeyframeType oldType = m_keyframeList[pos].first;
KeyframeType newType = convertFromMltType((mlt_keyframe_type)type);
QVariant value = m_keyframeList[pos].second;
// Check if keyframe is different
if (m_paramType == ParamType::KeyframeParam) {
if (oldType == newType) return true;
}
auto operation = updateKeyframe_lambda(pos, newType, value, true);
auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
bool res = operation();
if (res) {
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
return res;
}
Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, pos, type, value, notify]() {
qDebug() << "udpate lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
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;
if (notify) emit dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
return true;
};
}
Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, QVariant value, bool notify)
{
QWriteLocker locker(&m_lock);
return [this, notify, pos, type, value]() {
qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
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));
}
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" << pos.frames(pCore->getCurrentFps()) << notify;
qDebug() << "before" << getAnimProperty();
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)));
if (notify) beginRemoveRows(QModelIndex(), row, row);
m_keyframeList.erase(pos);
if (notify) endRemoveRows();
qDebug() << "after" << getAnimProperty();
return true;
};
}
QHash KeyframeModel::roleNames() const
{
QHash roles;
roles[PosRole] = "position";
roles[FrameRole] = "frame";
roles[TypeRole] = "type";
roles[ValueRole] = "value";
roles[NormalizedValueRole] = "normalizedValue";
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 NormalizedValueRole: {
if (m_paramType == ParamType::AnimatedRect) {
const QString &data = it->second.second.toString();
QLocale locale;
return locale.toDouble(data.section(QLatin1Char(' '), -1));
}
double val = it->second.second.toDouble();
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double linear = val * factor;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (linear >= norm) {
return 0.5 + (linear - norm) / (max * factor - norm) * 0.5;
}
// transform current value to 0..1 scale
double scaled = (linear - norm) / (min * factor - norm);
// Log scale
return 0.5 - pow(scaled, 0.6) * 0.5;
}
return (linear - min) / (max - min);
} else {
qDebug() << "// CANNOT LOCK effect MODEL";
}
return 1;
}
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());
}
bool KeyframeModel::singleKeyframe() const
{
READ_LOCK();
return m_keyframeList.size() <= 1;
}
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
{
if (m_paramType == ParamType::Roto_spline) {
return getRotoProperty();
}
QString prop;
bool first = true;
QLocale locale;
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;
}
switch (m_paramType) {
case ParamType::AnimatedRect:
prop += keyframe.second.second.toString();
break;
default:
prop += locale.toString(keyframe.second.second.toDouble());
break;
}
}
return prop;
}
QString KeyframeModel::getRotoProperty() const
{
QJsonDocument doc;
if (auto ptr = m_model.lock()) {
int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
int out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
QMap map;
for (const auto keyframe : m_keyframeList) {
map.insert(QString::number(in + keyframe.first.frames(pCore->getCurrentFps())).rightJustified(log10((double)out) + 1, '0'), keyframe.second.second);
}
doc = QJsonDocument::fromVariant(QVariant(map));
}
return doc.toJson();
}
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;
}
void KeyframeModel::parseAnimProperty(const QString &prop)
{
Fun undo = []() { return true; };
Fun redo = []() { return true; };
Mlt::Properties mlt_prop;
QLocale locale;
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" << anim->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);
if (!prop.contains(QLatin1Char('='))) {
// TODO: use a default user defined type
type = mlt_keyframe_linear;
}
QVariant value;
switch (m_paramType) {
case ParamType::AnimatedRect: {
mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(locale.toString(rect.o)));
break;
}
default:
value = QVariant(mlt_prop.anim_get_double("key", frame));
break;
}
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"< keyframes;
QMap map = data.toMap();
QMap::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
++i;
}
}
}
QVariant KeyframeModel::getInterpolatedValue(int p) const
{
auto pos = GenTime(p, pCore->getCurrentFps());
return getInterpolatedValue(pos);
}
QVariant KeyframeModel::getNormalizedValue(double newVal) const
{
if (auto ptr = m_model.lock()) {
double min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
double max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
double realValue;
if (logRole == -1) {
// Logarythmic scale for lower than norm values
if (newVal >= 0.5) {
realValue = norm + (2 * (newVal - 0.5) * (max / factor - norm));
} else {
realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
}
} else {
realValue = (newVal * (max - min) + min) / factor;
}
return QVariant(realValue);
}
return QVariant();
}
QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
{
if (m_keyframeList.count(pos) > 0) {
return m_keyframeList.at(pos).second;
}
if (m_keyframeList.size() == 0) {
return QVariant();
}
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;
QLocale locale;
int p = pos.frames(pCore->getCurrentFps());
if (m_paramType == ParamType::KeyframeParam) {
prop.anim_set("keyframe", prev->second.second.toDouble(), prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(prev->second.first));
prop.anim_set("keyframe", next->second.second.toDouble(), next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(next->second.first));
return QVariant(prop.anim_get_double("keyframe", p));
} else if (m_paramType == ParamType::AnimatedRect) {
QStringList vals = prev->second.second.toString().split(QLatin1Char(' '));
if (vals.count() >= 4) {
mlt_rect rect;
rect.x = vals.at(0).toInt();
rect.y = vals.at(1).toInt();
rect.w = vals.at(2).toInt();
rect.h = vals.at(3).toInt();
if (vals.count() > 4) {
rect.o = locale.toDouble(vals.at(4));
} else {
rect.o = 1;
}
prop.anim_set("keyframe", rect, prev->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(prev->second.first));
}
vals = next->second.second.toString().split(QLatin1Char(' '));
if (vals.count() >= 4) {
mlt_rect rect;
rect.x = vals.at(0).toInt();
rect.y = vals.at(1).toInt();
rect.w = vals.at(2).toInt();
rect.h = vals.at(3).toInt();
if (vals.count() > 4) {
rect.o = locale.toDouble(vals.at(4));
} else {
rect.o = 1;
}
prop.anim_set("keyframe", rect, next->first.frames(pCore->getCurrentFps()), next->first.frames(pCore->getCurrentFps()),
convertToMltType(next->second.first));
}
mlt_rect rect = prop.anim_get_rect("keyframe", p);
const QString res = QStringLiteral("%1 %2 %3 %4 %5").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h).arg(locale.toString(rect.o));
return QVariant(res);
} else if (m_paramType == ParamType::Roto_spline) {
// interpolate
QSize frame = pCore->getCurrentFrameSize();
QList p1 = RotoHelper::getPoints(prev->second.second, frame);
qreal relPos = (p - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps())) + 1);
QList p2 = RotoHelper::getPoints(next->second.second, frame);
int count = qMin(p1.count(), p2.count());
QList vlist;
for (int i = 0; i < count; ++i) {
BPoint bp;
QList pl;
for (int j = 0; j < 3; ++j) {
if (p1.at(i)[j] != p2.at(i)[j]) {
bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
} else {
bp[j] = p1.at(i)[j];
}
pl << QVariant(QList() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
}
vlist << QVariant(pl);
}
return vlist;
}
return QVariant();
}
void KeyframeModel::sendModification()
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
QString data;
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::Roto_spline) {
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()
{
if (auto ptr = m_model.lock()) {
Q_ASSERT(m_index.isValid());
QString data = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
if (data == m_lastData) {
// nothing to do
return;
}
if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) {
qDebug() << "parsing keyframe" << data;
parseAnimProperty(data);
} else if (m_paramType == ParamType::Roto_spline) {
parseRotoProperty(data);
} else {
// 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, QVariant(value), false, undo, redo);
qDebug() << "KEYFRAME ADDED" << value;
} else {
Q_ASSERT(false); // Not implemented, TODO
}
}
} else {
qDebug() << "WARNING : unable to access keyframe's model";
}
}
diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp
index 365ad5a63..ca1ab8e19 100644
--- a/src/assets/view/widgets/keyframewidget.cpp
+++ b/src/assets/view/widgets/keyframewidget.cpp
@@ -1,368 +1,369 @@
/***************************************************************************
* Copyright (C) 2011 by Till Theato (root@ttill.de) *
* Copyright (C) 2017 by Nicolas Carion *
* This file is part of Kdenlive (www.kdenlive.org). *
* *
* Kdenlive 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) any later version. *
* *
* Kdenlive 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 Kdenlive. If not, see . *
***************************************************************************/
#include "keyframewidget.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "assets/keyframes/model/rotoscoping/rotohelper.hpp"
#include "assets/keyframes/model/corners/cornershelper.hpp"
#include "assets/keyframes/view/keyframeview.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "monitor/monitor.h"
#include "timecode.h"
#include "timecodedisplay.h"
#include "widgets/doublewidget.h"
#include "widgets/geometrywidget.h"
#include
#include
#include
#include
KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(model, index, parent)
, m_keyframes(model->getKeyframeModel())
, m_monitorHelper(nullptr)
, m_neededScene(MonitorSceneType::MonitorSceneDefault)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_lay = new QVBoxLayout(this);
m_lay->setContentsMargins(2, 2, 2, 0);
m_lay->setSpacing(0);
bool ok = false;
int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok);
Q_ASSERT(ok);
m_keyframeview = new KeyframeView(m_keyframes, this);
m_keyframeview->setDuration(duration);
m_buttonAddDelete = new QToolButton(this);
m_buttonAddDelete->setAutoRaise(true);
m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
m_buttonAddDelete->setToolTip(i18n("Add keyframe"));
m_buttonPrevious = new QToolButton(this);
m_buttonPrevious->setAutoRaise(true);
m_buttonPrevious->setIcon(QIcon::fromTheme(QStringLiteral("media-skip-backward")));
m_buttonPrevious->setToolTip(i18n("Go to previous keyframe"));
m_buttonNext = new QToolButton(this);
m_buttonNext->setAutoRaise(true);
m_buttonNext->setIcon(QIcon::fromTheme(QStringLiteral("media-skip-forward")));
m_buttonNext->setToolTip(i18n("Go to next keyframe"));
// Keyframe type widget
m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this);
QAction *linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this);
linear->setData((int)mlt_keyframe_linear);
linear->setCheckable(true);
m_selectType->addAction(linear);
QAction *discrete = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this);
discrete->setData((int)mlt_keyframe_discrete);
discrete->setCheckable(true);
m_selectType->addAction(discrete);
QAction *curve = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this);
curve->setData((int)mlt_keyframe_smooth);
curve->setCheckable(true);
m_selectType->addAction(curve);
m_selectType->setCurrentAction(linear);
connect(m_selectType, static_cast(&KSelectAction::triggered), this, &KeyframeWidget::slotEditKeyframeType);
m_selectType->setToolBarMode(KSelectAction::ComboBoxMode);
m_toolbar = new QToolBar(this);
Monitor *monitor = pCore->getMonitor(m_model->monitorId);
m_time = new TimecodeDisplay(monitor->timecode(), this);
m_time->setRange(0, duration - 1);
m_toolbar->addWidget(m_buttonPrevious);
m_toolbar->addWidget(m_buttonAddDelete);
m_toolbar->addWidget(m_buttonNext);
m_toolbar->addAction(m_selectType);
m_toolbar->addWidget(m_time);
m_lay->addWidget(m_keyframeview);
m_lay->addWidget(m_toolbar);
// slotSetPosition(0, false);
monitorSeek(monitor->position());
connect(m_time, &TimecodeDisplay::timeCodeEditingFinished, [&]() { slotSetPosition(-1, true); });
connect(m_keyframeview, &KeyframeView::seekToPos, [&](int p) { slotSetPosition(p, true); });
connect(m_keyframeview, &KeyframeView::atKeyframe, this, &KeyframeWidget::slotAtKeyframe);
connect(m_keyframeview, &KeyframeView::modified, this, &KeyframeWidget::slotRefreshParams);
connect(m_buttonAddDelete, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotAddRemove);
connect(m_buttonPrevious, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToPrev);
connect(m_buttonNext, &QAbstractButton::pressed, m_keyframeview, &KeyframeView::slotGoToNext);
addParameter(index);
connect(monitor, &Monitor::seekToNextKeyframe, m_keyframeview, &KeyframeView::slotGoToNext, Qt::UniqueConnection);
connect(monitor, &Monitor::seekToPreviousKeyframe, m_keyframeview, &KeyframeView::slotGoToPrev, Qt::UniqueConnection);
connect(monitor, &Monitor::addRemoveKeyframe, m_keyframeview, &KeyframeView::slotAddRemove, Qt::UniqueConnection);
}
KeyframeWidget::~KeyframeWidget()
{
delete m_keyframeview;
delete m_buttonAddDelete;
delete m_buttonPrevious;
delete m_buttonNext;
delete m_time;
}
void KeyframeWidget::monitorSeek(int pos)
{
int in = pCore->getItemPosition(m_model->getOwnerId());
int out = in + pCore->getItemDuration(m_model->getOwnerId());
bool isInRange = pos >= in && pos < out;
m_buttonAddDelete->setEnabled(isInRange);
connectMonitor(isInRange);
int framePos = qBound(in, pos, out) - in;
if (isInRange) {
slotSetPosition(framePos, false);
}
}
void KeyframeWidget::slotEditKeyframeType(QAction *action)
{
int type = action->data().toInt();
m_keyframeview->slotEditType(type, m_index);
}
void KeyframeWidget::slotRefreshParams()
{
int pos = getPosition();
KeyframeType keyType = m_keyframes->keyframeType(GenTime(pos, pCore->getCurrentFps()));
m_selectType->setCurrentItem((int)keyType);
for (const auto &w : m_parameters) {
ParamType type = m_model->data(w.first, AssetParameterModel::TypeRole).value();
if (type == ParamType::KeyframeParam) {
((DoubleWidget *)w.second)->setValue(m_keyframes->getInterpolatedValue(pos, w.first).toDouble());
} else if (type == ParamType::AnimatedRect) {
const QString val = m_keyframes->getInterpolatedValue(pos, w.first).toString();
const QStringList vals = val.split(QLatin1Char(' '));
QRect rect;
double opacity = -1;
if (vals.count() >= 4) {
rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt());
if (vals.count() > 4) {
QLocale locale;
opacity = locale.toDouble(vals.at(4));
}
}
((GeometryWidget *)w.second)->setValue(rect, opacity);
}
}
if (m_monitorHelper) {
m_monitorHelper->refreshParams(pos);
return;
}
}
void KeyframeWidget::slotSetPosition(int pos, bool update)
{
if (pos < 0) {
pos = m_time->getValue();
m_keyframeview->slotSetPosition(pos, true);
} else {
m_time->setValue(pos);
m_keyframeview->slotSetPosition(pos, true);
}
+ m_buttonAddDelete->setEnabled(pos > 0);
slotRefreshParams();
if (update) {
emit seekToPos(pos);
}
}
int KeyframeWidget::getPosition() const
{
return m_time->getValue() + pCore->getItemIn(m_model->getOwnerId());
}
void KeyframeWidget::addKeyframe(int pos)
{
blockSignals(true);
m_keyframeview->slotAddKeyframe(pos);
blockSignals(false);
setEnabled(true);
}
void KeyframeWidget::updateTimecodeFormat()
{
m_time->slotUpdateTimeCodeFormat();
}
void KeyframeWidget::slotAtKeyframe(bool atKeyframe, bool singleKeyframe)
{
if (atKeyframe) {
m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
m_buttonAddDelete->setToolTip(i18n("Delete keyframe"));
} else {
m_buttonAddDelete->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
m_buttonAddDelete->setToolTip(i18n("Add keyframe"));
}
pCore->getMonitor(m_model->monitorId)->setEffectKeyframe(atKeyframe || singleKeyframe);
m_selectType->setEnabled(atKeyframe || singleKeyframe);
for (const auto &w : m_parameters) {
w.second->setEnabled(atKeyframe || singleKeyframe);
}
}
void KeyframeWidget::slotRefresh()
{
// update duration
bool ok = false;
int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok);
Q_ASSERT(ok);
// refresh keyframes
m_keyframes->refresh();
m_keyframeview->setDuration(duration);
m_time->setRange(0, duration - 1);
slotRefreshParams();
}
void KeyframeWidget::addParameter(const QPersistentModelIndex &index)
{
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
// Retrieve parameters from the model
QString name = m_model->data(index, Qt::DisplayRole).toString();
QString comment = m_model->data(index, AssetParameterModel::CommentRole).toString();
QString suffix = m_model->data(index, AssetParameterModel::SuffixRole).toString();
ParamType type = m_model->data(index, AssetParameterModel::TypeRole).value();
// Construct object
QWidget *paramWidget = nullptr;
if (type == ParamType::AnimatedRect) {
m_neededScene = MonitorSceneType::MonitorSceneGeometry;
int inPos = m_model->data(index, AssetParameterModel::ParentInRole).toInt();
QPair range(inPos, inPos + m_model->data(index, AssetParameterModel::ParentDurationRole).toInt());
QSize frameSize = pCore->getCurrentFrameSize();
const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString();
QRect rect;
double opacity = 0;
QStringList vals = value.split(QLatin1Char(' '));
if (vals.count() >= 4) {
rect = QRect(vals.at(0).toInt(), vals.at(1).toInt(), vals.at(2).toInt(), vals.at(3).toInt());
if (vals.count() > 4) {
opacity = locale.toDouble(vals.at(4));
}
}
// qtblend uses an opacity value in the (0-1) range, while older geometry effects use (0-100)
bool integerOpacity = m_model->getAssetId() != QLatin1String("qtblend");
GeometryWidget *geomWidget = new GeometryWidget(pCore->getMonitor(m_model->monitorId), range, rect, opacity, frameSize, false,
m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), integerOpacity, this);
connect(geomWidget, &GeometryWidget::valueChanged,
[this, index](const QString v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); });
paramWidget = geomWidget;
} else if (type == ParamType::Roto_spline) {
m_monitorHelper = new RotoHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection);
m_neededScene = MonitorSceneType::MonitorSceneRoto;
} else {
if (m_model->getAssetId() == QLatin1String("frei0r.c0rners")) {
if (m_neededScene == MonitorSceneDefault && !m_monitorHelper) {
m_neededScene = MonitorSceneType::MonitorSceneCorners;
m_monitorHelper = new CornersHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this);
connect(m_monitorHelper, &KeyframeMonitorHelper::updateKeyframeData, this, &KeyframeWidget::slotUpdateKeyframesFromMonitor, Qt::UniqueConnection);
connect(this, &KeyframeWidget::addIndex, m_monitorHelper, &CornersHelper::addIndex);
} else {
if (type == ParamType::KeyframeParam) {
int paramName = m_model->data(index, AssetParameterModel::NameRole).toInt();
if (paramName < 8) {
emit addIndex(index);
}
}
}
}
double value = m_keyframes->getInterpolatedValue(getPosition(), index).toDouble();
double min = locale.toDouble(m_model->data(index, AssetParameterModel::MinRole).toString());
double max = locale.toDouble(m_model->data(index, AssetParameterModel::MaxRole).toString());
double defaultValue = m_model->data(index, AssetParameterModel::DefaultRole).toDouble();
int decimals = m_model->data(index, AssetParameterModel::DecimalsRole).toInt();
double factor = locale.toDouble(m_model->data(index, AssetParameterModel::FactorRole).toString());
factor = qFuzzyIsNull(factor) ? 1 : factor;
auto doubleWidget = new DoubleWidget(name, value * factor, min, max, factor, defaultValue, comment, -1, suffix, decimals, this);
connect(doubleWidget, &DoubleWidget::valueChanged,
[this, index](double v) { m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); });
paramWidget = doubleWidget;
}
if (paramWidget) {
m_parameters[index] = paramWidget;
m_lay->addWidget(paramWidget);
}
}
void KeyframeWidget::slotInitMonitor(bool active)
{
if (m_keyframeview) {
m_keyframeview->initKeyframePos();
}
Monitor *monitor = pCore->getMonitor(m_model->monitorId);
connectMonitor(active);
if (active) {
connect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek, Qt::UniqueConnection);
} else {
disconnect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek);
}
}
void KeyframeWidget::connectMonitor(bool active)
{
if (m_monitorHelper) {
if (m_monitorHelper->connectMonitor(active)) {
slotRefreshParams();
}
}
for (const auto &w : m_parameters) {
ParamType type = m_model->data(w.first, AssetParameterModel::TypeRole).value();
if (type == ParamType::AnimatedRect) {
((GeometryWidget *)w.second)->connectMonitor(active);
break;
}
}
}
void KeyframeWidget::slotUpdateKeyframesFromMonitor(QPersistentModelIndex index, const QVariant &res)
{
if (m_keyframes->isEmpty()) {
m_keyframes->addKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), KeyframeType::Linear);
m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), res, index);
} else if (m_keyframes->hasKeyframe(getPosition()) || m_keyframes->singleKeyframe()) {
m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), res, index);
}
}
MonitorSceneType KeyframeWidget::requiredScene() const
{
qDebug()<<"// // // RESULTING REQUIRED SCENE: "<isVisible();
}
void KeyframeWidget::showKeyframes(bool enable)
{
m_toolbar->setVisible(enable);
m_keyframeview->setVisible(enable);
}
diff --git a/src/timeline2/view/qml/KeyframeView.qml b/src/timeline2/view/qml/KeyframeView.qml
index 694716d5a..d83dbf00e 100644
--- a/src/timeline2/view/qml/KeyframeView.qml
+++ b/src/timeline2/view/qml/KeyframeView.qml
@@ -1,232 +1,252 @@
/***************************************************************************
* Copyright (C) 2017 by Jean-Baptiste Mardelle *
* 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 . *
***************************************************************************/
import QtQuick 2.6
import QtQuick.Controls 1.4
import QtQml.Models 2.2
Rectangle
{
property alias kfrCount : keyframes.count
anchors.fill: parent
color: Qt.rgba(1,1,1, 0.6)
id: keyframeContainer
property int activeFrame
property int activeIndex
property int inPoint
property int outPoint
property bool selected
onKfrCountChanged: {
keyframecanvas.requestPaint()
}
Keys.onShortcutOverride: {
if (event.key == Qt.Key_Left) {
if (event.modifiers & Qt.AltModifier) {
activeFrame = keyframes.itemAt(Math.max(0, --activeIndex)).frame
} else {
var oldFrame = activeFrame
activeFrame -= 1
if (activeFrame < 0) {
activeFrame = 0
} else {
keyframeModel.moveKeyframe(oldFrame, activeFrame, true)
}
}
event.accepted = true
}
else if (event.key == Qt.Key_Right) {
if (event.modifiers & Qt.AltModifier) {
activeFrame = keyframes.itemAt(Math.min(keyframes.count - 1, ++activeIndex)).frame
} else {
var oldFrame = activeFrame
activeFrame += 1
keyframeModel.moveKeyframe(oldFrame, activeFrame, true)
}
event.accepted = true
}
else if (event.key == Qt.Key_Return || event.key == Qt.Key_Escape) {
keyframeContainer.focus = false
event.accepted = true
}
- if (event.key == Qt.Key_Plus) {
+ if ((event.key == Qt.Key_Plus) && !(event.modifiers & Qt.ControlModifier)) {
var newVal = Math.min(keyframes.itemAt(activeIndex).value / parent.height + .05, 1)
keyframeModel.updateKeyframe(activeFrame, newVal)
event.accepted = true
}
- else if (event.key == Qt.Key_Minus) {
+ else if ((event.key == Qt.Key_Minus) && !(event.modifiers & Qt.ControlModifier)) {
var newVal = Math.max(keyframes.itemAt(activeIndex).value / parent.height - .05, 0)
keyframeModel.updateKeyframe(activeFrame, newVal)
event.accepted = true
}
}
Repeater {
id: keyframes
model: keyframeModel
Rectangle {
id: keyframe
property int frame : model.frame
property int frameType : model.type
x: (model.frame - inPoint) * timeScale
height: parent.height // * model.normalizedValue
property int value: parent.height * model.normalizedValue
property int tmpVal : keyframeVal.y + root.baseUnit / 2
property int tmpPos : x + keyframeVal.x + root.baseUnit / 2
anchors.bottom: parent.bottom
onFrameTypeChanged: {
keyframecanvas.requestPaint()
}
onValueChanged: {
keyframecanvas.requestPaint()
}
width: Math.max(1, timeScale)
color: kfMouseArea.containsMouse ? 'darkred' : 'transparent'
visible: keyframeContainer.selected
MouseArea {
id: kfMouseArea
anchors.fill: parent
anchors.leftMargin: -2
anchors.rightMargin: -2
hoverEnabled: true
cursorShape: Qt.SizeHorCursor
drag.target: parent
drag.smoothed: false
drag.axis: Drag.XAxis
onReleased: {
root.stopScrolling = false
var newPos = Math.round(parent.x / timeScale) + inPoint
- if (newPos != frame) {
+ if (frame != inPoint && newPos != frame) {
if (mouse.modifiers & Qt.ShiftModifier) {
// offset all subsequent keyframes
keyframeModel.offsetKeyframes(frame, newPos, true)
} else {
keyframeModel.moveKeyframe(frame, newPos, true)
}
}
}
onPositionChanged: {
- if (mouse.buttons === Qt.LeftButton) {
+ if (mouse.buttons === Qt.LeftButton && frame != inPoint) {
var newPos = Math.round(parent.x / timeScale)
parent.x = newPos * timeScale
keyframecanvas.requestPaint()
}
}
}
Rectangle {
id: keyframeVal
x: - root.baseUnit / 2
y: parent.height - keyframe.value - root.baseUnit / 2
width: root.baseUnit
height: width
radius: width / 2
color: Qt.rgba(1,0,0, 0.4)
border.color: keyframeContainer.activeFrame == keyframe.frame ? 'black' : kf1MouseArea.containsMouse ? 'blue' : 'transparent'
MouseArea {
id: kf1MouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
drag.target: parent
drag.smoothed: false
onClicked: {
keyframeContainer.activeFrame = frame
keyframeContainer.activeIndex = index
keyframeContainer.focus = true
}
onReleased: {
root.stopScrolling = false
- var newPos = Math.round((keyframe.x + parent.x + root.baseUnit / 2) / timeScale) + inPoint
+ var newPos = frame == inPoint ? inPoint : Math.round((keyframe.x + parent.x + root.baseUnit / 2) / timeScale) + inPoint
+ if (newPos == frame && keyframe.value == keyframe.height - parent.y - root.baseUnit / 2) {
+ var pos = clipRoot.modelStart + frame - inPoint
+ if (timeline.position != pos) {
+ timeline.seekPosition = pos
+ timeline.position = timeline.seekPosition
+ }
+ return
+ }
var newVal = (keyframeContainer.height - (parent.y + mouse.y)) / keyframeContainer.height
- if (newVal > 1.5 || newVal < -0.5) {
+ if (frame != inPoint && (newVal > 1.5 || newVal < -0.5)) {
timeline.removeClipEffectKeyframe(clipRoot.clipId, frame);
- } else if (frame != newPos) {
- newVal = newVal < 0 ? 0 : newVal > 1 ? 1 : newVal
+ } else {
+ if (newVal < 0) {
+ newVal = 0;
+ parent.y = keyframes.height - (root.baseUnit / 2)
+ keyframecanvas.requestPaint()
+ } else if (newVal > 1) {
+ newVal = 1;
+ parent.y = - (root.baseUnit / 2)
+ keyframecanvas.requestPaint()
+ }
timeline.updateClipEffectKeyframe(clipRoot.clipId, frame, newPos, newVal)
}
}
onPositionChanged: {
if (mouse.buttons === Qt.LeftButton) {
- var newPos = Math.round(parent.x / timeScale)
- parent.x = newPos * timeScale
+ if (frame == inPoint) {
+ parent.x = - root.baseUnit / 2
+ } else {
+ var newPos = Math.round(parent.x / timeScale)
+ parent.x = newPos * timeScale
+ }
keyframecanvas.requestPaint()
}
}
onDoubleClicked: {
timeline.removeClipEffectKeyframe(clipRoot.clipId, frame);
}
}
}
}
}
Canvas {
id: keyframecanvas
anchors.fill: parent
contextType: "2d"
Component {
id: comp
PathCurve { }
}
Component {
id: compline
PathLine { }
}
property var paths : []
Path {
id: myPath
startX: 0; startY: parent.height
}
onPaint: {
if (keyframes.count == 0) {
return
}
context.beginPath()
context.fillStyle = Qt.rgba(0,0,0.8, 0.4);
paths = []
var xpos
var ypos
for(var i = 0; i < keyframes.count; i++)
{
var type = keyframes.itemAt(i).frameType
xpos = keyframes.itemAt(i).tmpPos
if (type == 1) {
// discrete
paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
}
ypos = keyframes.itemAt(i).tmpVal
if (type < 2) {
// linear
paths.push(compline.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
} else if (type == 2) {
// curve
paths.push(comp.createObject(keyframecanvas, {"x": xpos, "y": ypos} ))
}
}
paths.push(compline.createObject(keyframecanvas, {"x": parent.width, "y": ypos} ))
paths.push(compline.createObject(keyframecanvas, {"x": parent.width, "y": parent.height} ))
myPath.pathElements = paths
context.clearRect(0,0, width, height);
context.path = myPath;
context.closePath()
context.fill()
}
}
}