diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp index c878d5312..022a6ba8c 100644 --- a/src/assets/keyframes/model/keyframemodel.cpp +++ b/src/assets/keyframes/model/keyframemodel.cpp @@ -1,1238 +1,1215 @@ /*************************************************************************** * 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 "core.h" #include "doc/docundostack.hpp" #include "macros.hpp" #include "profiles/profilemodel.hpp" #include "rotoscoping/bpoint.h" #include "rotoscoping/rotohelper.hpp" #include #include #include #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 { 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) { QVariant result = getNormalizedValue(normalizedValue); if (result.isValid()) { // TODO: Use default configurable kf type return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, result); } 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, std::move(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, bool notify) { qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify; 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, notify); Fun local_redo = deleteKeyframe_lambda(pos, notify); if (local_redo()) { qDebug() << "after" << getAnimProperty(); 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, QVariant 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); if (oldPos == pos) { if (!newVal.isValid()) { // no change return true; } if (m_paramType == ParamType::AnimatedRect) { return updateKeyframe(pos, newVal); } // Calculate real value from normalized QVariant result = getNormalizedValue(newVal.toDouble()); return updateKeyframe(pos, result); } if (oldPos != pos && hasKeyframe(pos)) { return false; } KeyframeType oldType = m_keyframeList[oldPos].first; QVariant oldValue = m_keyframeList[oldPos].second; 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 (m_paramType == ParamType::AnimatedRect) { if (!newVal.isValid()) { newVal = oldValue; } res = addKeyframe(pos, oldType, newVal, true, local_undo, local_redo); } else if (newVal.isValid()) { QVariant result = getNormalizedValue(newVal.toDouble()); if (result.isValid()) { res = addKeyframe(pos, oldType, result, 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, QVariant(), 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, QVariant(), undo, redo); } if (res && logUndo) { PUSH_UNDO(undo, redo, i18n("Move keyframes")); } return res; } bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal) { GenTime oPos(oldPos, pCore->getCurrentFps()); GenTime nPos(pos, pCore->getCurrentFps()); return moveKeyframe(oPos, nPos, std::move(newVal), true); } bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant 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, std::move(newVal), undo, redo); if (res && logUndo) { PUSH_UNDO(undo, redo, i18n("Move keyframe")); } return res; } bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType type = m_keyframeList[pos].first; auto operation = updateKeyframe_lambda(pos, type, std::move(value), true); return operation(); } bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update) { QWriteLocker locker(&m_lock); Q_ASSERT(m_keyframeList.count(pos) > 0); KeyframeType type = m_keyframeList[pos].first; QVariant oldValue = m_keyframeList[pos].second; // Check if keyframe is different if (m_paramType == ParamType::KeyframeParam) { if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true; } auto operation = updateKeyframe_lambda(pos, type, value, update); auto reverse = updateKeyframe_lambda(pos, type, oldValue, update); 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, std::move(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, const QVariant &value, bool notify) { QWriteLocker locker(&m_lock); return [this, pos, type, value, notify]() { qDebug() << "update 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, const 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; }; int kfrCount = (int)m_keyframeList.size() - 1; if (kfrCount <= 0) { // Nothing to do UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } // we trigger only one global remove/insertrow event Fun update_redo_start = [this, kfrCount]() { beginRemoveRows(QModelIndex(), 1, kfrCount); return true; }; Fun update_redo_end = [this]() { endRemoveRows(); return true; }; Fun update_undo_start = [this, kfrCount]() { beginInsertRows(QModelIndex(), 1, kfrCount); return true; }; Fun update_undo_end = [this]() { endInsertRows(); return true; }; PUSH_LAMBDA(update_redo_start, local_redo); PUSH_LAMBDA(update_undo_start, local_undo); for (const auto &m : m_keyframeList) { all_pos.push_back(m.first); } update_redo_start(); 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, false); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } update_redo_end(); PUSH_LAMBDA(update_redo_end, local_redo); PUSH_LAMBDA(update_undo_end, local_undo); 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; } 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; } QString KeyframeModel::getAnimProperty() const { if (m_paramType == ParamType::Roto_spline) { return getRotoProperty(); } Mlt::Properties mlt_prop; if (auto ptr = m_model.lock()) { ptr->passProperties(mlt_prop); } int ix = 0; bool first = true; std::shared_ptr anim(nullptr); for (const auto &keyframe : m_keyframeList) { if (first) { switch (m_paramType) { case ParamType::AnimatedRect: mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps())); break; default: mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps())); break; } anim.reset(mlt_prop.get_anim("key")); anim->key_set_type(ix, convertToMltType(keyframe.second.first)); first = false; ix++; continue; } switch (m_paramType) { case ParamType::AnimatedRect: mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps())); break; default: mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps())); break; } anim->key_set_type(ix, convertToMltType(keyframe.second.first)); ix++; } QString ret; if (anim) { char *cut = anim->serialize_cut(); ret = QString(cut); free(cut); } return ret; } QString KeyframeModel::getRotoProperty() const { QJsonDocument doc; if (auto ptr = m_model.lock()) { int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt(); int out = in + ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); QVariantMap map; for (const auto &keyframe : m_keyframeList) { map.insert(QString::number(keyframe.first.frames(pCore->getCurrentFps())).rightJustified(log10((double)out) + 1, '0'), keyframe.second.second); } doc = QJsonDocument::fromVariant(map); } return doc.toJson(); } void KeyframeModel::parseAnimProperty(const QString &prop) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; QLocale locale; disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification); removeAllKeyframes(undo, redo); int in = 0; int out = 0; bool useOpacity = true; Mlt::Properties mlt_prop; if (auto ptr = m_model.lock()) { in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt(); out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); ptr->passProperties(mlt_prop); useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool(); } else { qDebug()<<"###################\n\n/// ERROR LOCKING MODEL!!! "; } mlt_prop.set("key", prop.toUtf8().constData()); // This is a fake query to force the animation to be parsed (void)mlt_prop.anim_get_double("key", 0, out); Mlt::Animation anim = mlt_prop.get_animation("key"); qDebug() << "Found" << anim.key_count() << ", OUT: " << out << ", animation properties: " << prop; bool useDefaultType = !prop.contains(QLatin1Char('=')); for (int i = 0; i < anim.key_count(); ++i) { int frame; mlt_keyframe_type type; anim.key_get(i, frame, type); if (useDefaultType) { // 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); if (useOpacity) { 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))); } else { value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h)); } break; } default: value = QVariant(mlt_prop.anim_get_double("key", frame)); break; } if (i == 0 && frame > in) { // Always add a keyframe at start pos addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo); } else if (frame == in && hasKeyframe(GenTime(in))) { // First keyframe already exists, adjust its value updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true); continue; } addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo); } connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification); } void KeyframeModel::resetAnimProperty(const QString &prop) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; // Delete all existing keyframes disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification); removeAllKeyframes(undo, redo); Mlt::Properties mlt_prop; QLocale locale; int in = 0; bool useOpacity = true; if (auto ptr = m_model.lock()) { in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt(); ptr->passProperties(mlt_prop); if (m_paramType == ParamType::AnimatedRect) { useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool(); } } 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_animation("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); if (useOpacity) { 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))); } else { value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h)); } break; } default: value = QVariant(mlt_prop.anim_get_double("key", frame)); break; } if (i == 0 && frame > in) { // Always add a keyframe at start pos addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo); } else if (frame == in && hasKeyframe(GenTime(in))) { // First keyframe already exists, adjust its value updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false); continue; } addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo); } QString effectName; if (auto ptr = m_model.lock()) { effectName = ptr->data(m_index, Qt::DisplayRole).toString(); } else { effectName = i18n("effect"); } Fun update_local = [this]() { emit dataChanged(index(0), index((int)m_keyframeList.size()), {}); return true; }; update_local(); PUSH_LAMBDA(update_local, undo); PUSH_LAMBDA(update_local, redo); PUSH_UNDO(undo, redo, i18n("Reset %1", effectName)); connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification); } void KeyframeModel::parseRotoProperty(const QString &prop) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; QJsonParseError jsonError; QJsonDocument doc = QJsonDocument::fromJson(prop.toLatin1(), &jsonError); QVariant data = doc.toVariant(); if (data.canConvert(QVariant::Map)) { QList 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::updateInterpolated(const QVariant &interpValue, double val) { QStringList vals = interpValue.toString().split(QLatin1Char(' ')); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); if (!vals.isEmpty()) { vals[vals.size() - 1] = locale.toString(val); } return vals.join(QLatin1Char(' ')); } 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; - bool useOpacity = true; + Mlt::Properties mlt_prop; + QString animData; + int in = 0; + int out = 0; + bool useOpacity = false; if (auto ptr = m_model.lock()) { - ptr->passProperties(prop); - if (m_paramType == ParamType::AnimatedRect) { - useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool(); - } + ptr->passProperties(mlt_prop); + in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt(); + out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt(); + useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool(); + animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString(); } - 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 (useOpacity) { - if (vals.count()) { - 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)); + if (!animData.isEmpty()) { + mlt_prop.set("key", animData.toUtf8().constData()); + // This is a fake query to force the animation to be parsed + (void)mlt_prop.anim_get_double("key", 0, out); + return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps()))); } - 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(); + return QVariant(); + } else if (m_paramType == ParamType::AnimatedRect) { + if (!animData.isEmpty()) { + QLocale locale; + mlt_prop.set("key", animData.toUtf8().constData()); + // This is a fake query to force the animation to be parsed + (void)mlt_prop.anim_get_double("key", 0, out); + mlt_rect rect = mlt_prop.anim_get_rect("key", pos.frames(pCore->getCurrentFps())); + QString res = QStringLiteral("%1 %2 %3 %4").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h); if (useOpacity) { - if (vals.count() > 4) { - rect.o = locale.toDouble(vals.at(4)); - } else { - rect.o = 1; - } + res.append(QStringLiteral(" %1").arg(locale.toString(rect.o))); } - 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); - QString res = QStringLiteral("%1 %2 %3 %4").arg((int)rect.x).arg((int)rect.y).arg((int)rect.w).arg((int)rect.h); - if (useOpacity) { - res.append(QStringLiteral(" %1").arg(locale.toString(rect.o))); + return QVariant(res); } - return QVariant(res); + return QVariant(); } else if (m_paramType == ParamType::Roto_spline) { // interpolate + 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; + QSize frame = pCore->getCurrentFrameSize(); QList p1 = RotoHelper::getPoints(prev->second.second, frame); QList p2 = RotoHelper::getPoints(next->second.second, frame); // relPos should be in [0,1]: // - equal to 0 on prev keyframe // - equal to 1 on next keyframe qreal relPos = 0; if (next->first != prev->first) { - relPos = (p - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps()))); + relPos = (pos.frames(pCore->getCurrentFps()) - prev->first.frames(pCore->getCurrentFps())) / (qreal)(((next->first - prev->first).frames(pCore->getCurrentFps()))); } 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(); if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect || m_paramType == ParamType::Roto_spline) { m_lastData = getAnimProperty(); ptr->setParameter(name, m_lastData, false); } else { Q_ASSERT(false); // Not implemented, TODO } } } void KeyframeModel::refresh() { Q_ASSERT(m_index.isValid()); QString animData; if (auto ptr = m_model.lock()) { animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString(); } else { qDebug() << "WARNING : unable to access keyframe's model"; return; } if (animData == m_lastData) { // nothing to do qDebug() << "// DATA WAS ALREADY PARSED, ABORTING REFRESH\n_________________"; return; } if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) { parseAnimProperty(animData); } else if (m_paramType == ParamType::Roto_spline) { parseRotoProperty(animData); } else { // first, try to convert to double bool ok = false; double value = animData.toDouble(&ok); if (ok) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo); } else { Q_ASSERT(false); // Not implemented, TODO } } m_lastData = animData; } void KeyframeModel::reset() { Q_ASSERT(m_index.isValid()); QString animData; if (auto ptr = m_model.lock()) { animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString(); } else { qDebug() << "WARNING : unable to access keyframe's model"; return; } if (animData == m_lastData) { // nothing to do qDebug() << "// DATA WAS ALREADY PARSED, ABORTING\n_________________"; return; } if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::AnimatedRect) { qDebug() << "parsing keyframe" << animData; resetAnimProperty(animData); } else if (m_paramType == ParamType::Roto_spline) { // TODO: resetRotoProperty(animData); } else { // first, try to convert to double bool ok = false; double value = animData.toDouble(&ok); if (ok) { Fun undo = []() { return true; }; Fun redo = []() { return true; }; addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo); PUSH_UNDO(undo, redo, i18n("Reset effect")); qDebug() << "KEYFRAME ADDED" << value; } else { Q_ASSERT(false); // Not implemented, TODO } } m_lastData = animData; } QList KeyframeModel::getRanges(const QString &animData, const std::shared_ptr &model) { Mlt::Properties mlt_prop; model->passProperties(mlt_prop); mlt_prop.set("key", animData.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_animation("key"); int frame; mlt_keyframe_type type; anim.key_get(0, frame, type); mlt_rect rect = mlt_prop.anim_get_rect("key", frame); QPoint pX(rect.x, rect.x); QPoint pY(rect.y, rect.y); QPoint pW(rect.w, rect.w); QPoint pH(rect.h, rect.h); QPoint pO(rect.o, rect.o); for (int i = 1; i < anim.key_count(); ++i) { anim.key_get(i, frame, type); if (!animData.contains(QLatin1Char('='))) { // TODO: use a default user defined type type = mlt_keyframe_linear; } rect = mlt_prop.anim_get_rect("key", frame); pX.setX(qMin((int)rect.x, pX.x())); pX.setY(qMax((int)rect.x, pX.y())); pY.setX(qMin((int)rect.y, pY.x())); pY.setY(qMax((int)rect.y, pY.y())); pW.setX(qMin((int)rect.w, pW.x())); pW.setY(qMax((int)rect.w, pW.y())); pH.setX(qMin((int)rect.h, pH.x())); pH.setY(qMax((int)rect.h, pH.y())); pO.setX(qMin((int)rect.o, pO.x())); pO.setY(qMax((int)rect.o, pO.y())); // 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))); } QList result{pX, pY, pW, pH, pO}; return result; } std::shared_ptr KeyframeModel::getAnimation(std::shared_ptr model, const QString &animData, int duration) { std::shared_ptr mlt_prop(new Mlt::Properties()); model->passProperties(*mlt_prop.get()); mlt_prop->set("key", animData.toUtf8().constData()); // This is a fake query to force the animation to be parsed (void)mlt_prop->anim_get_rect("key", 0, duration); return mlt_prop; } const QString KeyframeModel::getAnimationStringWithOffset(std::shared_ptr model, const QString &animData, int offset) { Mlt::Properties mlt_prop; model->passProperties(mlt_prop); mlt_prop.set("key", animData.toUtf8().constData()); // This is a fake query to force the animation to be parsed (void)mlt_prop.anim_get_rect("key", 0); Mlt::Animation anim = mlt_prop.get_animation("key"); if (offset > 0) { for (int i = anim.key_count() - 1; i >= 0; --i) { int pos = anim.key_get_frame(i) + offset; anim.key_set_frame(i, pos); } } else { for (int i = 0; i < anim.key_count(); ++i) { int pos = anim.key_get_frame(i) + offset; if (pos > 0) { anim.key_set_frame(i, pos); } } } return qstrdup(anim.serialize_cut()); } QList KeyframeModel::getKeyframePos() const { QList all_pos; for (const auto &m : m_keyframeList) { all_pos.push_back(m.first); } return all_pos; } bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); std::vector all_pos; Fun local_undo = []() { return true; }; Fun local_redo = []() { return true; }; int firstPos = 0; for (const auto &m : m_keyframeList) { if (m.first <= pos) { firstPos++; continue; } all_pos.push_back(m.first); } int kfrCount = (int)all_pos.size(); // we trigger only one global remove/insertrow event Fun update_redo_start = [this, firstPos, kfrCount]() { beginRemoveRows(QModelIndex(), firstPos, kfrCount); return true; }; Fun update_redo_end = [this]() { endRemoveRows(); return true; }; Fun update_undo_start = [this, firstPos, kfrCount]() { beginInsertRows(QModelIndex(), firstPos, kfrCount); return true; }; Fun update_undo_end = [this]() { endInsertRows(); return true; }; PUSH_LAMBDA(update_redo_start, local_redo); PUSH_LAMBDA(update_undo_start, local_undo); update_redo_start(); bool res = true; for (const auto &p : all_pos) { res = removeKeyframe(p, local_undo, local_redo, false); if (!res) { bool undone = local_undo(); Q_ASSERT(undone); return false; } } update_redo_end(); PUSH_LAMBDA(update_redo_end, local_redo); PUSH_LAMBDA(update_undo_end, local_undo); UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo); return true; } diff --git a/src/assets/keyframes/model/keyframemodellist.cpp b/src/assets/keyframes/model/keyframemodellist.cpp index 017a9fe4c..1fe8b05f9 100644 --- a/src/assets/keyframes/model/keyframemodellist.cpp +++ b/src/assets/keyframes/model/keyframemodellist.cpp @@ -1,487 +1,505 @@ /*************************************************************************** * 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 "assets/model/assetcommand.hpp" #include "assets/model/assetparametermodel.hpp" #include "core.h" #include "doc/docundostack.hpp" #include "keyframemodel.hpp" #include "klocalizedstring.h" #include "macros.hpp" #include #include #include KeyframeModelList::KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack) : m_model(std::move(model)) , m_undoStack(std::move(undo_stack)) , m_lock(QReadWriteLock::Recursive) { qDebug() << "Construct keyframemodellist. Checking model:" << m_model.expired(); addParameter(index); connect(m_parameters.begin()->second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged); } ObjectId KeyframeModelList::getOwnerId() const { if (auto ptr = m_model.lock()) { return ptr->getOwnerId(); } return {}; } void KeyframeModelList::addParameter(const QModelIndex &index) { std::shared_ptr parameter(new KeyframeModel(m_model, index, m_undoStack)); 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 ¶m : 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) { QVariant value = param->getInterpolatedValue(pos); return param->addKeyframe(pos, type, value, true, undo, redo); }; return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe")); } bool KeyframeModelList::addKeyframe(int frame, double val) { QWriteLocker locker(&m_lock); GenTime pos(frame, pCore->getCurrentFps()); Q_ASSERT(m_parameters.size() > 0); bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0); bool isRectParam = false; if (m_inTimelineIndex.isValid()) { if (auto ptr = m_model.lock()) { auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value(); if (tp == ParamType::AnimatedRect) { isRectParam = true; } } } auto op = [this, pos, val, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) { QVariant value; if (m_inTimelineIndex.isValid()) { if (m_parameters.at(m_inTimelineIndex) == param) { if (isRectParam) { value = param->getInterpolatedValue(pos); value = param->updateInterpolated(value, val); } else { value = param->getNormalizedValue(val); } } else { value = param->getInterpolatedValue(pos); } } else if (m_parameters.begin()->second == param) { value = param->getNormalizedValue(val); } else { value = param->getInterpolatedValue(pos); } return param->addKeyframe(pos, (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), value, true, 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::removeNextKeyframes(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->removeNextKeyframes(pos, undo, redo); }; return applyOperation(op, i18n("Delete 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, QVariant(), undo, redo); }; return applyOperation(op, logUndo ? i18n("Move keyframe") : QString()); } bool KeyframeModelList::updateKeyframe(GenTime oldPos, GenTime pos, const QVariant &normalizedVal, bool logUndo) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.size() > 0); bool isRectParam = false; if (m_inTimelineIndex.isValid()) { if (auto ptr = m_model.lock()) { auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value(); if (tp == ParamType::AnimatedRect) { isRectParam = true; } } } auto op = [this, oldPos, pos, normalizedVal, isRectParam](std::shared_ptr param, Fun &undo, Fun &redo) { QVariant value; if (m_inTimelineIndex.isValid()) { if (m_parameters.at(m_inTimelineIndex) == param) { if (isRectParam) { if (normalizedVal.isValid()) { value = param->getInterpolatedValue(oldPos); value = param->updateInterpolated(value, normalizedVal.toDouble()); } } else { value = normalizedVal; } } } else if (m_parameters.begin()->second == param) { value = normalizedVal; } return param->moveKeyframe(oldPos, pos, value, undo, redo); }; return applyOperation(op, logUndo ? i18n("Move keyframe") : QString()); } bool KeyframeModelList::updateKeyframe(GenTime pos, const QVariant &value, const QPersistentModelIndex &index) { if (singleKeyframe()) { bool ok = false; Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok); pos = kf.first; } if (auto ptr = m_model.lock()) { auto *command = new AssetKeyframeCommand(ptr, index, value, pos); pCore->pushUndo(command); } return true; } bool KeyframeModelList::updateKeyframeType(GenTime pos, int type, const QPersistentModelIndex &index) { QWriteLocker locker(&m_lock); Q_ASSERT(m_parameters.count(index) > 0); Fun undo = []() { return true; }; Fun redo = []() { return true; }; if (singleKeyframe()) { bool ok = false; Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok); pos = kf.first; } // Update kf type in all parameters bool res = true; for (const auto ¶m : m_parameters) { res = res && param.second->updateKeyframeType(pos, type, undo, redo); } if (res) { PUSH_UNDO(undo, redo, i18n("Update keyframe")); } return res; } KeyframeType KeyframeModelList::keyframeType(GenTime pos) const { QWriteLocker locker(&m_lock); if (singleKeyframe()) { bool ok = false; Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok); return kf.second; } bool ok = false; Keyframe kf = m_parameters.begin()->second->getKeyframe(pos, &ok); return kf.second; } 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); } bool KeyframeModelList::singleKeyframe() const { READ_LOCK(); Q_ASSERT(m_parameters.size() > 0); return m_parameters.begin()->second->singleKeyframe(); } bool KeyframeModelList::isEmpty() const { READ_LOCK(); return (m_parameters.size() == 0 || m_parameters.begin()->second->rowCount() == 0); } +int KeyframeModelList::count() const +{ + READ_LOCK(); + if (m_parameters.size() > 0) + return m_parameters.begin()->second->rowCount(); + return 0; +} + 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); } void KeyframeModelList::refresh() { QWriteLocker locker(&m_lock); for (const auto ¶m : m_parameters) { param.second->refresh(); } } void KeyframeModelList::reset() { QWriteLocker locker(&m_lock); for (const auto ¶m : m_parameters) { param.second->reset(); } } QVariant KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex &index) const { READ_LOCK(); Q_ASSERT(m_parameters.count(index) > 0); return m_parameters.at(index)->getInterpolatedValue(pos); } KeyframeModel *KeyframeModelList::getKeyModel() { if (m_inTimelineIndex.isValid()) { return m_parameters.at(m_inTimelineIndex).get(); } if (auto ptr = m_model.lock()) { for (const auto ¶m : m_parameters) { if (ptr->data(param.first, AssetParameterModel::ShowInTimelineRole) == true) { m_inTimelineIndex = param.first; return param.second.get(); } } } return nullptr; } KeyframeModel *KeyframeModelList::getKeyModel(const QPersistentModelIndex &index) { if (m_parameters.size() > 0 && m_parameters.find(index) != m_parameters.end()) { return m_parameters.at(index).get(); } return nullptr; } void KeyframeModelList::resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo) { bool ok; bool ok2; QList positions; if (!adjustFromEnd) { if (offset != 0) { // this is an endless resize clip GenTime old_in(oldIn, pCore->getCurrentFps()); GenTime new_in(in + offset, pCore->getCurrentFps()); getKeyframe(new_in, &ok2); positions = m_parameters.begin()->second->getKeyframePos(); std::sort(positions.begin(), positions.end()); for (const auto ¶m : m_parameters) { if (offset > 0) { QVariant value = param.second->getInterpolatedValue(new_in); param.second->updateKeyframe(old_in, value, undo, redo); } for (auto frame : positions) { if (new_in > GenTime()) { if (frame > new_in) { param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo); continue; } } else if (frame > GenTime()) { param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo); continue; } if (frame != GenTime()) { param.second->removeKeyframe(frame, undo, redo); } } } } else if (oldIn != in) { GenTime old_in(oldIn, pCore->getCurrentFps()); GenTime new_in(in, pCore->getCurrentFps()); Keyframe kf = getKeyframe(old_in, &ok); KeyframeType type = kf.second; getKeyframe(new_in, &ok2); if (!ok2) { // Add new in point for (const auto ¶m : m_parameters) { QVariant value = param.second->getInterpolatedValue(new_in); param.second->addKeyframe(new_in, type, value, true, undo, redo); } } if (ok) { // Remove previous in point for (const auto ¶m : m_parameters) { param.second->removeKeyframe(old_in, undo, redo); } } // Remove all keyframes before in bool nextOk = false; kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &nextOk); GenTime pos; while (nextOk) { pos = kf.first; if (pos < new_in) { for (const auto ¶m : m_parameters) { param.second->removeKeyframe(pos, undo, redo); } kf = m_parameters.begin()->second->getNextKeyframe(pos, &nextOk); } else { break; } } // qDebug()<<"/// \n\nKEYS TO DELETE: "<getCurrentFps()); GenTime new_out(out, pCore->getCurrentFps()); Keyframe kf = getKeyframe(old_out, &ok); KeyframeType type = kf.second; getKeyframe(new_out, &ok2); // Check keyframes after last position bool ok3; Keyframe toDel = getNextKeyframe(new_out, &ok3); if (ok && !ok2) { // Check if we have only 2 keyframes (in/out), in which case we move the out keyframe to new position bool ok4; kf = getPrevKeyframe(old_out, &ok4); if (ok4) { GenTime current_in(oldIn, pCore->getCurrentFps()); qDebug()<<" = = = = = = = \n\nGOT 2 KF SITUATION: "<moveKeyframe(old_out, new_out, QVariant(), undo, redo); } return; } } positions << old_out; } if (toDel.first == GenTime()) { // No keyframes return; } while (ok3) { if (!positions.contains(toDel.first)) { positions << toDel.first; } toDel = getNextKeyframe(toDel.first, &ok3); } if ((ok || positions.size() > 0) && !ok2) { for (const auto ¶m : m_parameters) { QVariant value = param.second->getInterpolatedValue(new_out); param.second->addKeyframe(new_out, type, value, true, undo, redo); for (auto frame : positions) { param.second->removeKeyframe(frame, undo, redo); } } } } } void KeyframeModelList::checkConsistency() { if (m_parameters.size() < 2) { return; } // Check keyframes in all parameters QList fullList; for (const auto ¶m : m_parameters) { QList list = param.second->getKeyframePos(); for (auto &time : list) { if (!fullList.contains(time)) { fullList << time; } } } Fun local_update = []() { return true; }; KeyframeType type = (KeyframeType)KdenliveSettings::defaultkeyframeinterp(); for (const auto ¶m : m_parameters) { QList list = param.second->getKeyframePos(); for (auto &time : fullList) { if (!list.contains(time)) { qDebug()<<" = = = \n\n = = = = \n\nWARNING; MISSING KF DETECTED AT: "<getInterpolatedValue(time); local_update = param.second->addKeyframe_lambda(time, type, missingVal, false); local_update(); } } } } + +GenTime KeyframeModelList::getPosAtIndex(int ix) +{ + QList positions = m_parameters.begin()->second->getKeyframePos(); + std::sort(positions.begin(), positions.end()); + if (ix < 0 || ix >= positions.count()) { + return GenTime(); + } + return positions.at(ix); +} diff --git a/src/assets/keyframes/model/keyframemodellist.hpp b/src/assets/keyframes/model/keyframemodellist.hpp index 31ed36543..3c4afe9a5 100644 --- a/src/assets/keyframes/model/keyframemodellist.hpp +++ b/src/assets/keyframes/model/keyframemodellist.hpp @@ -1,164 +1,170 @@ /*************************************************************************** * 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 KEYFRAMELISTMODELLIST_H #define KEYFRAMELISTMODELLIST_H #include "definitions.h" #include "gentime.h" #include "keyframemodel.hpp" #include "undohelper.hpp" #include #include #include #include #include class AssetParameterModel; class DocUndoStack; /* @brief This class is a container for the keyframe models. If an asset has several keyframable parameters, each one has its own keyframeModel, but we regroup all of these in a common class to provide unified access. */ class KeyframeModelList : public QObject { Q_OBJECT public: /* @brief Construct a keyframe list bound to the given asset @param init_value and index correspond to the first parameter */ explicit KeyframeModelList(std::weak_ptr model, const QModelIndex &index, std::weak_ptr undo_stack); /* @brief Add a keyframable parameter to be managed by this model */ void addParameter(const QModelIndex &index); /* @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); bool addKeyframe(int frame, double val); /* @brief Removes the keyframe at the given position. */ bool removeKeyframe(GenTime pos); /* @brief Delete all the keyframes of the model (except first) */ bool removeAllKeyframes(); /* @brief Delete all the keyframes after a certain position (except first) */ bool removeNextKeyframes(GenTime pos); /* @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 @param index is the index of the wanted keyframe */ bool updateKeyframe(GenTime pos, const QVariant &value, const QPersistentModelIndex &index); bool updateKeyframeType(GenTime pos, int type, const QPersistentModelIndex &index); bool updateKeyframe(GenTime oldPos, GenTime pos, const QVariant &normalizedVal, bool logUndo = true); KeyframeType keyframeType(GenTime pos) const; /* @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 we only have 1 keyframe */ bool singleKeyframe() const; /* @brief Returns true if we only have no keyframe */ bool isEmpty() const; + /* @brief Returns the number of keyframes + */ + int count() 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; /* @brief Return the interpolated value of a parameter. @param pos is the position where we interpolate @param index is the index of the queried parameter. */ QVariant getInterpolatedValue(int pos, const QPersistentModelIndex &index) const; /* @brief Load keyframes from the current parameter value. */ void refresh(); /* @brief Reset all keyframes and add a default one */ void reset(); Q_INVOKABLE KeyframeModel *getKeyModel(); KeyframeModel *getKeyModel(const QPersistentModelIndex &index); /** @brief Returns parent asset owner id*/ ObjectId getOwnerId() const; /** @brief Parent item size change, update keyframes*/ void resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo); + /** @brief Return position of the nth keyframe (ix = nth)*/ + GenTime getPosAtIndex(int ix); + /** @brief Check that all keyframable parameters have the same keyframes on loading * (that's how our model works) */ void checkConsistency(); protected: /** @brief Helper function to apply a given operation on all parameters */ bool applyOperation(const std::function, Fun &, Fun &)> &op, const QString &undoString); signals: void modelChanged(); private: std::weak_ptr m_model; std::weak_ptr m_undoStack; std::unordered_map> m_parameters; // Index of the parameter that is displayed in timeline QModelIndex m_inTimelineIndex; mutable QReadWriteLock m_lock; // This is a lock that ensures safety in case of concurrent access public: // this is to enable for range loops auto begin() -> decltype(m_parameters.begin()->second->begin()) { return m_parameters.begin()->second->begin(); } auto end() -> decltype(m_parameters.begin()->second->end()) { return m_parameters.begin()->second->end(); } }; #endif diff --git a/src/assets/keyframes/model/keyframemonitorhelper.cpp b/src/assets/keyframes/model/keyframemonitorhelper.cpp index 113d63d73..2fa8845fc 100644 --- a/src/assets/keyframes/model/keyframemonitorhelper.cpp +++ b/src/assets/keyframes/model/keyframemonitorhelper.cpp @@ -1,54 +1,132 @@ /* Copyright (C) 2018 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 . */ #include "keyframemonitorhelper.hpp" #include "assets/model/assetparametermodel.hpp" +#include "assets/keyframes/model/keyframemodellist.hpp" #include "monitor/monitor.h" #include +#include KeyframeMonitorHelper::KeyframeMonitorHelper(Monitor *monitor, std::shared_ptr model, const QPersistentModelIndex &index, QObject *parent) : QObject(parent) , m_monitor(monitor) , m_model(std::move(model)) , m_active(false) { m_indexes << index; } bool KeyframeMonitorHelper::connectMonitor(bool activate) { if (activate == m_active) { return false; } m_active = activate; if (activate) { connect(m_monitor, &Monitor::effectPointsChanged, this, &KeyframeMonitorHelper::slotUpdateFromMonitorData, Qt::UniqueConnection); } else { m_monitor->setEffectKeyframe(false); disconnect(m_monitor, &Monitor::effectPointsChanged, this, &KeyframeMonitorHelper::slotUpdateFromMonitorData); } return m_active; } void KeyframeMonitorHelper::addIndex(const QPersistentModelIndex &index) { m_indexes << index; } + +void KeyframeMonitorHelper::refreshParams(int pos) +{ + QVariantList points; + QVariantList types; + std::shared_ptr keyframes = m_model->getKeyframeModel(); + for (const auto &ix : m_indexes) { + auto type = m_model->data(ix, AssetParameterModel::TypeRole).value(); + if (type != ParamType::AnimatedRect) { + continue; + } + KeyframeModel *kfr = keyframes->getKeyModel(ix); + bool ok; + Keyframe kf = kfr->getNextKeyframe(GenTime(-1), &ok); + while (ok) { + if (kf.second == KeyframeType::Curve) { + types << 1; + } else { + types << 0; + } + QString rectData = kfr->getInterpolatedValue(kf.first).toString(); + QStringList data = rectData.split(QLatin1Char(' ')); + if (data.size() > 3) { + QRectF r(data.at(0).toInt(), data.at(1).toInt(), data.at(2).toInt(), data.at(3).toInt()); + points.append(QVariant(r.center())); + } + kf = kfr->getNextKeyframe(kf.first, &ok); + } + break; + } + if (m_monitor) { + m_monitor->setUpEffectGeometry(QRect(), points, types); + } +} + +void KeyframeMonitorHelper::slotUpdateFromMonitorData(const QVariantList ¢ers) +{ + std::shared_ptr keyframes = m_model->getKeyframeModel(); + if (centers.count() != keyframes->count()) { + qDebug() << "* * * *CENTER POINTS MISMATCH, aborting edit"; + return; + } + for (const auto &ix : m_indexes) { + auto type = m_model->data(ix, AssetParameterModel::TypeRole).value(); + if (type != ParamType::AnimatedRect) { + continue; + } + + KeyframeModel *kfr = keyframes->getKeyModel(ix); + bool ok; + Keyframe kf = kfr->getNextKeyframe(GenTime(-1), &ok); + int i = 0; + while (ok) { + QString rectData = kfr->getInterpolatedValue(kf.first).toString(); + QStringList data = rectData.split(QLatin1Char(' ')); + if (data.size() > 3) { + QRectF r(data.at(0).toInt(), data.at(1).toInt(), data.at(2).toInt(), data.at(3).toInt()); + QPointF pt(r.center()); + QPointF expected = centers.at(i).toPointF(); + if (pt != expected) { + // Center rect to new pos + QPointF offset = expected - pt; + r.translate(offset); + QString res = QString("%1 %2 %3 %4").arg((int)r.x()).arg((int)r.y()).arg((int)r.width()).arg((int)r.height()); + if (data.size() > 4) { + res.append(QString(" %1").arg(data.at(4))); + } + kfr->updateKeyframe(kf.first, res); + } + } + kf = kfr->getNextKeyframe(kf.first, &ok); + i++; + } + break; + } +} diff --git a/src/assets/keyframes/model/keyframemonitorhelper.hpp b/src/assets/keyframes/model/keyframemonitorhelper.hpp index e92719b00..75dea652b 100644 --- a/src/assets/keyframes/model/keyframemonitorhelper.hpp +++ b/src/assets/keyframes/model/keyframemonitorhelper.hpp @@ -1,80 +1,80 @@ /* Copyright (C) 2018 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 . */ #ifndef KFRMONITORHELPER_H #define KFRMONITORHELPER_H #include #include #include #include class Monitor; class AssetParameterModel; /** @brief This class helps manage effects that receive data from the monitor's qml overlay to translate the data and pass it to the model */ class KeyframeMonitorHelper : public QObject { 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 KeyframeMonitorHelper(Monitor *monitor, std::shared_ptr model, const QPersistentModelIndex &index, QObject *parent = nullptr); /** @brief Send signals to the monitor to update the qml overlay. @param returns : true if the monitor's connection was changed to active. */ bool connectMonitor(bool activate); /** @brief Send data update to the monitor */ - virtual void refreshParams(int pos) = 0; + virtual void refreshParams(int pos); protected: Monitor *m_monitor; std::shared_ptr m_model; /** @brief List of indexes managed by this class */ QList m_indexes; bool m_active; private slots: - virtual void slotUpdateFromMonitorData(const QVariantList &v) = 0; + virtual void slotUpdateFromMonitorData(const QVariantList &v); public slots: /** @brief For classes that manage several parameters, add a param index to the list */ void addIndex(const QPersistentModelIndex &index); signals: /** @brief Send updated keyframe data to the parameter @index */ void updateKeyframeData(QPersistentModelIndex index, const QVariant &v); }; #endif diff --git a/src/assets/view/widgets/keyframewidget.cpp b/src/assets/view/widgets/keyframewidget.cpp index b365d4854..16e0f11aa 100644 --- a/src/assets/view/widgets/keyframewidget.cpp +++ b/src/assets/view/widgets/keyframewidget.cpp @@ -1,508 +1,518 @@ /*************************************************************************** * 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/corners/cornershelper.hpp" #include "assets/keyframes/model/keyframemodellist.hpp" #include "assets/keyframes/model/rotoscoping/rotohelper.hpp" +#include "assets/keyframes/model/keyframemonitorhelper.hpp" #include "assets/keyframes/view/keyframeview.hpp" #include "assets/model/assetparametermodel.hpp" #include "assets/view/widgets/keyframeimport.h" #include "core.h" #include "kdenlivesettings.h" #include "monitor/monitor.h" #include "timecode.h" #include "timecodedisplay.h" #include "widgets/doublewidget.h" #include "widgets/geometrywidget.h" #include #include #include #include #include #include #include #include #include #include KeyframeWidget::KeyframeWidget(std::shared_ptr model, QModelIndex index, QSize frameSize, QWidget *parent) : AbstractParamWidget(std::move(model), index, parent) , m_monitorHelper(nullptr) , m_neededScene(MonitorSceneType::MonitorSceneDefault) , m_sourceFrameSize(frameSize.isValid() && !frameSize.isNull() ? frameSize : pCore->getCurrentFrameSize()) , m_baseHeight(0) , m_addedHeight(0) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_lay = new QVBoxLayout(this); m_lay->setSpacing(0); bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); m_model->prepareKeyframes(); m_keyframes = m_model->getKeyframeModel(); m_keyframeview = new KeyframeView(m_keyframes, duration, this); 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); connect(monitor, &Monitor::seekPosition, this, &KeyframeWidget::monitorSeek, Qt::UniqueConnection); + connect(monitor, &Monitor::seekToKeyframe, this, &KeyframeWidget::slotSeekToKeyframe, Qt::UniqueConnection); m_time = new TimecodeDisplay(pCore->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); // copy/paste keyframes from clipboard QAction *copy = new QAction(i18n("Copy keyframes to clipboard"), this); connect(copy, &QAction::triggered, this, &KeyframeWidget::slotCopyKeyframes); QAction *paste = new QAction(i18n("Import keyframes from clipboard"), this); connect(paste, &QAction::triggered, this, &KeyframeWidget::slotImportKeyframes); // Remove keyframes QAction *removeNext = new QAction(i18n("Remove all keyframes after cursor"), this); connect(removeNext, &QAction::triggered, this, &KeyframeWidget::slotRemoveNextKeyframes); // Default kf interpolation KSelectAction *kfType = new KSelectAction(i18n("Default keyframe type"), this); QAction *discrete2 = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this); discrete2->setData((int)mlt_keyframe_discrete); discrete2->setCheckable(true); kfType->addAction(discrete2); QAction *linear2 = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this); linear2->setData((int)mlt_keyframe_linear); linear2->setCheckable(true); kfType->addAction(linear2); QAction *curve2 = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this); curve2->setData((int)mlt_keyframe_smooth); curve2->setCheckable(true); kfType->addAction(curve2); switch (KdenliveSettings::defaultkeyframeinterp()) { case mlt_keyframe_discrete: kfType->setCurrentAction(discrete2); break; case mlt_keyframe_smooth: kfType->setCurrentAction(curve2); break; default: kfType->setCurrentAction(linear2); break; } connect(kfType, static_cast(&KSelectAction::triggered), [&](QAction *ac) { KdenliveSettings::setDefaultkeyframeinterp(ac->data().toInt()); }); auto *container = new QMenu(this); container->addAction(copy); container->addAction(paste); container->addSeparator(); container->addAction(kfType); container->addAction(removeNext); // Menu toolbutton auto *menuButton = new QToolButton(this); menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu"))); menuButton->setToolTip(i18n("Options")); menuButton->setMenu(container); menuButton->setPopupMode(QToolButton::InstantPopup); m_toolbar->addWidget(menuButton); m_toolbar->addWidget(m_time); m_lay->addWidget(m_keyframeview); m_lay->addWidget(m_toolbar); 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_keyframeview, &KeyframeView::activateEffect, this, &KeyframeWidget::activateEffect); 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); //m_baseHeight = m_keyframeview->height() + m_selectType->defaultWidget()->sizeHint().height(); QMargins mrg = m_lay->contentsMargins(); m_baseHeight = m_keyframeview->height() + m_toolbar->sizeHint().height() + mrg.top() + mrg.bottom(); setFixedHeight(m_baseHeight); 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 && pos > in); connectMonitor(isInRange); int framePos = qBound(in, pos, out) - in; if (isInRange && framePos != m_time->getValue()) { slotSetPosition(framePos, false); } } void KeyframeWidget::slotEditKeyframeType(QAction *action) { int type = action->data().toInt(); m_keyframeview->slotEditType(type, m_index); activateEffect(); } void KeyframeWidget::slotRefreshParams() { int pos = getPosition(); KeyframeType keyType = m_keyframes->keyframeType(GenTime(pos, pCore->getCurrentFps())); int i = 0; while (auto ac = m_selectType->action(i)) { if (ac->data().toInt() == (int)keyType) { m_selectType->setCurrentItem(i); break; } i++; } for (const auto &w : m_parameters) { auto 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); // m_model->dataChanged(QModelIndex(), QModelIndex()); //->getKeyframeModel()->getKeyModel(m_index)->dataChanged(QModelIndex(), QModelIndex()); m_keyframeview->setDuration(duration); m_time->setRange(0, duration - 1); slotRefreshParams(); } void KeyframeWidget::resetKeyframes() { // update duration bool ok = false; int duration = m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(&ok); Q_ASSERT(ok); // reset keyframes m_keyframes->refresh(); // m_model->dataChanged(QModelIndex(), QModelIndex()); 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(); auto 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()); const QString value = m_keyframes->getInterpolatedValue(getPosition(), index).toString(); + m_monitorHelper = new KeyframeMonitorHelper(pCore->getMonitor(m_model->monitorId), m_model, index, this); QRect rect; double opacity = 0; QStringList vals = value.split(QLatin1Char(' ')); - if (vals.count() >= 4) { + if (vals.count() > 3) { 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, m_sourceFrameSize, false, m_model->data(m_index, AssetParameterModel::OpacityRole).toBool(), integerOpacity, this); connect(geomWidget, &GeometryWidget::valueChanged, [this, index](const QString v) { activateEffect(); 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, min, max, factor, defaultValue, comment, -1, suffix, decimals, m_model->data(index, AssetParameterModel::OddRole).toBool(), this); connect(doubleWidget, &DoubleWidget::valueChanged, [this, index](double v) { activateEffect(); m_keyframes->updateKeyframe(GenTime(getPosition(), pCore->getCurrentFps()), QVariant(v), index); }); paramWidget = doubleWidget; } if (paramWidget) { m_parameters[index] = paramWidget; m_lay->addWidget(paramWidget); m_addedHeight += paramWidget->minimumHeight(); setFixedHeight(m_baseHeight + m_addedHeight); } } void KeyframeWidget::slotInitMonitor(bool active) { Monitor *monitor = pCore->getMonitor(m_model->monitorId); if (m_keyframeview) { m_keyframeview->initKeyframePos(); connect(monitor, &Monitor::updateScene, m_keyframeview, &KeyframeView::slotModelChanged, Qt::UniqueConnection); } connectMonitor(active); } void KeyframeWidget::connectMonitor(bool active) { if (m_monitorHelper) { if (m_monitorHelper->connectMonitor(active)) { slotRefreshParams(); } } for (const auto &w : m_parameters) { auto type = m_model->data(w.first, AssetParameterModel::TypeRole).value(); if (type == ParamType::AnimatedRect) { ((GeometryWidget *)w.second)->connectMonitor(active); break; } } } void KeyframeWidget::slotUpdateKeyframesFromMonitor(const QPersistentModelIndex &index, const QVariant &res) { activateEffect(); if (m_keyframes->isEmpty()) { GenTime pos(pCore->getItemIn(m_model->getOwnerId()) + m_time->getValue(), pCore->getCurrentFps()); if (m_time->getValue() > 0) { GenTime pos0(pCore->getItemIn(m_model->getOwnerId()), pCore->getCurrentFps()); m_keyframes->addKeyframe(pos0, KeyframeType::Linear); m_keyframes->updateKeyframe(pos0, res, index); } m_keyframes->addKeyframe(pos, KeyframeType::Linear); m_keyframes->updateKeyframe(pos, res, index); } else if (m_keyframes->hasKeyframe(getPosition()) || m_keyframes->singleKeyframe()) { GenTime pos(getPosition(), pCore->getCurrentFps()); m_keyframes->updateKeyframe(pos, res, index); } } MonitorSceneType KeyframeWidget::requiredScene() const { qDebug() << "// // // RESULTING REQUIRED SCENE: " << m_neededScene; return m_neededScene; } bool KeyframeWidget::keyframesVisible() const { return m_keyframeview->isVisible(); } void KeyframeWidget::showKeyframes(bool enable) { if (enable && m_toolbar->isVisible()) { return; } m_toolbar->setVisible(enable); m_keyframeview->setVisible(enable); setFixedHeight(m_addedHeight + (enable ? m_baseHeight : 0)); } void KeyframeWidget::slotCopyKeyframes() { QJsonDocument effectDoc = m_model->toJson(false); if (effectDoc.isEmpty()) { return; } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(QString(effectDoc.toJson())); } void KeyframeWidget::slotImportKeyframes() { QClipboard *clipboard = QApplication::clipboard(); QString values = clipboard->text(); QList indexes; for (const auto &w : m_parameters) { indexes << w.first; } QPointer import = new KeyframeImport(values, m_model, indexes, m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(), m_model->data(m_index, AssetParameterModel::ParentDurationRole).toInt(), this); if (import->exec() != QDialog::Accepted) { delete import; return; } import->importSelectedData(); /*m_model->getKeyframeModel()->getKeyModel()->dataChanged(QModelIndex(), QModelIndex());*/ /*m_model->modelChanged(); qDebug()<<"//// UPDATING KEYFRAMES CORE---------"; pCore->updateItemKeyframes(m_model->getOwnerId());*/ delete import; } void KeyframeWidget::slotRemoveNextKeyframes() { int pos = m_time->getValue() + m_model->data(m_index, AssetParameterModel::ParentInRole).toInt(); m_keyframes->removeNextKeyframes(GenTime(pos, pCore->getCurrentFps())); } + + +void KeyframeWidget::slotSeekToKeyframe(int ix) +{ + int pos = m_keyframes->getPosAtIndex(ix).frames(pCore->getCurrentFps()); + slotSetPosition(pos, true); +} diff --git a/src/assets/view/widgets/keyframewidget.hpp b/src/assets/view/widgets/keyframewidget.hpp index 5d3003d42..87b540b4b 100644 --- a/src/assets/view/widgets/keyframewidget.hpp +++ b/src/assets/view/widgets/keyframewidget.hpp @@ -1,108 +1,109 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef KEYFRAMEWIDGET_H #define KEYFRAMEWIDGET_H #include "abstractparamwidget.hpp" #include "definitions.h" #include #include #include class AssetParameterModel; class DoubleWidget; class KeyframeView; class KeyframeModelList; class QVBoxLayout; class QToolButton; class QToolBar; class TimecodeDisplay; class KSelectAction; class KeyframeMonitorHelper; class KeyframeWidget : public AbstractParamWidget { Q_OBJECT public: explicit KeyframeWidget(std::shared_ptr model, QModelIndex index, QSize frameSize, QWidget *parent = nullptr); ~KeyframeWidget() override; /* @brief Add a new parameter to be managed using the same keyframe viewer */ void addParameter(const QPersistentModelIndex &index); int getPosition() const; void addKeyframe(int pos = -1); /** @brief Returns the monitor scene required for this asset */ MonitorSceneType requiredScene() const; void updateTimecodeFormat(); /** @brief Show / hide keyframe related widgets */ void showKeyframes(bool enable); /** @brief Returns true if keyframes options are visible */ bool keyframesVisible() const; void resetKeyframes(); public slots: void slotRefresh() override; /** @brief initialize qml overlay */ void slotInitMonitor(bool active) override; public slots: void slotSetPosition(int pos = -1, bool update = true); private slots: /* brief Update the value of the widgets to reflect keyframe change */ void slotRefreshParams(); void slotAtKeyframe(bool atKeyframe, bool singleKeyframe); void monitorSeek(int pos); void slotEditKeyframeType(QAction *action); void slotUpdateKeyframesFromMonitor(const QPersistentModelIndex &index, const QVariant &res); void slotCopyKeyframes(); void slotImportKeyframes(); void slotRemoveNextKeyframes(); + void slotSeekToKeyframe(int ix); private: QVBoxLayout *m_lay; QToolBar *m_toolbar; std::shared_ptr m_keyframes; KeyframeView *m_keyframeview; KeyframeMonitorHelper *m_monitorHelper; QToolButton *m_buttonAddDelete; QToolButton *m_buttonPrevious; QToolButton *m_buttonNext; KSelectAction *m_selectType; TimecodeDisplay *m_time; MonitorSceneType m_neededScene; QSize m_sourceFrameSize; void connectMonitor(bool active); std::unordered_map m_parameters; int m_baseHeight; int m_addedHeight; signals: void addIndex(QPersistentModelIndex ix); void setKeyframes(const QString &); }; #endif