diff --git a/src/assets/keyframes/model/keyframemodel.cpp b/src/assets/keyframes/model/keyframemodel.cpp
index 78c7a9de4..f4a74627f 100644
--- a/src/assets/keyframes/model/keyframemodel.cpp
+++ b/src/assets/keyframes/model/keyframemodel.cpp
@@ -1,1238 +1,1238 @@
/***************************************************************************
* 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 = 0; // 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();
}
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;
if (auto ptr = m_model.lock()) {
ptr->passProperties(prop);
if (m_paramType == ParamType::AnimatedRect) {
useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
}
}
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));
}
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 (useOpacity) {
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);
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);
} else if (m_paramType == ParamType::Roto_spline) {
// interpolate
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())));
}
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);
- 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_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/model/assetcommand.cpp b/src/assets/model/assetcommand.cpp
index 983a100e9..c3e80e864 100644
--- a/src/assets/model/assetcommand.cpp
+++ b/src/assets/model/assetcommand.cpp
@@ -1,212 +1,214 @@
/***************************************************************************
* Copyright (C) 2017 by 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 . *
***************************************************************************/
#include "assetcommand.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "effects/effectsrepository.hpp"
#include "transitions/transitionsrepository.hpp"
#include
#include
AssetCommand::AssetCommand(const std::shared_ptr &model, const QModelIndex &index, QString value, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_index(index)
, m_value(std::move(value))
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
m_name = m_model->data(index, AssetParameterModel::NameRole).toString();
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
setText(i18n("Edit %1", EffectsRepository::get()->getName(id)));
} else if (TransitionsRepository::get()->exists(id)) {
setText(i18n("Edit %1", TransitionsRepository::get()->getName(id)));
}
QVariant previousVal = m_model->data(index, AssetParameterModel::ValueRole);
m_oldValue = previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString();
}
void AssetCommand::undo()
{
m_model->setParameter(m_name, m_oldValue, true, m_index);
}
// virtual
void AssetCommand::redo()
{
m_model->setParameter(m_name, m_value, m_updateView, m_index);
m_updateView = true;
}
// virtual
int AssetCommand::id() const
{
return 1;
}
// virtual
bool AssetCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_index != m_index ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) {
return false;
}
m_value = static_cast(other)->m_value;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetMultiCommand::AssetMultiCommand(const std::shared_ptr &model, const QList indexes, const QStringList values, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_indexes(indexes)
, m_values(values)
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
qDebug()<<"CREATING MULTIPLE COMMAND!!!\nVALUES: "<data(indexes.first(), AssetParameterModel::NameRole).toString();
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
setText(i18n("Edit %1", EffectsRepository::get()->getName(id)));
} else if (TransitionsRepository::get()->exists(id)) {
setText(i18n("Edit %1", TransitionsRepository::get()->getName(id)));
}
for (QModelIndex ix : m_indexes) {
QVariant previousVal = m_model->data(ix, AssetParameterModel::ValueRole);
m_oldValues << (previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString());
}
}
void AssetMultiCommand::undo()
{
int indx = 0;
int max = m_indexes.size() - 1;
for (const QModelIndex &ix : m_indexes) {
m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_oldValues.at(indx), indx == max, ix);
indx++;
}
}
// virtual
void AssetMultiCommand::redo()
{
int indx = 0;
int max = m_indexes.size() - 1;
for (const QModelIndex &ix : m_indexes) {
m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_values.at(indx), m_updateView && indx == max, ix);
indx++;
}
m_updateView = true;
}
// virtual
int AssetMultiCommand::id() const
{
return 1;
}
// virtual
bool AssetMultiCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_indexes != m_indexes ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) {
return false;
}
m_values = static_cast(other)->m_values;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetKeyframeCommand::AssetKeyframeCommand(const std::shared_ptr &model, const QModelIndex &index, QVariant value, GenTime pos,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_index(index)
, m_value(std::move(value))
, m_pos(pos)
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
setText(i18n("Edit %1 keyframe", EffectsRepository::get()->getName(id)));
} else if (TransitionsRepository::get()->exists(id)) {
setText(i18n("Edit %1 keyframe", TransitionsRepository::get()->getName(id)));
}
m_oldValue = m_model->getKeyframeModel()->getKeyModel(m_index)->getInterpolatedValue(m_pos);
}
void AssetKeyframeCommand::undo()
{
m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_oldValue);
}
// virtual
void AssetKeyframeCommand::redo()
{
m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_value);
m_updateView = true;
}
// virtual
int AssetKeyframeCommand::id() const
{
return 2;
}
// virtual
bool AssetKeyframeCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_index != m_index ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 1000) {
return false;
}
m_value = static_cast(other)->m_value;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetUpdateCommand::AssetUpdateCommand(const std::shared_ptr &model, QVector> parameters, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_value(std::move(parameters))
{
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
setText(i18n("Update %1", EffectsRepository::get()->getName(id)));
} else if (TransitionsRepository::get()->exists(id)) {
setText(i18n("Update %1", TransitionsRepository::get()->getName(id)));
}
m_oldValue = m_model->getAllParameters();
}
void AssetUpdateCommand::undo()
{
m_model->setParameters(m_oldValue);
}
// virtual
void AssetUpdateCommand::redo()
{
m_model->setParameters(m_value);
}
// virtual
int AssetUpdateCommand::id() const
{
return 3;
}
diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp
index 1922f35e4..b8f04c059 100644
--- a/src/assets/model/assetparametermodel.cpp
+++ b/src/assets/model/assetparametermodel.cpp
@@ -1,870 +1,871 @@
/***************************************************************************
* 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 "assetparametermodel.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "profiles/profilemodel.hpp"
#include
#include
#include
#include
#include
#include
AssetParameterModel::AssetParameterModel(std::unique_ptr asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
QObject *parent)
: QAbstractListModel(parent)
, monitorId(ownerId.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)
, m_assetId(assetId)
, m_ownerId(ownerId)
, m_asset(std::move(asset))
, m_keyframes(nullptr)
{
Q_ASSERT(m_asset->is_valid());
QDomNodeList nodeList = assetXml.elementsByTagName(QStringLiteral("parameter"));
m_hideKeyframesByDefault = assetXml.hasAttribute(QStringLiteral("hideKeyframes"));
m_isAudio = assetXml.attribute(QStringLiteral("type")) == QLatin1String("audio");
bool needsLocaleConversion = false;
QChar separator, oldSeparator;
// Check locale, default effects xml has no LC_NUMERIC defined and always uses the C locale
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
if (assetXml.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
QLocale effectLocale = QLocale(assetXml.attribute(QStringLiteral("LC_NUMERIC")));
if (QLocale::c().decimalPoint() != effectLocale.decimalPoint()) {
needsLocaleConversion = true;
separator = QLocale::c().decimalPoint();
oldSeparator = effectLocale.decimalPoint();
}
}
qDebug() << "XML parsing of " << assetId << ". found : " << nodeList.count();
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement currentParameter = nodeList.item(i).toElement();
// Convert parameters if we need to
if (needsLocaleConversion) {
QDomNamedNodeMap attrs = currentParameter.attributes();
for (int k = 0; k < attrs.count(); ++k) {
QString nodeName = attrs.item(k).nodeName();
if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) {
QString val = attrs.item(k).nodeValue();
if (val.contains(oldSeparator)) {
QString newVal = val.replace(oldSeparator, separator);
attrs.item(k).setNodeValue(newVal);
}
}
}
}
// Parse the basic attributes of the parameter
QString name = currentParameter.attribute(QStringLiteral("name"));
QString type = currentParameter.attribute(QStringLiteral("type"));
QString value = currentParameter.attribute(QStringLiteral("value"));
ParamRow currentRow;
currentRow.type = paramTypeFromStr(type);
currentRow.xml = currentParameter;
if (value.isEmpty()) {
QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter);
value = defaultValue.type() == QVariant::Double ? locale.toString(defaultValue.toDouble()) : defaultValue.toString();
}
bool isFixed = (type == QLatin1String("fixed"));
if (isFixed) {
m_fixedParams[name] = value;
} else if (currentRow.type == ParamType::Position) {
int val = value.toInt();
if (val < 0) {
int in = pCore->getItemIn(m_ownerId);
int out = in + pCore->getItemDuration(m_ownerId) - 1;
val += out;
value = QString::number(val);
}
} else if (currentRow.type == ParamType::KeyframeParam || currentRow.type == ParamType::AnimatedRect) {
if (!value.contains(QLatin1Char('='))) {
value.prepend(QStringLiteral("%1=").arg(pCore->getItemIn(m_ownerId)));
}
}
if (!name.isEmpty()) {
internalSetParameter(name, value);
// Keep track of param order
m_paramOrder.push_back(name);
}
if (isFixed) {
// fixed parameters are not displayed so we don't store them.
continue;
}
currentRow.value = value;
QString title = i18n(currentParameter.firstChildElement(QStringLiteral("name")).text().toUtf8().data());
currentRow.name = title.isEmpty() ? name : title;
m_params[name] = currentRow;
m_rows.push_back(name);
}
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Sox effects need to have a special "Effect" value set
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
}
qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size();
emit modelChanged();
}
void AssetParameterModel::prepareKeyframes()
{
if (m_keyframes) return;
int ix = 0;
for (const auto &name : m_rows) {
if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect ||
m_params.at(name).type == ParamType::Roto_spline) {
addKeyframeParam(index(ix, 0));
}
ix++;
}
if (m_keyframes) {
// Make sure we have keyframes at same position for all parameters
m_keyframes->checkConsistency();
}
}
QStringList AssetParameterModel::getKeyframableParameters() const
{
QStringList paramNames;
int ix = 0;
for (const auto &name : m_rows) {
if (m_params.at(name).type == ParamType::KeyframeParam || m_params.at(name).type == ParamType::AnimatedRect) {
//addKeyframeParam(index(ix, 0));
paramNames << name;
}
ix++;
}
return paramNames;
}
void AssetParameterModel::setParameter(const QString &name, int value, bool update)
{
Q_ASSERT(m_asset->is_valid());
m_asset->set(name.toLatin1().constData(), value);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
qDebug() << "// Warning, SOX effect, need unplug/replug";
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
} else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) {
// these effects don't understand param change and need to be rebuild
emit replugEffect(shared_from_this());
}
if (update) {
emit modelChanged();
emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {});
// Update fades in timeline
pCore->updateItemModel(m_ownerId, m_assetId);
if (!m_isAudio) {
// Trigger monitor refresh
pCore->refreshProjectItem(m_ownerId);
// Invalidate timeline preview
pCore->invalidateItem(m_ownerId);
}
}
}
void AssetParameterModel::internalSetParameter(const QString &name, const QString ¶mValue, const QModelIndex ¶mIndex)
{
Q_ASSERT(m_asset->is_valid());
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
// TODO: this does not really belong here, but I don't see another way to do it so that undo works
if (data(paramIndex, AssetParameterModel::TypeRole).value() == ParamType::Curve) {
QStringList vals = paramValue.split(QLatin1Char(';'), QString::SkipEmptyParts);
int points = vals.size();
m_asset->set("3", points / 10.);
// for the curve, inpoints are numbered: 6, 8, 10, 12, 14
// outpoints, 7, 9, 11, 13,15 so we need to deduce these enums
for (int i = 0; i < points; i++) {
const QString &pointVal = vals.at(i);
int idx = 2 * i + 6;
m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 0, 0).toDouble());
idx++;
m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 1, 1).toDouble());
}
}
bool conversionSuccess;
double doubleValue = locale.toDouble(paramValue, &conversionSuccess);
if (conversionSuccess) {
m_asset->set(name.toLatin1().constData(), doubleValue);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = doubleValue;
} else {
m_fixedParams[name] = doubleValue;
}
} else {
m_asset->set(name.toLatin1().constData(), paramValue.toUtf8().constData());
qDebug() << " = = SET EFFECT PARAM: " << name << " = " << paramValue;
if (m_fixedParams.count(name) == 0) {
m_params[name].value = paramValue;
if (m_keyframes) {
KeyframeModel *km = m_keyframes->getKeyModel(paramIndex);
if (km) {
km->refresh();
}
//m_keyframes->refresh();
}
} else {
m_fixedParams[name] = paramValue;
}
}
}
void AssetParameterModel::setParameter(const QString &name, const QString ¶mValue, bool update, const QModelIndex ¶mIndex)
{
//qDebug() << "// PROCESSING PARAM CHANGE: " << name << ", UPDATE: " << update << ", VAL: " << paramValue;
internalSetParameter(name, paramValue, paramIndex);
bool updateChildRequired = true;
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
updateChildRequired = false;
} else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) {
// these effects don't understand param change and need to be rebuild
emit replugEffect(shared_from_this());
updateChildRequired = false;
} else if (update) {
qDebug() << "// SENDING DATA CHANGE....";
if (paramIndex.isValid()) {
emit dataChanged(paramIndex, paramIndex);
} else {
QModelIndex ix = index(m_rows.indexOf(name), 0);
emit dataChanged(ix, ix);
}
emit modelChanged();
}
if (updateChildRequired) {
emit updateChildren(name);
}
// Update timeline view if necessary
if (m_ownerId.first == ObjectType::NoItem) {
// Used for generator clips
if (!update) emit modelChanged();
} else {
// Update fades in timeline
pCore->updateItemModel(m_ownerId, m_assetId);
if (!m_isAudio) {
// Trigger monitor refresh
pCore->refreshProjectItem(m_ownerId);
// Invalidate timeline preview
pCore->invalidateItem(m_ownerId);
}
}
}
AssetParameterModel::~AssetParameterModel() = default;
QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) {
return QVariant();
}
QString paramName = m_rows[index.row()];
Q_ASSERT(m_params.count(paramName) > 0);
const QDomElement &element = m_params.at(paramName).xml;
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return m_params.at(paramName).name;
case NameRole:
return paramName;
case TypeRole:
return QVariant::fromValue(m_params.at(paramName).type);
case CommentRole: {
QDomElement commentElem = element.firstChildElement(QStringLiteral("comment"));
QString comment;
if (!commentElem.isNull()) {
comment = i18n(commentElem.text().toUtf8().data());
}
return comment;
}
case InRole:
return m_asset->get_int("in");
case OutRole:
return m_asset->get_int("out");
case ParentInRole:
return pCore->getItemIn(m_ownerId);
case ParentDurationRole:
return pCore->getItemDuration(m_ownerId);
case ParentPositionRole:
return pCore->getItemPosition(m_ownerId);
case HideKeyframesFirstRole:
return m_hideKeyframesByDefault;
case MinRole:
return parseAttribute(m_ownerId, QStringLiteral("min"), element);
case MaxRole:
return parseAttribute(m_ownerId, QStringLiteral("max"), element);
case FactorRole:
return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1);
case ScaleRole:
return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0);
case DecimalsRole:
return parseAttribute(m_ownerId, QStringLiteral("decimals"), element);
case OddRole:
return element.attribute(QStringLiteral("odd")) == QLatin1String("1");
case DefaultRole:
return parseAttribute(m_ownerId, QStringLiteral("default"), element);
case FilterRole:
return parseAttribute(m_ownerId, QStringLiteral("filter"), element);
case FilterParamsRole:
return parseAttribute(m_ownerId, QStringLiteral("filterparams"), element);
case FilterJobParamsRole:
return parseSubAttributes(QStringLiteral("jobparam"), element);
case AlternateNameRole: {
QDomNode child = element.firstChildElement(QStringLiteral("name"));
if (child.toElement().hasAttribute(QStringLiteral("conditional"))) {
return child.toElement().attribute(QStringLiteral("conditional"));
}
return m_params.at(paramName).name;
}
case SuffixRole:
return element.attribute(QStringLiteral("suffix"));
case OpacityRole:
return element.attribute(QStringLiteral("opacity")) != QLatin1String("false");
case RelativePosRole:
return element.attribute(QStringLiteral("relative")) == QLatin1String("true");
case ShowInTimelineRole:
return !element.hasAttribute(QStringLiteral("notintimeline"));
case AlphaRole:
return element.attribute(QStringLiteral("alpha")) == QLatin1String("1");
case ValueRole: {
QString value(m_asset->get(paramName.toUtf8().constData()));
return value.isEmpty() ? (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(m_ownerId, QStringLiteral("default"), element)
: element.attribute(QStringLiteral("value")))
: value;
}
case ListValuesRole:
return element.attribute(QStringLiteral("paramlist")).split(QLatin1Char(';'));
case ListNamesRole: {
QDomElement namesElem = element.firstChildElement(QStringLiteral("paramlistdisplay"));
return i18n(namesElem.text().toUtf8().data()).split(QLatin1Char(','));
}
case List1Role:
return parseAttribute(m_ownerId, QStringLiteral("list1"), element);
case List2Role:
return parseAttribute(m_ownerId, QStringLiteral("list2"), element);
case Enum1Role:
return m_asset->get_double("1");
case Enum2Role:
return m_asset->get_double("2");
case Enum3Role:
return m_asset->get_double("3");
case Enum4Role:
return m_asset->get_double("4");
case Enum5Role:
return m_asset->get_double("5");
case Enum6Role:
return m_asset->get_double("6");
case Enum7Role:
return m_asset->get_double("7");
case Enum8Role:
return m_asset->get_double("8");
case Enum9Role:
return m_asset->get_double("9");
case Enum10Role:
return m_asset->get_double("10");
case Enum11Role:
return m_asset->get_double("11");
case Enum12Role:
return m_asset->get_double("12");
case Enum13Role:
return m_asset->get_double("13");
case Enum14Role:
return m_asset->get_double("14");
case Enum15Role:
return m_asset->get_double("15");
}
return QVariant();
}
int AssetParameterModel::rowCount(const QModelIndex &parent) const
{
qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
if (parent.isValid()) return 0;
return m_rows.size();
}
// static
ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
{
if (type == QLatin1String("double") || type == QLatin1String("float") || type == QLatin1String("constant")) {
return ParamType::Double;
}
if (type == QLatin1String("list")) {
return ParamType::List;
}
if (type == QLatin1String("bool")) {
return ParamType::Bool;
}
if (type == QLatin1String("switch")) {
return ParamType::Switch;
} else if (type == QLatin1String("simplekeyframe")) {
return ParamType::KeyframeParam;
} else if (type == QLatin1String("animatedrect")) {
return ParamType::AnimatedRect;
} else if (type == QLatin1String("geometry")) {
return ParamType::Geometry;
} else if (type == QLatin1String("addedgeometry")) {
return ParamType::Addedgeometry;
} else if (type == QLatin1String("keyframe") || type == QLatin1String("animated")) {
return ParamType::KeyframeParam;
} else if (type == QLatin1String("color")) {
return ParamType::Color;
} else if (type == QLatin1String("colorwheel")) {
return ParamType::ColorWheel;
} else if (type == QLatin1String("position")) {
return ParamType::Position;
} else if (type == QLatin1String("curve")) {
return ParamType::Curve;
} else if (type == QLatin1String("bezier_spline")) {
return ParamType::Bezier_spline;
} else if (type == QLatin1String("roto-spline")) {
return ParamType::Roto_spline;
} else if (type == QLatin1String("wipe")) {
return ParamType::Wipe;
} else if (type == QLatin1String("url")) {
return ParamType::Url;
} else if (type == QLatin1String("keywords")) {
return ParamType::Keywords;
} else if (type == QLatin1String("fontfamily")) {
return ParamType::Fontfamily;
} else if (type == QLatin1String("filterjob")) {
return ParamType::Filterjob;
} else if (type == QLatin1String("readonly")) {
return ParamType::Readonly;
} else if (type == QLatin1String("hidden")) {
return ParamType::Hidden;
}
qDebug() << "WARNING: Unknown type :" << type;
return ParamType::Double;
}
// static
QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
{
QString keyframes = QString::number(start);
if (linearOnly) {
keyframes.append(QLatin1Char('='));
} else {
switch (KdenliveSettings::defaultkeyframeinterp()) {
case mlt_keyframe_discrete:
keyframes.append(QStringLiteral("|="));
break;
case mlt_keyframe_smooth:
keyframes.append(QStringLiteral("~="));
break;
default:
keyframes.append(QLatin1Char('='));
break;
}
}
keyframes.append(defaultValue);
return keyframes;
}
// static
QVariant AssetParameterModel::parseAttribute(const ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue)
{
if (!element.hasAttribute(attribute) && !defaultValue.isNull()) {
return defaultValue;
}
ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type")));
QString content = element.attribute(attribute);
if (content.contains(QLatin1Char('%'))) {
std::unique_ptr &profile = pCore->getCurrentProfile();
int width = profile->width();
int height = profile->height();
int in = pCore->getItemIn(owner);
int out = in + pCore->getItemDuration(owner);
int frame_duration = pCore->getDurationFromString(KdenliveSettings::fade_duration());
// replace symbols in the double parameter
content.replace(QLatin1String("%maxWidth"), QString::number(width))
.replace(QLatin1String("%maxHeight"), QString::number(height))
.replace(QLatin1String("%width"), QString::number(width))
.replace(QLatin1String("%height"), QString::number(height))
.replace(QLatin1String("%out"), QString::number(out))
.replace(QLatin1String("%fade"), QString::number(frame_duration));
if (type == ParamType::Double || type == ParamType::Hidden) {
// Use a Mlt::Properties to parse mathematical operators
Mlt::Properties p;
p.set("eval", content.prepend(QLatin1Char('@')).toLatin1().constData());
return p.get_double("eval");
}
} else if (type == ParamType::Double || type == ParamType::Hidden) {
if (attribute == QLatin1String("default")) {
return content.toDouble();
}
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
return locale.toDouble(content);
}
if (attribute == QLatin1String("default")) {
if (type == ParamType::RestrictedAnim) {
content = getDefaultKeyframes(0, content, true);
} else if (type == ParamType::KeyframeParam) {
return content.toDouble();
} else if (type == ParamType::List) {
bool ok;
double res = content.toDouble(&ok);
if (ok) {
return res;
}
return defaultValue.isNull() ? content : defaultValue;
} else if (type == ParamType::Bezier_spline) {
QLocale locale;
if (locale.decimalPoint() != QLocale::c().decimalPoint()) {
return content.replace(QLocale::c().decimalPoint(), locale.decimalPoint());
}
}
}
return content;
}
QVariant AssetParameterModel::parseSubAttributes(const QString &attribute, const QDomElement &element) const
{
QDomNodeList nodeList = element.elementsByTagName(attribute);
if (nodeList.isEmpty()) {
return QVariant();
}
QVariantList jobDataList;
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement currentParameter = nodeList.item(i).toElement();
QStringList jobData {currentParameter.attribute(QStringLiteral("name")), currentParameter.text()};
jobDataList << jobData;
}
return jobDataList;
}
QString AssetParameterModel::getAssetId() const
{
return m_assetId;
}
QVector> AssetParameterModel::getAllParameters() const
{
QVector> res;
res.reserve((int)m_fixedParams.size() + (int)m_params.size());
for (const auto &fixed : m_fixedParams) {
res.push_back(QPair(fixed.first, fixed.second));
}
for (const auto ¶m : m_params) {
res.push_back(QPair(param.first, param.second.value));
}
return res;
}
QJsonDocument AssetParameterModel::toJson(bool includeFixed) const
{
QJsonArray list;
- QLocale locale;
if (includeFixed) {
for (const auto &fixed : m_fixedParams) {
QJsonObject currentParam;
QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
currentParam.insert(QLatin1String("value"), fixed.second.type() == QVariant::Double ? QJsonValue(fixed.second.toDouble()) : QJsonValue(fixed.second.toString()));
int type = data(ix, AssetParameterModel::TypeRole).toInt();
double min = data(ix, AssetParameterModel::MinRole).toDouble();
double max = data(ix, AssetParameterModel::MaxRole).toDouble();
double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
int in = data(ix, AssetParameterModel::ParentInRole).toInt();
int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
if (factor > 0) {
min /= factor;
max /= factor;
}
currentParam.insert(QLatin1String("type"), QJsonValue(type));
currentParam.insert(QLatin1String("min"), QJsonValue(min));
currentParam.insert(QLatin1String("max"), QJsonValue(max));
currentParam.insert(QLatin1String("in"), QJsonValue(in));
currentParam.insert(QLatin1String("out"), QJsonValue(out));
list.push_back(currentParam);
}
}
for (const auto ¶m : m_params) {
if (!includeFixed && param.second.type != ParamType::KeyframeParam && param.second.type != ParamType::AnimatedRect) {
continue;
}
QJsonObject currentParam;
QModelIndex ix = index(m_rows.indexOf(param.first), 0);
currentParam.insert(QLatin1String("name"), QJsonValue(param.first));
currentParam.insert(QLatin1String("value"), param.second.value.type() == QVariant::Double ? QJsonValue(param.second.value.toDouble()) : QJsonValue(param.second.value.toString()));
int type = data(ix, AssetParameterModel::TypeRole).toInt();
double min = data(ix, AssetParameterModel::MinRole).toDouble();
double max = data(ix, AssetParameterModel::MaxRole).toDouble();
double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
int in = data(ix, AssetParameterModel::ParentInRole).toInt();
int out = in + data(ix, AssetParameterModel::ParentDurationRole).toInt();
if (factor > 0) {
min /= factor;
max /= factor;
}
currentParam.insert(QLatin1String("type"), QJsonValue(type));
currentParam.insert(QLatin1String("min"), QJsonValue(min));
currentParam.insert(QLatin1String("max"), QJsonValue(max));
currentParam.insert(QLatin1String("in"), QJsonValue(in));
currentParam.insert(QLatin1String("out"), QJsonValue(out));
list.push_back(currentParam);
}
return QJsonDocument(list);
}
void AssetParameterModel::deletePreset(const QString &presetFile, const QString &presetName)
{
QJsonObject object;
QJsonArray array;
QFile loadFile(presetFile);
if (loadFile.exists()) {
if (loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isArray()) {
array = loadDoc.array();
QList toDelete;
for (int i = 0; i < array.size(); i++) {
QJsonValue val = array.at(i);
if (val.isObject() && val.toObject().keys().contains(presetName)) {
toDelete << i;
}
}
for (int i : toDelete) {
array.removeAt(i);
}
} else if (loadDoc.isObject()) {
QJsonObject obj = loadDoc.object();
qDebug() << " * * ** JSON IS AN OBJECT, DELETING: " << presetName;
if (obj.keys().contains(presetName)) {
obj.remove(presetName);
} else {
qDebug() << " * * ** JSON DOES NOT CONTAIN: " << obj.keys();
}
array.append(obj);
}
loadFile.close();
}
}
if (!loadFile.open(QIODevice::WriteOnly)) {
pCore->displayMessage(i18n("Cannot open preset file %1", presetFile), ErrorMessage);
return;
}
if (array.isEmpty()) {
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (dir.exists(presetFile)) {
// Ensure we don't delete an unwanted file
loadFile.remove();
}
} else {
loadFile.write(QJsonDocument(array).toJson());
}
}
void AssetParameterModel::savePreset(const QString &presetFile, const QString &presetName)
{
QJsonObject object;
QJsonArray array;
QJsonDocument doc = toJson();
QFile loadFile(presetFile);
if (loadFile.exists()) {
if (loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isArray()) {
array = loadDoc.array();
QList toDelete;
for (int i = 0; i < array.size(); i++) {
QJsonValue val = array.at(i);
if (val.isObject() && val.toObject().keys().contains(presetName)) {
toDelete << i;
}
}
for (int i : toDelete) {
array.removeAt(i);
}
} else if (loadDoc.isObject()) {
QJsonObject obj = loadDoc.object();
if (obj.keys().contains(presetName)) {
obj.remove(presetName);
}
array.append(obj);
}
loadFile.close();
}
}
if (!loadFile.open(QIODevice::WriteOnly)) {
pCore->displayMessage(i18n("Cannot open preset file %1", presetFile), ErrorMessage);
return;
}
object[presetName] = doc.array();
array.append(object);
loadFile.write(QJsonDocument(array).toJson());
}
const QStringList AssetParameterModel::getPresetList(const QString &presetFile) const
{
QFile loadFile(presetFile);
if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isObject()) {
qDebug() << "// PRESET LIST IS AN OBJECT!!!";
return loadDoc.object().keys();
} else if (loadDoc.isArray()) {
qDebug() << "// PRESET LIST IS AN ARRAY!!!";
QStringList result;
QJsonArray array = loadDoc.array();
for (auto &&i : array) {
QJsonValue val = i;
if (val.isObject()) {
result << val.toObject().keys();
}
}
return result;
}
}
return QStringList();
}
const QVector> AssetParameterModel::loadPreset(const QString &presetFile, const QString &presetName)
{
QFile loadFile(presetFile);
QVector> params;
if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isObject() && loadDoc.object().contains(presetName)) {
qDebug() << "..........\n..........\nLOADING OBJECT JSON";
QJsonValue val = loadDoc.object().value(presetName);
if (val.isObject()) {
QVariantMap map = val.toObject().toVariantMap();
QMap::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
params.append({i.key(), i.value()});
++i;
}
}
} else if (loadDoc.isArray()) {
QJsonArray array = loadDoc.array();
for (auto &&i : array) {
QJsonValue val = i;
if (val.isObject() && val.toObject().contains(presetName)) {
QJsonValue preset = val.toObject().value(presetName);
if (preset.isArray()) {
QJsonArray paramArray = preset.toArray();
for (auto &&j : paramArray) {
QJsonValue v1 = j;
if (v1.isObject()) {
QJsonObject ob = v1.toObject();
params.append({ob.value("name").toString(), ob.value("value").toVariant()});
}
}
}
qDebug() << "// LOADED PRESET: " << presetName << "\n" << params;
break;
}
}
}
}
return params;
}
void AssetParameterModel::setParameters(const QVector> ¶ms)
{
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
setParameter(param.first, locale.toString(param.second.toDouble()), false);
} else {
setParameter(param.first, param.second.toString(), false);
}
}
if (m_keyframes) {
m_keyframes->refresh();
}
emit dataChanged(index(0), index(m_rows.count()), {});
}
ObjectId AssetParameterModel::getOwnerId() const
{
return m_ownerId;
}
void AssetParameterModel::addKeyframeParam(const QModelIndex &index)
{
if (m_keyframes) {
m_keyframes->addParameter(index);
} else {
m_keyframes.reset(new KeyframeModelList(shared_from_this(), index, pCore->undoStack()));
}
}
std::shared_ptr AssetParameterModel::getKeyframeModel()
{
return m_keyframes;
}
void AssetParameterModel::resetAsset(std::unique_ptr asset)
{
m_asset = std::move(asset);
}
bool AssetParameterModel::hasMoreThanOneKeyframe() const
{
if (m_keyframes) {
return (!m_keyframes->isEmpty() && !m_keyframes->singleKeyframe());
}
return false;
}
int AssetParameterModel::time_to_frames(const QString &time)
{
return m_asset->time_to_frames(time.toUtf8().constData());
}
void AssetParameterModel::passProperties(Mlt::Properties &target)
{
target.set("_profile", pCore->getCurrentProfile()->get_profile(), 0);
target.set_lcnumeric(m_asset->get_lcnumeric());
}
diff --git a/src/assets/view/assetparameterview.cpp b/src/assets/view/assetparameterview.cpp
index 5ceef648b..65073b6b4 100644
--- a/src/assets/view/assetparameterview.cpp
+++ b/src/assets/view/assetparameterview.cpp
@@ -1,385 +1,386 @@
/***************************************************************************
* 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 "assetparameterview.hpp"
#include "assets/model/assetcommand.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "assets/view/widgets/abstractparamwidget.hpp"
#include "assets/view/widgets/keyframewidget.hpp"
#include "core.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
AssetParameterView::AssetParameterView(QWidget *parent)
: QWidget(parent)
{
m_lay = new QVBoxLayout(this);
m_lay->setContentsMargins(0, 0, 0, 2);
m_lay->setSpacing(0);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
// Presets Combo
m_presetMenu = new QMenu(this);
}
void AssetParameterView::setModel(const std::shared_ptr &model, QSize frameSize, bool addSpacer)
{
unsetModel();
QMutexLocker lock(&m_lock);
m_model = model;
setSizePolicy(QSizePolicy::Preferred, addSpacer ? QSizePolicy::Preferred : QSizePolicy::Fixed);
const QString paramTag = model->getAssetId();
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(paramTag));
connect(this, &AssetParameterView::updatePresets, [this, presetFile](const QString &presetName) {
m_presetMenu->clear();
m_presetGroup.reset(new QActionGroup(this));
m_presetGroup->setExclusive(true);
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Reset Effect"), this, SLOT(resetValues()));
// Save preset
m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Save preset"), this, SLOT(slotSavePreset()));
QAction *updatePreset = m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("document-save-as-template")), i18n("Update current preset"), this, SLOT(slotUpdatePreset()));
QAction *deletePreset = m_presetMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this, SLOT(slotDeletePreset()));
m_presetMenu->addSeparator();
QStringList presets = m_model->getPresetList(presetFile);
if (presetName.isEmpty() || presets.isEmpty()) {
updatePreset->setEnabled(false);
deletePreset->setEnabled(false);
}
for (const QString &pName : presets) {
QAction *ac = m_presetMenu->addAction(pName, this, SLOT(slotLoadPreset()));
m_presetGroup->addAction(ac);
ac->setData(pName);
ac->setCheckable(true);
if (pName == presetName) {
ac->setChecked(true);
}
}
});
emit updatePresets();
connect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
if (paramTag.endsWith(QStringLiteral("lift_gamma_gain"))) {
// Special case, the colorwheel widget manages several parameters
QModelIndex index = model->index(0, 0);
auto w = AbstractParamWidget::construct(model, index, frameSize, this);
connect(w, &AbstractParamWidget::valuesChanged, this, &AssetParameterView::commitMultipleChanges);
connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
m_lay->addWidget(w);
connect(w, &AbstractParamWidget::updateHeight, [&, w](int h) {
setFixedHeight(h + m_lay->contentsMargins().bottom());
emit updateHeight();
});
m_widgets.push_back(w);
} else {
int minHeight = 0;
for (int i = 0; i < model->rowCount(); ++i) {
QModelIndex index = model->index(i, 0);
auto type = model->data(index, AssetParameterModel::TypeRole).value();
if (m_mainKeyframeWidget &&
(type == ParamType::Geometry || type == ParamType::Animated || type == ParamType::RestrictedAnim || type == ParamType::KeyframeParam)) {
// Keyframe widget can have some extra params that shouldn't build a new widget
qDebug() << "// FOUND ADDED PARAM";
m_mainKeyframeWidget->addParameter(index);
} else {
auto w = AbstractParamWidget::construct(model, index, frameSize, this);
connect(this, &AssetParameterView::initKeyframeView, w, &AbstractParamWidget::slotInitMonitor);
connect(w, &AbstractParamWidget::valueChanged, this, &AssetParameterView::commitChanges);
connect(w, &AbstractParamWidget::seekToPos, this, &AssetParameterView::seekToPos);
connect(w, &AbstractParamWidget::updateHeight, [&, w]() {
setFixedHeight(contentHeight());
emit updateHeight();
});
m_lay->addWidget(w);
if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect || type == ParamType::Roto_spline) {
m_mainKeyframeWidget = static_cast(w);
} else {
minHeight += w->minimumHeight();
}
m_widgets.push_back(w);
}
}
setMinimumHeight(m_mainKeyframeWidget ? m_mainKeyframeWidget->minimumHeight() + minHeight : minHeight);
}
if (addSpacer) {
m_lay->addStretch();
}
}
QVector> AssetParameterView::getDefaultValues() const
{
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
QVector> values;
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
QString name = m_model->data(index, AssetParameterModel::NameRole).toString();
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
QVariant defaultValue = m_model->data(index, AssetParameterModel::DefaultRole);
if (type == ParamType::KeyframeParam || type == ParamType::AnimatedRect) {
QString val = type == ParamType::KeyframeParam ? locale.toString(defaultValue.toDouble()) : defaultValue.toString();
if (!val.contains(QLatin1Char('='))) {
val.prepend(QStringLiteral("%1=").arg(m_model->data(index, AssetParameterModel::ParentInRole).toInt()));
defaultValue = QVariant(val);
}
}
values.append({name, defaultValue});
}
return values;
}
void AssetParameterView::resetValues()
{
const QVector> values = getDefaultValues();
auto *command = new AssetUpdateCommand(m_model, values);
if (m_model->getOwnerId().second != -1) {
pCore->pushUndo(command);
} else {
command->redo();
delete command;
}
// Unselect preset if any
QAction *ac = m_presetGroup->checkedAction();
if (ac) {
ac->setChecked(false);;
}
}
void AssetParameterView::commitChanges(const QModelIndex &index, const QString &value, bool storeUndo)
{
// Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
auto *command = new AssetCommand(m_model, index, value);
if (storeUndo && m_model->getOwnerId().second != -1) {
pCore->pushUndo(command);
} else {
command->redo();
delete command;
}
}
void AssetParameterView::commitMultipleChanges(const QList indexes, const QStringList &values, bool storeUndo)
{
// Warning: please note that some widgets (for example keyframes) do NOT send the valueChanged signal and do modifications on their own
auto *command = new AssetMultiCommand(m_model, indexes, values);
if (storeUndo) {
pCore->pushUndo(command);
} else {
command->redo();
delete command;
}
}
void AssetParameterView::unsetModel()
{
QMutexLocker lock(&m_lock);
if (m_model) {
// if a model is already there, we have to disconnect signals first
disconnect(m_model.get(), &AssetParameterModel::dataChanged, this, &AssetParameterView::refresh);
}
m_mainKeyframeWidget = nullptr;
// clear layout
m_widgets.clear();
QLayoutItem *child;
while ((child = m_lay->takeAt(0)) != nullptr) {
if (child->layout()) {
QLayoutItem *subchild;
while ((subchild = child->layout()->takeAt(0)) != nullptr) {
delete subchild->widget();
delete subchild->spacerItem();
}
}
delete child->widget();
delete child->spacerItem();
}
// Release ownership of smart pointer
m_model.reset();
}
void AssetParameterView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles)
{
QMutexLocker lock(&m_lock);
if (m_widgets.size() == 0) {
// no visible param for this asset, abort
return;
}
Q_UNUSED(roles);
// We are expecting indexes that are children of the root index, which is "invalid"
Q_ASSERT(!topLeft.parent().isValid());
// We make sure the range is valid
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->slotRefresh();
} else {
auto type = m_model->data(m_model->index(topLeft.row(), 0), AssetParameterModel::TypeRole).value();
if (type == ParamType::ColorWheel) {
// Some special widgets, like colorwheel handle multiple params so we can have cases where param index row is greater than the number of widgets.
// Should be better managed
m_widgets[0]->slotRefresh();
return;
}
size_t max;
if (!bottomRight.isValid()) {
max = m_widgets.size() - 1;
} else {
max = (size_t)bottomRight.row();
}
Q_ASSERT(max < m_widgets.size());
for (size_t i = (size_t)topLeft.row(); i <= max; ++i) {
m_widgets[i]->slotRefresh();
}
}
}
int AssetParameterView::contentHeight() const
{
return m_lay->minimumSize().height();
}
MonitorSceneType AssetParameterView::needsMonitorEffectScene() const
{
if (m_mainKeyframeWidget) {
return m_mainKeyframeWidget->requiredScene();
}
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
if (type == ParamType::Geometry) {
return MonitorSceneGeometry;
}
}
return MonitorSceneDefault;
}
/*void AssetParameterView::initKeyframeView()
{
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->initMonitor();
} else {
for (int i = 0; i < m_model->rowCount(); ++i) {
QModelIndex index = m_model->index(i, 0);
auto type = m_model->data(index, AssetParameterModel::TypeRole).value();
if (type == ParamType::Geometry) {
return MonitorSceneGeometry;
}
}
}
}*/
void AssetParameterView::slotRefresh()
{
refresh(m_model->index(0, 0), m_model->index(m_model->rowCount() - 1, 0), {});
}
bool AssetParameterView::keyframesAllowed() const
{
return m_mainKeyframeWidget != nullptr;
}
bool AssetParameterView::modelHideKeyframes() const
{
return m_mainKeyframeWidget != nullptr && !m_mainKeyframeWidget->keyframesVisible();
}
void AssetParameterView::toggleKeyframes(bool enable)
{
if (m_mainKeyframeWidget) {
m_mainKeyframeWidget->showKeyframes(enable);
setFixedHeight(contentHeight());
emit updateHeight();
}
}
void AssetParameterView::slotDeletePreset()
{
QAction *ac = m_presetGroup->checkedAction();
if (!ac) {
return;
}
slotDeletePreset(ac->data().toString());
}
void AssetParameterView::slotDeletePreset(const QString &presetName)
{
if (presetName.isEmpty()) {
return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (dir.exists()) {
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
m_model->deletePreset(presetFile, presetName);
emit updatePresets();
}
}
void AssetParameterView::slotUpdatePreset()
{
QAction *ac = m_presetGroup->checkedAction();
if (!ac) {
return;
}
slotSavePreset(ac->data().toString());
}
void AssetParameterView::slotSavePreset(QString presetName)
{
if (presetName.isEmpty()) {
bool ok;
presetName = QInputDialog::getText(this, i18n("Enter preset name"), i18n("Enter the name of this preset"), QLineEdit::Normal, QString(), &ok);
if (!ok) return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
m_model->savePreset(presetFile, presetName);
emit updatePresets(presetName);
}
void AssetParameterView::slotLoadPreset()
{
auto *action = qobject_cast(sender());
if (!action) {
return;
}
const QString presetName = action->data().toString();
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
const QString presetFile = dir.absoluteFilePath(QString("%1.json").arg(m_model->getAssetId()));
const QVector> params = m_model->loadPreset(presetFile, presetName);
auto *command = new AssetUpdateCommand(m_model, params);
pCore->pushUndo(command);
emit updatePresets(presetName);
}
QMenu *AssetParameterView::presetMenu()
{
return m_presetMenu;
}
diff --git a/src/assets/view/widgets/animationwidget.cpp b/src/assets/view/widgets/animationwidget.cpp
index 39c6e1aaf..85cfb6adc 100644
--- a/src/assets/view/widgets/animationwidget.cpp
+++ b/src/assets/view/widgets/animationwidget.cpp
@@ -1,1687 +1,1686 @@
/***************************************************************************
* Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* 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 "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "mlt++/MltAnimation.h"
#include "mlt++/MltProfile.h"
#include "animationwidget.h"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "mltcontroller/effectscontroller.h"
#include "monitor/monitor.h"
#include "timecodedisplay.h"
#include "widgets/doublewidget.h"
#include "widgets/dragvalue.h"
AnimationWidget::AnimationWidget(std::shared_ptr model, QModelIndex index, QPair range, QWidget *parent)
: AbstractParamWidget(std::move(model), index, parent)
, m_active(false)
//, m_clipPos(clipPos)
, m_editedKeyframe(-1)
, m_attachedToEnd(-2)
//, m_xml(xml)
//, m_effectId(effectId)
, m_spinX(nullptr)
, m_spinY(nullptr)
, m_spinWidth(nullptr)
, m_spinHeight(nullptr)
, m_spinSize(nullptr)
, m_spinOpacity(nullptr)
{
setAcceptDrops(true);
auto *vbox2 = new QVBoxLayout(this);
// Keyframe ruler
if (range.second >= 0) {
m_inPoint = range.first;
m_outPoint = range.second;
m_offset = m_model->data(m_index, AssetParameterModel::InRole).toInt();
} else {
m_offset = 0;
m_inPoint = m_model->data(m_index, AssetParameterModel::InRole).toInt();
m_outPoint = m_model->data(m_index, AssetParameterModel::OutRole).toInt() + 1;
}
m_monitorSize = pCore->getCurrentFrameSize();
m_monitor = pCore->getMonitor(m_model->monitorId);
m_timePos = new TimecodeDisplay(m_monitor->timecode(), this);
m_ruler = new AnimKeyframeRuler(0, m_outPoint - m_inPoint, this);
connect(m_ruler, &AnimKeyframeRuler::addKeyframe, this, &AnimationWidget::slotAddKeyframe);
connect(m_ruler, &AnimKeyframeRuler::removeKeyframe, this, &AnimationWidget::slotDeleteKeyframe);
vbox2->addWidget(m_ruler);
vbox2->setContentsMargins(0, 0, 0, 0);
auto *tb = new QToolBar(this);
vbox2->addWidget(tb);
setLayout(vbox2);
connect(m_ruler, &AnimKeyframeRuler::requestSeek, this, &AnimationWidget::requestSeek);
connect(m_ruler, &AnimKeyframeRuler::moveKeyframe, this, &AnimationWidget::moveKeyframe);
connect(m_timePos, SIGNAL(timeCodeEditingFinished()), this, SLOT(slotPositionChanged()));
connect(m_monitor, &Monitor::seekPosition, this, &AnimationWidget::monitorSeek, Qt::UniqueConnection);
if (m_frameSize.isNull() || m_frameSize.width() == 0 || m_frameSize.height() == 0) {
m_frameSize = m_monitorSize;
}
// seek to previous
m_previous = tb->addAction(QIcon::fromTheme(QStringLiteral("media-skip-backward")), i18n("Previous keyframe"), this, SLOT(slotPrevious()));
// Add/remove keyframe
m_addKeyframe = new KDualAction(i18n("Add keyframe"), i18n("Remove keyframe"), this);
m_addKeyframe->setInactiveIcon(QIcon::fromTheme(QStringLiteral("list-add")));
m_addKeyframe->setActiveIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
connect(m_addKeyframe, SIGNAL(activeChangedByUser(bool)), this, SLOT(slotAddDeleteKeyframe(bool)));
tb->addAction(m_addKeyframe);
// seek to next
m_next = tb->addAction(QIcon::fromTheme(QStringLiteral("media-skip-forward")), i18n("Next keyframe"), this, SLOT(slotNext()));
// Preset combo
m_presetCombo = new QComboBox(this);
m_presetCombo->setToolTip(i18n("Presets"));
connect(m_presetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(applyPreset(int)));
tb->addWidget(m_presetCombo);
// Keyframe type widget
m_selectType = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Keyframe interpolation"), this);
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 *linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this);
linear->setData((int)mlt_keyframe_linear);
linear->setCheckable(true);
m_selectType->addAction(linear);
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, SIGNAL(triggered(QAction *)), this, SLOT(slotEditKeyframeType(QAction *)));
KSelectAction *defaultInterp = new KSelectAction(QIcon::fromTheme(QStringLiteral("keyframes")), i18n("Default interpolation"), this);
discrete = new QAction(QIcon::fromTheme(QStringLiteral("discrete")), i18n("Discrete"), this);
discrete->setData((int)mlt_keyframe_discrete);
discrete->setCheckable(true);
defaultInterp->addAction(discrete);
linear = new QAction(QIcon::fromTheme(QStringLiteral("linear")), i18n("Linear"), this);
linear->setData((int)mlt_keyframe_linear);
linear->setCheckable(true);
defaultInterp->addAction(linear);
curve = new QAction(QIcon::fromTheme(QStringLiteral("smooth")), i18n("Smooth"), this);
curve->setData((int)mlt_keyframe_smooth);
curve->setCheckable(true);
defaultInterp->addAction(curve);
switch (KdenliveSettings::defaultkeyframeinterp()) {
case mlt_keyframe_discrete:
defaultInterp->setCurrentAction(discrete);
break;
case mlt_keyframe_smooth:
defaultInterp->setCurrentAction(curve);
break;
default:
defaultInterp->setCurrentAction(linear);
break;
}
connect(defaultInterp, SIGNAL(triggered(QAction *)), this, SLOT(slotSetDefaultInterp(QAction *)));
m_selectType->setToolBarMode(KSelectAction::ComboBoxMode);
m_endAttach = new QAction(i18n("Attach keyframe to end"), this);
m_endAttach->setCheckable(true);
connect(m_endAttach, &QAction::toggled, this, &AnimationWidget::slotReverseKeyframeType);
// copy/paste keyframes from clipboard
QAction *copy = new QAction(i18n("Copy keyframes to clipboard"), this);
connect(copy, &QAction::triggered, this, &AnimationWidget::slotCopyKeyframes);
QAction *paste = new QAction(i18n("Import keyframes from clipboard"), this);
connect(paste, &QAction::triggered, this, &AnimationWidget::slotImportKeyframes);
QAction *removeNext = new QAction(i18n("Remove all keyframes after cursor"), this);
connect(removeNext, &QAction::triggered, this, &AnimationWidget::slotRemoveNext);
// save preset
QAction *savePreset = new QAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save preset"), this);
connect(savePreset, &QAction::triggered, this, &AnimationWidget::savePreset);
// delete preset
QAction *delPreset = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete preset"), this);
connect(delPreset, &QAction::triggered, this, &AnimationWidget::deletePreset);
auto *container = new QMenu;
tb->addAction(m_selectType);
container->addAction(m_endAttach);
container->addSeparator();
container->addAction(copy);
container->addAction(paste);
container->addAction(removeNext);
container->addSeparator();
container->addAction(savePreset);
container->addAction(delPreset);
container->addAction(defaultInterp);
auto *menuButton = new QToolButton;
menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
menuButton->setToolTip(i18n("Options"));
menuButton->setMenu(container);
menuButton->setPopupMode(QToolButton::InstantPopup);
tb->addWidget(menuButton);
// Spacer
QWidget *empty = new QWidget();
empty->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
tb->addWidget(empty);
// Timecode
tb->addWidget(m_timePos);
m_timePos->setFrame(false);
m_timePos->setRange(0, m_outPoint - m_inPoint - 1);
// Prepare property
mlt_profile profile = m_monitor->profile()->get_profile();
m_animProperties.set("_profile", profile, 0, nullptr, nullptr);
// Display keyframe parameter
addParameter(m_index);
finishSetup();
// Update displayed values
monitorSeek(m_monitor->position());
}
AnimationWidget::~AnimationWidget() {}
void AnimationWidget::finishSetup()
{
// Load effect presets
loadPresets();
}
// static
QString AnimationWidget::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
{
QString keyframes = QString::number(start);
if (linearOnly) {
keyframes.append(QLatin1Char('='));
} else {
switch (KdenliveSettings::defaultkeyframeinterp()) {
case mlt_keyframe_discrete:
keyframes.append(QStringLiteral("|="));
break;
case mlt_keyframe_smooth:
keyframes.append(QStringLiteral("~="));
break;
default:
keyframes.append(QLatin1Char('='));
break;
}
}
keyframes.append(defaultValue);
return keyframes;
}
void AnimationWidget::updateTimecodeFormat()
{
m_timePos->slotUpdateTimeCodeFormat();
}
void AnimationWidget::slotPrevious()
{
int previous = qMax(-m_offset, m_animController.previous_key(m_timePos->getValue() - m_offset - 1)) + m_offset;
if (previous == m_timePos->getValue() && previous > 0) {
// Make sure we can seek to effect start even if there is no keyframe
previous = 0;
}
m_ruler->setActiveKeyframe(previous);
slotPositionChanged(previous, true);
}
void AnimationWidget::slotNext()
{
int next = m_animController.next_key(m_timePos->getValue() - m_offset + 1) + m_offset;
if (!m_animController.is_key(next - m_offset)) {
// No keyframe after current pos, return end position
next = m_timePos->maximum();
} else {
m_ruler->setActiveKeyframe(next - m_offset);
}
slotPositionChanged(next, true);
}
void AnimationWidget::slotAddKeyframe(int pos)
{
slotAddDeleteKeyframe(true, pos);
}
void AnimationWidget::doAddKeyframe(int pos, QString paramName, bool directUpdate)
{
if (paramName.isEmpty()) {
paramName = m_inTimeline;
}
if (pos == -1) {
pos = m_timePos->getValue();
}
pos -= m_offset;
// Try to get previous key's type
mlt_keyframe_type type;
if (m_selectType->isVisible()) {
type = (mlt_keyframe_type)KdenliveSettings::defaultkeyframeinterp();
if (m_animController.key_count() > 1) {
int previous = m_animController.previous_key(pos);
if (m_animController.is_key(previous)) {
type = m_animController.keyframe_type(previous);
} else {
int next = m_animController.next_key(pos);
if (m_animController.is_key(next)) {
type = m_animController.keyframe_type(next);
}
}
}
} else {
type = mlt_keyframe_linear;
}
if (paramName == m_rectParameter) {
mlt_rect rect = m_animProperties.anim_get_rect(paramName.toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(paramName.toUtf8().constData(), rect, pos, m_outPoint, type);
} else {
double val = m_animProperties.anim_get_double(paramName.toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(paramName.toUtf8().constData(), val, pos, m_outPoint, type);
}
slotPositionChanged(-1, false);
if (directUpdate) {
m_ruler->setActiveKeyframe(pos);
rebuildKeyframes();
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
}
}
void AnimationWidget::slotDeleteKeyframe(int pos)
{
slotAddDeleteKeyframe(false, pos);
}
void AnimationWidget::slotAddDeleteKeyframe(bool add, int pos)
{
if (pos == -1) {
pos = m_timePos->getValue();
}
QStringList paramNames = m_doubleWidgets.keys();
if (!m_rectParameter.isEmpty()) {
paramNames << m_rectParameter;
}
if (!add) {
// Delete keyframe in all animations at current pos
for (int i = 0; i < paramNames.count(); i++) {
m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData());
if (m_animController.is_key(pos - m_offset)) {
m_animController.remove(pos - m_offset);
}
}
m_selectType->setEnabled(false);
m_addKeyframe->setActive(false);
slotPositionChanged(-1, false);
} else {
// Add keyframe in all animations
for (int i = 0; i < paramNames.count(); i++) {
m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData());
if (!m_animController.is_key(pos - m_offset)) {
doAddKeyframe(pos, paramNames.at(i), false);
}
}
m_ruler->setActiveKeyframe(pos);
}
// Rebuild
rebuildKeyframes();
// Send updates
for (int i = 0; i < m_parameters.count(); i++) {
m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData());
emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true);
}
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
void AnimationWidget::slotRemoveNext()
{
int pos = m_timePos->getValue();
// Delete keyframe in all animations at current pos
QStringList paramNames = m_doubleWidgets.keys();
if (!m_rectParameter.isEmpty()) {
paramNames << m_rectParameter;
}
int kfrPos;
for (int i = 0; i < paramNames.count(); i++) {
m_animController = m_animProperties.get_animation(paramNames.at(i).toUtf8().constData());
int j = 0;
while (j < m_animController.key_count()) {
kfrPos = m_animController.key_get_frame(j);
if (kfrPos > (pos - m_offset)) {
m_animController.remove(kfrPos);
} else {
j++;
}
}
}
m_selectType->setEnabled(false);
m_addKeyframe->setActive(false);
slotPositionChanged(-1, false);
// Send updates
for (int i = 0; i < m_parameters.count(); i++) {
m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData());
emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true);
}
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
// Rebuild
rebuildKeyframes();
}
void AnimationWidget::slotSyncPosition(int relTimelinePos)
{
// do only sync if this effect is keyframable
if (m_timePos->maximum() > 0) {
relTimelinePos = qBound(0, relTimelinePos, m_timePos->maximum());
slotPositionChanged(relTimelinePos, false);
}
}
void AnimationWidget::moveKeyframe(int oldPos, int newPos)
{
bool isKey;
mlt_keyframe_type type;
if (m_animController.get_item(oldPos - m_offset, isKey, type) != 0) {
qCDebug(KDENLIVE_LOG) << "////////ERROR NO KFR";
return;
}
if (!m_rectParameter.isEmpty()) {
m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData());
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), oldPos - m_offset, m_outPoint);
m_animController.remove(oldPos - m_offset);
m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, newPos - m_offset, m_outPoint, type);
}
QStringList paramNames = m_doubleWidgets.keys();
for (int i = 0; i < paramNames.count(); i++) {
const QString ¶m = paramNames.at(i);
m_animController = m_animProperties.get_animation(param.toUtf8().constData());
double val = m_animProperties.anim_get_double(param.toUtf8().constData(), oldPos - m_offset, m_outPoint);
m_animController.remove(oldPos - m_offset);
m_animProperties.anim_set(param.toUtf8().constData(), val, newPos - m_offset, m_outPoint, type);
}
m_ruler->setActiveKeyframe(newPos);
if (m_attachedToEnd == oldPos) {
m_attachedToEnd = newPos;
}
rebuildKeyframes();
slotPositionChanged(m_ruler->position(), false);
// Send updates
for (int i = 0; i < m_parameters.count(); i++) {
m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData());
emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true);
}
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
void AnimationWidget::rebuildKeyframes()
{
// Fetch keyframes
QVector keyframes;
QVector types;
int frame;
mlt_keyframe_type type;
int count = m_animController.key_count();
for (int i = 0; i < count; i++) {
if (m_animController.key_get(i, frame, type) == 0) {
frame += m_offset;
if (frame >= 0) {
keyframes << frame;
types << (count > 1 ? (int)type : mlt_keyframe_linear);
}
}
}
m_ruler->updateKeyframes(keyframes, types, m_attachedToEnd);
}
void AnimationWidget::updateToolbar()
{
int pos = m_timePos->getValue();
QMapIterator i(m_doubleWidgets);
while (i.hasNext()) {
i.next();
double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint);
i.value()->setValue(val * i.value()->factor);
}
if (m_animController.is_key(pos) && m_selectType->isVisible()) {
QList types = m_selectType->actions();
for (int j = 0; j < types.count(); j++) {
if (types.at(j)->data().toInt() == (int)m_animController.keyframe_type(pos)) {
m_selectType->setCurrentAction(types.at(j));
break;
}
}
m_selectType->setEnabled(m_animController.key_count() > 1);
m_addKeyframe->setActive(true);
m_addKeyframe->setEnabled(m_animController.key_count() > 1);
if (m_doubleWidgets.value(m_inTimeline) != nullptr) {
m_doubleWidgets.value(m_inTimeline)->enableEdit(true);
}
} else {
m_selectType->setEnabled(false);
m_addKeyframe->setActive(false);
m_addKeyframe->setEnabled(true);
if (m_doubleWidgets.value(m_inTimeline) != nullptr) {
m_doubleWidgets.value(m_inTimeline)->enableEdit(false);
}
}
}
void AnimationWidget::slotPositionChanged(int pos, bool seek)
{
if (pos == -1) {
pos = m_timePos->getValue();
} else {
m_timePos->setValue(pos);
}
m_ruler->setValue(pos);
if (m_spinX) {
updateRect(pos);
}
updateSlider(pos);
m_previous->setEnabled(pos > 0);
m_next->setEnabled(pos < (m_outPoint - 1));
// scene ratio lock
if ((m_spinWidth != nullptr) && m_spinWidth->isEnabled()) {
double ratio =
m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height() : (double)m_monitorSize.width() / m_monitorSize.height();
bool lockRatio = m_spinHeight->value() == (int)(m_spinWidth->value() / ratio + 0.5);
m_lockRatio->blockSignals(true);
m_lockRatio->setChecked(lockRatio);
m_lockRatio->blockSignals(false);
m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_lockRatio->isChecked() ? ratio : -1);
}
if (seek) {
m_monitor->requestSeek(pos + m_inPoint);
}
}
void AnimationWidget::requestSeek(int pos)
{
m_monitor->requestSeek(pos + m_inPoint);
}
void AnimationWidget::updateSlider(int pos)
{
m_endAttach->blockSignals(true);
QMapIterator i(m_doubleWidgets);
while (i.hasNext()) {
i.next();
m_animController = m_animProperties.get_animation(i.key().toUtf8().constData());
double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint);
if (!m_animController.is_key(pos)) {
// no keyframe
m_addKeyframe->setEnabled(true);
if (m_animController.key_count() <= 1) {
// Special case: only one keyframe, allow adjusting whatever the position is
i.value()->enableEdit(true);
if (!i.value()->hasEditFocus()) {
i.value()->setValue(val * i.value()->factor);
}
if (i.key() == m_inTimeline) {
if (m_active) {
m_monitor->setEffectKeyframe(true);
}
m_endAttach->setEnabled(true);
m_endAttach->setChecked(m_attachedToEnd > -2 && m_animController.key_get_frame(0) >= m_attachedToEnd);
}
} else {
i.value()->enableEdit(false);
i.value()->setValue(val * i.value()->factor);
if (i.key() == m_inTimeline) {
if (m_active) {
m_monitor->setEffectKeyframe(false);
}
m_endAttach->setEnabled(false);
}
}
if (i.key() == m_inTimeline) {
m_selectType->setEnabled(false);
m_addKeyframe->setActive(false);
}
} else {
// keyframe
i.value()->setValue(val * i.value()->factor);
if (i.key() == m_inTimeline) {
if (m_active) {
m_monitor->setEffectKeyframe(true);
}
m_addKeyframe->setActive(true);
m_addKeyframe->setEnabled(m_animController.key_count() > 1);
m_selectType->setEnabled(m_animController.key_count() > 1);
m_endAttach->setEnabled(true);
m_endAttach->setChecked(m_attachedToEnd > -2 && pos >= m_attachedToEnd);
if (m_selectType->isVisible()) {
mlt_keyframe_type currentType = m_animController.keyframe_type(pos);
QList types = m_selectType->actions();
for (int j = 0; j < types.count(); j++) {
if ((mlt_keyframe_type)types.at(j)->data().toInt() == currentType) {
m_selectType->setCurrentAction(types.at(j));
break;
}
}
}
}
i.value()->enableEdit(true);
}
}
m_endAttach->blockSignals(false);
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
void AnimationWidget::updateRect(int pos)
{
m_endAttach->blockSignals(true);
m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData());
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint);
m_spinX->blockSignals(true);
m_spinY->blockSignals(true);
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
m_spinSize->blockSignals(true);
m_spinX->setValue(rect.x);
m_spinY->setValue(rect.y);
m_spinWidth->setValue(rect.w);
m_spinHeight->setValue(rect.h);
double size;
if (rect.w / pCore->getCurrentDar() > rect.h) {
if (m_originalSize->isChecked()) {
size = rect.w * 100.0 / m_frameSize.width();
} else {
size = rect.w * 100.0 / m_monitorSize.width();
}
} else {
if (m_originalSize->isChecked()) {
size = rect.h * 100.0 / m_frameSize.height();
} else {
size = rect.h * 100.0 / m_monitorSize.height();
}
}
m_spinSize->setValue(size);
if (m_spinOpacity) {
m_spinOpacity->blockSignals(true);
m_spinOpacity->setValue(100.0 * rect.o);
m_spinOpacity->blockSignals(false);
}
bool enableEdit = false;
if (!m_animController.is_key(pos)) {
// no keyframe
m_addKeyframe->setEnabled(true);
if (m_animController.key_count() <= 1) {
// Special case: only one keyframe, allow adjusting whatever the position is
enableEdit = true;
if (m_active) {
m_monitor->setEffectKeyframe(true);
}
m_endAttach->setEnabled(true);
m_endAttach->setChecked(m_attachedToEnd > -2 && m_animController.key_get_frame(0) >= m_attachedToEnd);
} else {
enableEdit = false;
if (m_active) {
m_monitor->setEffectKeyframe(false);
}
m_endAttach->setEnabled(false);
}
m_selectType->setEnabled(false);
m_addKeyframe->setActive(false);
} else {
// keyframe
enableEdit = true;
if (m_active) {
m_monitor->setEffectKeyframe(true);
}
m_addKeyframe->setActive(true);
m_addKeyframe->setEnabled(m_animController.key_count() > 1);
m_selectType->setEnabled(m_animController.key_count() > 1);
m_endAttach->setEnabled(true);
m_endAttach->setChecked(m_attachedToEnd > -2 && pos >= m_attachedToEnd);
if (m_selectType->isVisible()) {
mlt_keyframe_type currentType = m_animController.keyframe_type(pos);
QList types = m_selectType->actions();
for (int i = 0; i < types.count(); i++) {
if ((mlt_keyframe_type)types.at(i)->data().toInt() == currentType) {
m_selectType->setCurrentAction(types.at(i));
break;
}
}
}
}
m_spinSize->setEnabled(enableEdit);
m_spinX->setEnabled(enableEdit);
m_spinY->setEnabled(enableEdit);
m_spinWidth->setEnabled(enableEdit);
m_spinHeight->setEnabled(enableEdit);
m_spinSize->setEnabled(enableEdit);
if (m_spinOpacity) {
m_spinOpacity->setEnabled(enableEdit);
}
m_spinX->blockSignals(false);
m_spinY->blockSignals(false);
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
m_spinSize->blockSignals(false);
m_endAttach->blockSignals(false);
setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h));
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
void AnimationWidget::slotEditKeyframeType(QAction *action)
{
int pos = m_timePos->getValue() - m_offset;
if (m_animController.is_key(pos)) {
for (int i = 0; i < m_parameters.count(); i++) {
m_animController = m_animProperties.get_animation(m_parameters.at(i).second.toUtf8().constData());
if (m_parameters.at(i).second == m_rectParameter) {
mlt_rect rect = m_animProperties.anim_get_rect(m_parameters.at(i).second.toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(m_parameters.at(i).second.toUtf8().constData(), rect, pos, m_outPoint, (mlt_keyframe_type)action->data().toInt());
} else {
double val = m_animProperties.anim_get_double(m_parameters.at(i).second.toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(m_parameters.at(i).second.toUtf8().constData(), val, pos, m_outPoint, (mlt_keyframe_type)action->data().toInt());
}
emit valueChanged(m_parameters.at(i).first, QString(m_animController.serialize_cut()), true);
}
rebuildKeyframes();
setupMonitor();
// Restore default controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
}
void AnimationWidget::slotSetDefaultInterp(QAction *action)
{
KdenliveSettings::setDefaultkeyframeinterp(action->data().toInt());
}
void AnimationWidget::addParameter(QModelIndex ix)
{
// Anim properties might at some point require some more infos like profile
ParamType type = (ParamType)m_model->data(ix, AssetParameterModel::TypeRole).toInt();
QString keyframes = m_model->data(ix, AssetParameterModel::ValueRole).toString();
QString paramTag = m_model->data(ix, AssetParameterModel::NameRole).toString();
m_animProperties.set(paramTag.toUtf8().constData(), keyframes.toUtf8().constData());
m_attachedToEnd = KeyframeView::checkNegatives(keyframes, m_outPoint);
if (type == ParamType::Animated || type == ParamType::RestrictedAnim) {
// one dimension parameter
// Required to initialize anim property
m_inTimeline = paramTag;
m_animProperties.anim_get_int(paramTag.toUtf8().constData(), 0, m_outPoint);
buildSliderWidget(paramTag, ix);
} else {
// one dimension parameter
// TODO: multiple rect parameters in effect ?
m_rectParameter = paramTag;
m_inTimeline = paramTag;
// Required to initialize anim property
m_animProperties.anim_get_rect(paramTag.toUtf8().constData(), 0, m_outPoint);
buildRectWidget(paramTag, ix);
}
if (type == ParamType::RestrictedAnim) {
// This param only support linear keyframes
m_selectType->setVisible(false);
m_selectType->setCurrentItem(0);
}
m_parameters << QPair(ix, paramTag);
// Load keyframes
rebuildKeyframes();
}
void AnimationWidget::buildSliderWidget(const QString ¶mTag, QModelIndex ix)
{
- QLocale locale;
QString paramName = i18n(m_model->data(ix, Qt::DisplayRole).toString().toUtf8().data());
QString comment = m_model->data(ix, AssetParameterModel::CommentRole).toString();
if (!comment.isEmpty()) {
comment = i18n(comment.toUtf8().data());
}
int index = m_params.count() - 1;
DoubleWidget *doubleparam = new DoubleWidget(
paramName, 0, m_model->data(ix, AssetParameterModel::MinRole).toDouble(), m_model->data(ix, AssetParameterModel::MaxRole).toDouble(),
m_model->data(ix, AssetParameterModel::FactorRole).toDouble() m_model->data(ix, AssetParameterModel::DefaultRole).toDouble(), comment, index,
m_model->data(ix, AssetParameterModel::SuffixRole).toString(), m_model->data(ix, AssetParameterModel::DecimalsRole).toInt(), m_model->data(ix, AssetParameterModel::OddRole).toBool(), this);
doubleparam->setObjectName(paramTag);
doubleparam->setProperty("index", ix);
connect(doubleparam, &DoubleWidget::valueChanged, this, &AnimationWidget::slotAdjustKeyframeValue);
layout()->addWidget(doubleparam);
// TODO: in timeline
/*if ((!e.hasAttribute(QStringLiteral("intimeline")) || e.attribute(QStringLiteral("intimeline")) == QLatin1String("1")) &&
!e.hasAttribute(QStringLiteral("notintimeline"))) {*/
{
m_inTimeline = paramTag;
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
}
m_doubleWidgets.insert(paramTag, doubleparam);
}
void AnimationWidget::buildRectWidget(const QString ¶mTag, QModelIndex ix)
{
QString paramName = i18n(paramTag.toUtf8().data());
QString comment = m_model->data(ix, AssetParameterModel::CommentRole).toString();
if (!comment.isEmpty()) {
comment = i18n(comment.toUtf8().data());
}
auto *horLayout = new QHBoxLayout;
m_spinX = new DragValue(i18nc("x axis position", "X"), 0, 0, -99000, 99000, -1, QString(), false, false, this);
connect(m_spinX, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue);
horLayout->addWidget(m_spinX);
m_spinX->setProperty("index", ix);
m_spinY = new DragValue(i18nc("y axis position", "Y"), 0, 0, -99000, 99000, -1, QString(), false, false, this);
connect(m_spinY, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue);
horLayout->addWidget(m_spinY);
m_spinWidth = new DragValue(i18nc("Frame width", "W"), m_monitorSize.width(), 0, 1, 99000, -1, QString(), false, false, this);
connect(m_spinWidth, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectWidth);
horLayout->addWidget(m_spinWidth);
// Lock ratio stuff
m_lockRatio = new QAction(QIcon::fromTheme(QStringLiteral("link")), i18n("Lock aspect ratio"), this);
m_lockRatio->setCheckable(true);
connect(m_lockRatio, &QAction::triggered, this, &AnimationWidget::slotLockRatio);
auto *ratioButton = new QToolButton;
ratioButton->setDefaultAction(m_lockRatio);
horLayout->addWidget(ratioButton);
m_spinHeight = new DragValue(i18nc("Frame height", "H"), m_monitorSize.height(), 0, 1, 99000, -1, QString(), false, false, this);
connect(m_spinHeight, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectHeight);
horLayout->addWidget(m_spinHeight);
horLayout->addStretch(10);
auto *horLayout2 = new QHBoxLayout;
m_spinSize = new DragValue(i18n("Size"), 100, 2, 1, 99000, -1, i18n("%"), false, false, this);
m_spinSize->setStep(10);
connect(m_spinSize, &DragValue::valueChanged, this, &AnimationWidget::slotResize);
horLayout2->addWidget(m_spinSize);
if (m_model->data(ix, AssetParameterModel::OpacityRole).toBool()) {
m_spinOpacity = new DragValue(i18n("Opacity"), 100, 0, 0, 100, -1, i18n("%"), true, false, this);
connect(m_spinOpacity, &DragValue::valueChanged, this, &AnimationWidget::slotAdjustRectKeyframeValue);
horLayout2->addWidget(m_spinOpacity);
}
// Build buttons
m_originalSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-original")), i18n("Adjust to original size"), this);
connect(m_originalSize, &QAction::triggered, this, &AnimationWidget::slotAdjustToSource);
m_originalSize->setCheckable(true);
QAction *adjustSize = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-best")), i18n("Adjust and center in frame"), this);
connect(adjustSize, &QAction::triggered, this, &AnimationWidget::slotAdjustToFrameSize);
QAction *fitToWidth = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-width")), i18n("Fit to width"), this);
connect(fitToWidth, &QAction::triggered, this, &AnimationWidget::slotFitToWidth);
QAction *fitToHeight = new QAction(QIcon::fromTheme(QStringLiteral("zoom-fit-height")), i18n("Fit to height"), this);
connect(fitToHeight, &QAction::triggered, this, &AnimationWidget::slotFitToHeight);
QAction *alignleft = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-left")), i18n("Align left"), this);
connect(alignleft, &QAction::triggered, this, &AnimationWidget::slotMoveLeft);
QAction *alignhcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-hor")), i18n("Center horizontally"), this);
connect(alignhcenter, &QAction::triggered, this, &AnimationWidget::slotCenterH);
QAction *alignright = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-right")), i18n("Align right"), this);
connect(alignright, &QAction::triggered, this, &AnimationWidget::slotMoveRight);
QAction *aligntop = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-top")), i18n("Align top"), this);
connect(aligntop, &QAction::triggered, this, &AnimationWidget::slotMoveTop);
QAction *alignvcenter = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-vert")), i18n("Center vertically"), this);
connect(alignvcenter, &QAction::triggered, this, &AnimationWidget::slotCenterV);
QAction *alignbottom = new QAction(QIcon::fromTheme(QStringLiteral("kdenlive-align-bottom")), i18n("Align bottom"), this);
connect(alignbottom, &QAction::triggered, this, &AnimationWidget::slotMoveBottom);
auto *alignLayout = new QHBoxLayout;
alignLayout->setSpacing(0);
auto *alignButton = new QToolButton;
alignButton->setDefaultAction(alignleft);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(alignhcenter);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(alignright);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(aligntop);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(alignvcenter);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(alignbottom);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(m_originalSize);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(adjustSize);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(fitToWidth);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignButton = new QToolButton;
alignButton->setDefaultAction(fitToHeight);
alignButton->setAutoRaise(true);
alignLayout->addWidget(alignButton);
alignLayout->addStretch(10);
static_cast(layout())->addLayout(horLayout);
static_cast(layout())->addLayout(alignLayout);
static_cast(layout())->addLayout(horLayout2);
m_animController = m_animProperties.get_animation(paramTag.toUtf8().constData());
}
void AnimationWidget::slotUpdateVisibleParameter(bool display)
{
if (!display) {
return;
}
DoubleWidget *slider = qobject_cast(QObject::sender());
if (slider) {
if (slider->objectName() == m_inTimeline) {
return;
}
m_inTimeline = slider->objectName();
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
rebuildKeyframes();
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
}
}
void AnimationWidget::slotAdjustKeyframeValue(double value)
{
DoubleWidget *slider = qobject_cast(QObject::sender());
if (!slider) {
return;
}
m_inTimeline = slider->objectName();
QModelIndex ix = slider->property("index").toModelIndex();
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
int pos = m_ruler->position() - m_offset;
mlt_keyframe_type type = m_selectType->isVisible() ? (m_selectType->isEnabled() ? (mlt_keyframe_type)m_selectType->currentAction()->data().toInt()
: (mlt_keyframe_type)KdenliveSettings::defaultkeyframeinterp())
: mlt_keyframe_linear;
if (m_animController.is_key(pos)) {
// This is a keyframe
type = m_animController.keyframe_type(pos);
m_animProperties.anim_set(m_inTimeline.toUtf8().constData(), value / slider->factor, pos, m_outPoint, type);
emit valueChanged(ix, QString(m_animController.serialize_cut()), true);
} else if (m_animController.key_count() <= 1) {
pos = m_animController.key_get_frame(0);
if (pos >= 0) {
if (m_animController.is_key(pos)) {
type = m_animController.keyframe_type(pos);
}
m_animProperties.anim_set(m_inTimeline.toUtf8().constData(), value / slider->factor, pos, m_outPoint, type);
emit valueChanged(ix, QString(m_animController.serialize_cut()), true);
}
}
}
void AnimationWidget::slotAdjustRectKeyframeValue()
{
m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData());
m_inTimeline = m_rectParameter;
int pos = m_ruler->position() - m_offset;
mlt_rect rect;
rect.x = m_spinX->value();
rect.y = m_spinY->value();
rect.w = m_spinWidth->value();
rect.h = m_spinHeight->value();
rect.o = m_spinOpacity ? m_spinOpacity->value() / 100.0 : DBL_MIN;
double size;
if (m_spinWidth->value() / pCore->getCurrentDar() > m_spinHeight->value()) {
if (m_originalSize->isChecked()) {
size = m_spinWidth->value() * 100.0 / m_frameSize.width();
} else {
size = m_spinWidth->value() * 100.0 / m_monitorSize.width();
}
} else {
if (m_originalSize->isChecked()) {
size = m_spinHeight->value() * 100.0 / m_frameSize.height();
} else {
size = m_spinHeight->value() * 100.0 / m_monitorSize.height();
}
}
m_spinSize->blockSignals(true);
m_spinSize->setValue(size);
m_spinSize->blockSignals(false);
if (m_animController.is_key(pos)) {
// This is a keyframe
m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint,
(mlt_keyframe_type)m_selectType->currentAction()->data().toInt());
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h));
} else if (m_animController.key_count() <= 1) {
pos = m_animController.key_get_frame(0);
if (pos >= 0) {
m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint,
(mlt_keyframe_type)m_selectType->currentAction()->data().toInt());
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
setupMonitor(QRect(rect.x, rect.y, rect.w, rect.h));
}
}
}
void AnimationWidget::slotResize(double value)
{
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
int w = m_originalSize->isChecked() ? m_frameSize.width() : m_monitorSize.width();
int h = m_originalSize->isChecked() ? m_frameSize.height() : m_monitorSize.height();
m_spinWidth->setValue(w * value / 100.0);
m_spinHeight->setValue(h * value / 100.0);
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
setupMonitor();
}
bool AnimationWidget::isActive(const QString &name) const
{
return name == m_inTimeline;
}
const QMap AnimationWidget::getAnimation()
{
QMap animationResults;
if (m_spinX) {
m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData());
// TODO: keyframes attached to end
animationResults.insert(m_rectParameter, m_animController.serialize_cut());
}
QMapIterator i(m_doubleWidgets);
while (i.hasNext()) {
i.next();
m_animController = m_animProperties.get_animation(i.key().toUtf8().constData());
// no negative keyframe trick, return simple serialize
if (m_attachedToEnd == -2) {
animationResults.insert(i.key(), m_animController.serialize_cut());
} else {
// Do processing ourselves to include negative values for keyframes relative to end
int pos;
mlt_keyframe_type type;
QString key;
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
QStringList result;
int duration = m_outPoint;
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, duration);
if (pos >= m_attachedToEnd) {
pos = qMin(pos - duration, -1);
}
key = QString::number(pos);
switch (type) {
case mlt_keyframe_discrete:
key.append(QStringLiteral("|="));
break;
case mlt_keyframe_smooth:
key.append(QStringLiteral("~="));
break;
default:
key.append(QStringLiteral("="));
break;
}
key.append(locale.toString(val));
result << key;
}
animationResults.insert(i.key(), result.join(QLatin1Char(';')));
}
}
// restore original controller
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
return animationResults;
}
void AnimationWidget::slotReverseKeyframeType(bool reverse)
{
int pos = m_timePos->getValue();
if (m_animController.is_key(pos)) {
if (reverse) {
m_attachedToEnd = pos;
} else {
m_attachedToEnd = -2;
}
rebuildKeyframes();
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
}
}
void AnimationWidget::loadPresets(QString currentText)
{
m_presetCombo->blockSignals(true);
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (currentText.isEmpty()) {
currentText = m_presetCombo->currentText();
}
while (m_presetCombo->count() > 0) {
m_presetCombo->removeItem(0);
}
m_presetCombo->removeItem(0);
QMap defaultEntry;
QStringList paramNames = m_doubleWidgets.keys();
for (int i = 0; i < paramNames.count(); i++) {
defaultEntry.insert(paramNames.at(i), getDefaultKeyframes(m_offset, m_model->data(m_index, AssetParameterModel::DefaultRole).toString()));
}
m_presetCombo->addItem(i18n("Default"), defaultEntry);
loadPreset(dir.absoluteFilePath(m_model->data(m_index, AssetParameterModel::TypeRole).toString()));
loadPreset(dir.absoluteFilePath(m_effectId));
if (!currentText.isEmpty()) {
int ix = m_presetCombo->findText(currentText);
if (ix >= 0) {
m_presetCombo->setCurrentIndex(ix);
}
}
m_presetCombo->blockSignals(false);
}
void AnimationWidget::loadPreset(const QString &path)
{
KConfig confFile(path, KConfig::SimpleConfig);
const QStringList groups = confFile.groupList();
for (const QString &grp : groups) {
QMap entries = KConfigGroup(&confFile, grp).entryMap();
QMap variantEntries;
QMapIterator i(entries);
while (i.hasNext()) {
i.next();
variantEntries.insert(i.key(), i.value());
}
m_presetCombo->addItem(grp, variantEntries);
}
}
void AnimationWidget::applyPreset(int ix)
{
QMap entries = m_presetCombo->itemData(ix).toMap();
QStringList presetNames = entries.keys();
QStringList paramNames = m_doubleWidgets.keys();
for (int i = 0; i < paramNames.count() && i < presetNames.count(); i++) {
QString keyframes = entries.value(paramNames.at(i)).toString();
if (!keyframes.isEmpty()) {
m_animProperties.set(paramNames.at(i).toUtf8().constData(), keyframes.toUtf8().constData());
// Required to initialize anim property
m_animProperties.anim_get_int(m_inTimeline.toUtf8().constData(), 0, m_outPoint);
}
}
if (!m_rectParameter.isEmpty()) {
QString keyframes = entries.value(m_rectParameter).toString();
if (!keyframes.isEmpty()) {
m_animProperties.set(m_rectParameter.toUtf8().constData(), keyframes.toUtf8().constData());
m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint);
}
}
m_animController = m_animProperties.get_animation(m_inTimeline.toUtf8().constData());
rebuildKeyframes();
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
}
void AnimationWidget::savePreset()
{
QDialog d(this);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Save);
auto *l = new QVBoxLayout;
d.setLayout(l);
QLineEdit effectName(&d);
effectName.setPlaceholderText(i18n("Enter preset name"));
QCheckBox cb(i18n("Save as global preset (available to all effects)"), &d);
l->addWidget(&effectName);
l->addWidget(&cb);
l->addWidget(buttonBox);
d.connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
d.connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
if (d.exec() != QDialog::Accepted) {
return;
}
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
if (!dir.exists()) {
dir.mkpath(QStringLiteral("."));
}
QString fileName = cb.isChecked() ? dir.absoluteFilePath(m_xml.attribute(QStringLiteral("type"))) : m_effectId;
KConfig confFile(dir.absoluteFilePath(fileName), KConfig::SimpleConfig);
KConfigGroup grp(&confFile, effectName.text());
// Parse keyframes
QMap currentKeyframes = getAnimation();
QMapIterator i(currentKeyframes);
while (i.hasNext()) {
i.next();
grp.writeEntry(i.key(), i.value());
}
confFile.sync();
loadPresets(effectName.text());
}
void AnimationWidget::deletePreset()
{
QString effectName = m_presetCombo->currentText();
// try deleting as effect preset first
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/presets/"));
KConfig confFile(dir.absoluteFilePath(m_effectId), KConfig::SimpleConfig);
KConfigGroup grp(&confFile, effectName);
if (grp.exists()) {
} else {
// try global preset
grp = KConfigGroup(&confFile, m_xml.attribute(QStringLiteral("type")));
}
grp.deleteGroup();
confFile.sync();
loadPresets();
}
void AnimationWidget::setActiveKeyframe(int frame)
{
m_ruler->setActiveKeyframe(frame);
}
void AnimationWidget::slotUpdateGeometryRect(const QRect r)
{
int pos = m_timePos->getValue();
if (!m_animController.is_key(pos)) {
// no keyframe
if (m_animController.key_count() <= 1) {
// Special case: only one keyframe, allow adjusting whatever the position is
pos = m_animController.key_get_frame(0);
if (pos < 0) {
// error
qCDebug(KDENLIVE_LOG) << "* * *NOT on a keyframe, something is wrong";
return;
}
}
}
m_spinX->blockSignals(true);
m_spinY->blockSignals(true);
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
m_spinX->setValue(r.x());
m_spinY->setValue(r.y());
m_spinWidth->setValue(r.width());
m_spinHeight->setValue(r.height());
m_spinX->blockSignals(false);
m_spinY->blockSignals(false);
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
setupMonitor();
}
void AnimationWidget::slotUpdateCenters(const QVariantList ¢ers)
{
if (centers.count() != m_animController.key_count()) {
qCDebug(KDENLIVE_LOG) << "* * * *CENTER POINTS MISMATCH, aborting edit";
}
int pos;
mlt_keyframe_type type;
for (int i = 0; i < m_animController.key_count(); ++i) {
m_animController.key_get(i, pos, type);
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint);
// Center rect to new pos
QPointF offset = centers.at(i).toPointF() - QPointF(rect.x + rect.w / 2, rect.y + rect.h / 2);
rect.x += offset.x();
rect.y += offset.y();
m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, type);
}
slotAdjustRectKeyframeValue();
}
void AnimationWidget::setupMonitor(QRect r)
{
QVariantList points;
QVariantList types;
int pos;
mlt_keyframe_type type;
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
if (m_animController.key_get_type(j) == mlt_keyframe_smooth) {
types << 1;
} else {
types << 0;
}
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint);
QRectF frameRect(rect.x, rect.y, rect.w, rect.h);
points.append(QVariant(frameRect.center()));
}
if (m_active) {
m_monitor->setUpEffectGeometry(r, points, types);
}
}
void AnimationWidget::slotSeekToKeyframe(int ix)
{
int pos = m_animController.key_get_frame(ix);
slotPositionChanged(pos, true);
}
void AnimationWidget::connectMonitor(bool activate)
{
if (m_active == activate) {
return;
}
m_active = activate;
if (!m_spinX) {
// No animated rect displayed in monitor, return
return;
}
if (activate) {
connect(m_monitor, &Monitor::effectChanged, this, &AnimationWidget::slotUpdateGeometryRect, Qt::UniqueConnection);
connect(m_monitor, &Monitor::effectPointsChanged, this, &AnimationWidget::slotUpdateCenters, Qt::UniqueConnection);
connect(m_monitor, &Monitor::seekToKeyframe, this, &AnimationWidget::slotSeekToKeyframe, Qt::UniqueConnection);
connect(m_monitor, &Monitor::seekToNextKeyframe, this, &AnimationWidget::slotNext, Qt::UniqueConnection);
connect(m_monitor, &Monitor::seekToPreviousKeyframe, this, &AnimationWidget::slotPrevious, Qt::UniqueConnection);
connect(m_monitor, SIGNAL(addKeyframe()), this, SLOT(slotAddKeyframe()), Qt::UniqueConnection);
connect(m_monitor, SIGNAL(deleteKeyframe()), this, SLOT(slotDeleteKeyframe()), Qt::UniqueConnection);
int framePos = qBound(0, m_monitor->position() - m_inPoint, m_timePos->maximum());
slotPositionChanged(framePos, false);
double ratio = (double)m_spinWidth->value() / m_spinHeight->value();
if (m_frameSize.width() != m_monitorSize.width() || m_frameSize.height() != m_monitorSize.height()) {
// Source frame size different than project frame size, enable original size option accordingly
bool isOriginalSize =
qAbs((double)m_frameSize.width() / m_frameSize.height() - ratio) < qAbs((double)m_monitorSize.width() / m_monitorSize.height() - ratio);
if (isOriginalSize) {
m_originalSize->blockSignals(true);
m_originalSize->setChecked(true);
m_originalSize->blockSignals(false);
}
}
} else {
disconnect(m_monitor, &Monitor::effectChanged, this, &AnimationWidget::slotUpdateGeometryRect);
disconnect(m_monitor, &Monitor::effectPointsChanged, this, &AnimationWidget::slotUpdateCenters);
disconnect(m_monitor, SIGNAL(addKeyframe()), this, SLOT(slotAddKeyframe()));
disconnect(m_monitor, SIGNAL(deleteKeyframe()), this, SLOT(slotDeleteKeyframe()));
disconnect(m_monitor, &Monitor::seekToNextKeyframe, this, &AnimationWidget::slotNext);
disconnect(m_monitor, &Monitor::seekToPreviousKeyframe, this, &AnimationWidget::slotPrevious);
disconnect(m_monitor, &Monitor::seekToKeyframe, this, &AnimationWidget::slotSeekToKeyframe);
}
}
void AnimationWidget::offsetAnimation(int offset)
{
int pos = 0;
mlt_keyframe_type type;
QString offsetAnimation = QStringLiteral("kdenliveOffset");
if (m_spinX) {
m_animController = m_animProperties.get_animation(m_rectParameter.toUtf8().constData());
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(offsetAnimation.toUtf8().constData(), rect, pos + offset, m_outPoint, type);
}
Mlt::Animation offsetAnim = m_animProperties.get_animation(offsetAnimation.toUtf8().constData());
m_animProperties.set(m_rectParameter.toUtf8().constData(), offsetAnim.serialize_cut());
// Required to initialize anim property
m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint);
m_animProperties.set(offsetAnimation.toUtf8().constData(), "");
}
QMapIterator i(m_doubleWidgets);
while (i.hasNext()) {
i.next();
m_animController = m_animProperties.get_animation(i.key().toUtf8().constData());
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
double val = m_animProperties.anim_get_double(i.key().toUtf8().constData(), pos, m_outPoint);
m_animProperties.anim_set(offsetAnimation.toUtf8().constData(), val, pos + offset, m_outPoint, type);
}
Mlt::Animation offsetAnim = m_animProperties.get_animation(offsetAnimation.toUtf8().constData());
m_animProperties.set(i.key().toUtf8().constData(), offsetAnim.serialize_cut());
// Required to initialize anim property
m_animProperties.anim_get_int(i.key().toUtf8().constData(), 0, m_outPoint);
m_animProperties.set(offsetAnimation.toUtf8().constData(), "");
}
m_offset -= offset;
}
void AnimationWidget::reload(const QString &tag, const QString &data)
{
m_animProperties.set(tag.toUtf8().constData(), data.toUtf8().constData());
if (tag == m_rectParameter) {
m_animProperties.anim_get_rect(tag.toUtf8().constData(), 0, m_outPoint);
} else {
m_animProperties.anim_get_int(tag.toUtf8().constData(), 0, m_outPoint);
m_attachedToEnd = KeyframeView::checkNegatives(data, m_outPoint);
m_inTimeline = tag;
}
// Also add keyframes positions in other parameters
QStringList paramNames = m_doubleWidgets.keys();
- QLocale locale;
m_animController = m_animProperties.get_animation(tag.toUtf8().constData());
for (int i = 0; i < paramNames.count(); i++) {
const QString ¤tParam = paramNames.at(i);
if (currentParam == tag) {
continue;
}
// simple anim parameter, get default value
double def = m_model->data(m_index, AssetParameterModel::DefaultRole).toDouble();
// Clear current keyframes
m_animProperties.set(currentParam.toUtf8().constData(), "");
// Add default keyframes
int pos;
mlt_keyframe_type type;
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
m_animProperties.anim_set(currentParam.toUtf8().constData(), def, pos, m_outPoint, type);
}
m_animProperties.anim_get_int(currentParam.toUtf8().constData(), 0, m_outPoint);
}
if (!m_rectParameter.isEmpty() && tag != m_rectParameter) {
// reset geometry keyframes
// simple anim parameter, get default value
QString def = m_model->data(m_index, AssetParameterModel::DefaultRole).toString();
// Clear current keyframes
m_animProperties.set(m_rectParameter.toUtf8().constData(), def.toUtf8().constData());
// Add default keyframes
int pos;
mlt_keyframe_type type;
m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint);
mlt_rect rect = m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint);
for (int j = 0; j < m_animController.key_count(); ++j) {
m_animController.key_get(j, pos, type);
m_animProperties.anim_set(m_rectParameter.toUtf8().constData(), rect, pos, m_outPoint, type);
}
m_animProperties.anim_get_rect(m_rectParameter.toUtf8().constData(), 0, m_outPoint);
}
rebuildKeyframes();
emit valueChanged(m_index, QString(m_animController.serialize_cut()), true);
}
QString AnimationWidget::defaultValue(const QString ¶mName)
{
QStringList paramNames = m_doubleWidgets.keys();
for (int i = 0; i < paramNames.count(); i++) {
if (m_params.at(i).attribute(QStringLiteral("name")) == paramName) {
QString def = m_params.at(i).attribute(QStringLiteral("default"));
if (def.contains(QLatin1Char('%'))) {
def = EffectsController::getStringRectEval(def);
}
return def;
}
}
return QString();
}
void AnimationWidget::slotAdjustToSource()
{
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() + 0.5), false);
m_spinHeight->setValue(m_frameSize.height(), false);
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
if (m_lockRatio->isChecked()) {
m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height()
: (double)m_monitorSize.width() / m_monitorSize.height());
}
}
void AnimationWidget::slotAdjustToFrameSize()
{
double monitorDar = pCore->getCurrentDar();
double sourceDar = m_frameSize.width() / m_frameSize.height();
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
if (sourceDar > monitorDar) {
// Fit to width
double factor = (double)m_monitorSize.width() / m_frameSize.width() * pCore->getCurrentSar();
m_spinHeight->setValue((int)(m_frameSize.height() * factor + 0.5));
m_spinWidth->setValue(m_monitorSize.width());
// Center
m_spinY->blockSignals(true);
m_spinY->setValue((m_monitorSize.height() - m_spinHeight->value()) / 2);
m_spinY->blockSignals(false);
} else {
// Fit to height
double factor = (double)m_monitorSize.height() / m_frameSize.height();
m_spinHeight->setValue(m_monitorSize.height());
m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() * factor + 0.5));
// Center
m_spinX->blockSignals(true);
m_spinX->setValue((m_monitorSize.width() - m_spinWidth->value()) / 2);
m_spinX->blockSignals(false);
}
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
}
void AnimationWidget::slotFitToWidth()
{
double factor = (double)m_monitorSize.width() / m_frameSize.width() * pCore->getCurrentSar();
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
m_spinHeight->setValue((int)(m_frameSize.height() * factor + 0.5));
m_spinWidth->setValue(m_monitorSize.width());
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
}
void AnimationWidget::slotFitToHeight()
{
double factor = (double)m_monitorSize.height() / m_frameSize.height();
m_spinWidth->blockSignals(true);
m_spinHeight->blockSignals(true);
m_spinHeight->setValue(m_monitorSize.height());
m_spinWidth->setValue((int)(m_frameSize.width() / pCore->getCurrentSar() * factor + 0.5));
m_spinWidth->blockSignals(false);
m_spinHeight->blockSignals(false);
slotAdjustRectKeyframeValue();
}
void AnimationWidget::slotMoveLeft()
{
m_spinX->setValue(0);
}
void AnimationWidget::slotCenterH()
{
m_spinX->setValue((m_monitorSize.width() - m_spinWidth->value()) / 2);
}
void AnimationWidget::slotMoveRight()
{
m_spinX->setValue(m_monitorSize.width() - m_spinWidth->value());
}
void AnimationWidget::slotMoveTop()
{
m_spinY->setValue(0);
}
void AnimationWidget::slotCenterV()
{
m_spinY->setValue((m_monitorSize.height() - m_spinHeight->value()) / 2);
}
void AnimationWidget::slotMoveBottom()
{
m_spinY->setValue(m_monitorSize.height() - m_spinHeight->value());
}
void AnimationWidget::slotCopyKeyframes()
{
const QMap anims = getAnimation();
if (anims.isEmpty()) {
return;
}
QString result;
QMapIterator i(anims);
while (i.hasNext()) {
i.next();
result.append(i.key() + QLatin1Char('=') + i.value() + QLatin1Char('\n'));
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(result);
}
void AnimationWidget::slotImportKeyframes()
{
QClipboard *clipboard = QApplication::clipboard();
QString values = clipboard->text();
emit setKeyframes(values);
}
void AnimationWidget::slotLockRatio()
{
QAction *lockRatio = qobject_cast(QObject::sender());
if (lockRatio->isChecked()) {
m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), m_originalSize->isChecked() ? (double)m_frameSize.width() / m_frameSize.height()
: (double)m_monitorSize.width() / m_monitorSize.height());
} else {
m_monitor->setEffectSceneProperty(QStringLiteral("lockratio"), -1);
}
}
void AnimationWidget::slotAdjustRectWidth()
{
if (m_lockRatio->isChecked()) {
m_spinHeight->blockSignals(true);
if (m_originalSize->isChecked()) {
m_spinHeight->setValue((int)(m_spinWidth->value() * m_frameSize.height() / m_frameSize.width() + 0.5));
} else {
m_spinHeight->setValue((int)(m_spinWidth->value() * m_monitorSize.height() / m_monitorSize.width() + 0.5));
}
m_spinHeight->blockSignals(false);
}
slotAdjustRectKeyframeValue();
}
void AnimationWidget::slotAdjustRectHeight()
{
if (m_lockRatio->isChecked()) {
m_spinWidth->blockSignals(true);
if (m_originalSize->isChecked()) {
m_spinWidth->setValue((int)(m_spinHeight->value() * m_frameSize.width() / m_frameSize.height() + 0.5));
} else {
m_spinWidth->setValue((int)(m_spinHeight->value() * m_monitorSize.width() / m_monitorSize.height() + 0.5));
}
m_spinWidth->blockSignals(false);
}
slotAdjustRectKeyframeValue();
}
void AnimationWidget::monitorSeek(int pos)
{
slotPositionChanged(pos - m_inPoint, false);
if (m_spinX) {
// Update monitor scene for geometry params
if (pos > m_inPoint && pos < m_outPoint) {
if (!m_active) {
connectMonitor(true);
m_monitor->slotShowEffectScene(MonitorSceneGeometry);
}
} else {
if (m_active) {
connectMonitor(false);
m_monitor->slotShowEffectScene(MonitorSceneDefault);
}
}
}
}
void AnimationWidget::slotShowComment(bool show) {}
void AnimationWidget::slotRefresh()
{
for (int i = 0; i < m_parameters.count(); i++) {
QModelIndex ix = m_parameters.at(i).first;
ParamType type = (ParamType)m_model->data(ix, AssetParameterModel::TypeRole).toInt();
QString keyframes = m_model->data(ix, AssetParameterModel::ValueRole).toString();
QString paramTag = m_model->data(ix, AssetParameterModel::NameRole).toString();
m_animProperties.set(paramTag.toUtf8().constData(), keyframes.toUtf8().constData());
m_attachedToEnd = KeyframeView::checkNegatives(keyframes, m_outPoint);
if (type == ParamType::Animated || type == ParamType::RestrictedAnim) {
// one dimension parameter
// Required to initialize anim property
m_animProperties.anim_get_int(paramTag.toUtf8().constData(), 0, m_outPoint);
} else {
// rect parameter
// TODO: multiple rect parameters in effect ?
m_rectParameter = paramTag;
m_inTimeline = paramTag;
// Required to initialize anim property
m_animProperties.anim_get_rect(paramTag.toUtf8().constData(), 0, m_outPoint);
}
}
rebuildKeyframes();
monitorSeek(m_monitor->position());
}
void AnimationWidget::slotSetRange(QPair range)
{
m_inPoint = range.first;
m_outPoint = range.second;
m_offset = m_model->data(m_index, AssetParameterModel::InRole).toInt();
m_ruler->setRange(0, m_outPoint - m_inPoint);
m_timePos->setRange(0, m_outPoint - m_inPoint - 1);
}
diff --git a/src/assets/view/widgets/keyframeimport.cpp b/src/assets/view/widgets/keyframeimport.cpp
index 61a9eefd9..f8fdff149 100644
--- a/src/assets/view/widgets/keyframeimport.cpp
+++ b/src/assets/view/widgets/keyframeimport.cpp
@@ -1,757 +1,758 @@
/***************************************************************************
* Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* 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 "klocalizedstring.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "assets/keyframes/view/keyframeview.hpp"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include "keyframeimport.h"
#include "profiles/profilemodel.hpp"
#include "widgets/positionwidget.h"
#include
#include "mlt++/MltAnimation.h"
#include "mlt++/MltProperties.h"
KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr model, QList indexes,
QWidget *parent)
: QDialog(parent)
, m_model(std::move(model))
, m_indexes(indexes)
, m_supportsAnim(false)
, m_previewLabel(nullptr)
, m_isReady(false)
{
auto *lay = new QVBoxLayout(this);
auto *l1 = new QHBoxLayout;
QLabel *lab = new QLabel(i18n("Data to import:"), this);
l1->addWidget(lab);
m_dataCombo = new QComboBox(this);
l1->addWidget(m_dataCombo);
l1->addStretch(10);
lay->addLayout(l1);
int in = -1;
int out = -1;
// Set up data
auto json = QJsonDocument::fromJson(animData.toUtf8());
if (!json.isArray()) {
qDebug() << "Error : Json file should be an array";
// Try to build data from a single value
QJsonArray list;
QJsonObject currentParam;
currentParam.insert(QLatin1String("name"), QStringLiteral("data"));
currentParam.insert(QLatin1String("value"), animData);
bool ok;
QString firstFrame = animData.section(QLatin1Char('='), 0, 0);
in = firstFrame.toInt(&ok);
if (!ok) {
firstFrame.chop(1);
in = firstFrame.toInt(&ok);
}
QString first = animData.section(QLatin1Char('='), 1, 1);
if (!first.isEmpty()) {
int spaces = first.count(QLatin1Char(' '));
switch (spaces) {
case 0:
currentParam.insert(QLatin1String("type"), QJsonValue((int)ParamType::Animated));
break;
default:
currentParam.insert(QLatin1String("type"), QJsonValue((int)ParamType::AnimatedRect));
break;
}
//currentParam.insert(QLatin1String("min"), QJsonValue(min));
//currentParam.insert(QLatin1String("max"), QJsonValue(max));
list.push_back(currentParam);
json = QJsonDocument(list);
}
}
if (!json.isArray()) {
qDebug() << "Error : Json file should be an array";
return;
}
auto list = json.array();
int ix = 0;
for (const auto &entry : list) {
if (!entry.isObject()) {
qDebug() << "Warning : Skipping invalid marker data";
continue;
}
auto entryObj = entry.toObject();
if (!entryObj.contains(QLatin1String("name"))) {
qDebug() << "Warning : Skipping invalid marker data (does not contain name)";
continue;
}
QString name = entryObj[QLatin1String("name")].toString();
QString value = entryObj[QLatin1String("value")].toString();
int type = entryObj[QLatin1String("type")].toInt(0);
double min = entryObj[QLatin1String("min")].toDouble(0);
double max = entryObj[QLatin1String("max")].toDouble(0);
if (in == -1) {
in = entryObj[QLatin1String("in")].toInt(0);
}
if (out == -1) {
out = entryObj[QLatin1String("out")].toInt(0);
}
m_dataCombo->insertItem(ix, name);
m_dataCombo->setItemData(ix, value, Qt::UserRole);
m_dataCombo->setItemData(ix, type, Qt::UserRole + 1);
m_dataCombo->setItemData(ix, min, Qt::UserRole + 2);
m_dataCombo->setItemData(ix, max, Qt::UserRole + 3);
ix++;
}
m_previewLabel = new QLabel(this);
m_previewLabel->setMinimumSize(100, 150);
m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_previewLabel->setScaledContents(true);
lay->addWidget(m_previewLabel);
// Zone in / out
in = qMax(0, in);
if (out <= 0) {
out = in + m_model->data(indexes.first(), AssetParameterModel::ParentDurationRole).toInt();
}
m_inPoint = new PositionWidget(i18n("In"), in, 0, out, pCore->currentDoc()->timecode(), QString(), this);
connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
lay->addWidget(m_inPoint);
m_outPoint = new PositionWidget(i18n("Out"), out, in, out, pCore->currentDoc()->timecode(), QString(), this);
connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
lay->addWidget(m_outPoint);
// Check what kind of parameters are in our target
for (const QPersistentModelIndex &idx : indexes) {
auto type = m_model->data(idx, AssetParameterModel::TypeRole).value();
if (type == ParamType::KeyframeParam) {
m_simpleTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
} else if (type == ParamType::AnimatedRect) {
m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
}
}
l1 = new QHBoxLayout;
m_targetCombo = new QComboBox(this);
m_sourceCombo = new QComboBox(this);
ix = 0;
/*if (!m_geometryTargets.isEmpty()) {
m_sourceCombo->insertItem(ix, i18n("Geometry"));
m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole);
ix++;
m_sourceCombo->insertItem(ix, i18n("Position"));
m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole);
ix++;
}
if (!m_simpleTargets.isEmpty()) {
m_sourceCombo->insertItem(ix, i18n("X"));
m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole);
ix++;
m_sourceCombo->insertItem(ix, i18n("Y"));
m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole);
ix++;
m_sourceCombo->insertItem(ix, i18n("Width"));
m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole);
ix++;
m_sourceCombo->insertItem(ix, i18n("Height"));
m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole);
ix++;
}*/
connect(m_sourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateRange()));
m_alignCombo = new QComboBox(this);
m_alignCombo->addItems(QStringList() << i18n("Align top left") << i18n("Align center") << i18n("Align bottom right"));
lab = new QLabel(i18n("Map "), this);
QLabel *lab2 = new QLabel(i18n(" to "), this);
l1->addWidget(lab);
l1->addWidget(m_sourceCombo);
l1->addWidget(lab2);
l1->addWidget(m_targetCombo);
l1->addWidget(m_alignCombo);
l1->addStretch(10);
ix = 0;
QMap::const_iterator j = m_geometryTargets.constBegin();
while (j != m_geometryTargets.constEnd()) {
m_targetCombo->insertItem(ix, j.key());
m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
++j;
ix++;
}
ix = 0;
j = m_simpleTargets.constBegin();
while (j != m_simpleTargets.constEnd()) {
m_targetCombo->insertItem(ix, j.key());
m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
++j;
ix++;
}
if (m_simpleTargets.count() + m_geometryTargets.count() > 1) {
// Target contains several animatable parameters, propose choice
}
lay->addLayout(l1);
// Output offset
m_offsetPoint = new PositionWidget(i18n("Offset"), 0, 0, out, pCore->currentDoc()->timecode(), "", this);
lay->addWidget(m_offsetPoint);
// Source range
m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this);
lay->addWidget(m_sourceRangeLabel);
// update range info
connect(m_targetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDestinationRange()));
// Destination range
l1 = new QHBoxLayout;
lab = new QLabel(i18n("Destination range"), this);
l1->addWidget(lab);
l1->addWidget(&m_destMin);
l1->addWidget(&m_destMax);
lay->addLayout(l1);
l1 = new QHBoxLayout;
m_limitRange = new QCheckBox(i18n("Actual range only"), this);
connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange);
connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
l1->addWidget(m_limitRange);
l1->addStretch(10);
lay->addLayout(l1);
l1 = new QHBoxLayout;
m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this);
m_limitKeyframes->setChecked(true);
m_limitNumber = new QSpinBox(this);
m_limitNumber->setMinimum(1);
m_limitNumber->setValue(20);
l1->addWidget(m_limitKeyframes);
l1->addWidget(m_limitNumber);
l1->addStretch(10);
lay->addLayout(l1);
connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled);
connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
connect(m_limitNumber, SIGNAL(valueChanged(int)), this, SLOT(updateDisplay()));
connect(m_dataCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDataDisplay()));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
lay->addWidget(buttonBox);
m_isReady = true;
updateDestinationRange();
updateDataDisplay();
}
KeyframeImport::~KeyframeImport() = default;
void KeyframeImport::resizeEvent(QResizeEvent *ev)
{
QWidget::resizeEvent(ev);
updateDisplay();
}
void KeyframeImport::updateDataDisplay()
{
QString comboData = m_dataCombo->currentData().toString();
auto type = m_dataCombo->currentData(Qt::UserRole + 1).value();
m_maximas = KeyframeModel::getRanges(comboData, m_model);
m_sourceCombo->clear();
if (type == ParamType::KeyframeParam) {
// 1 dimensional param.
m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue);
updateRange();
return;
}
double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry);
m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position);
m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly);
m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly);
if (wDist > 0) {
m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly);
}
if (hDist > 0) {
m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly);
}
updateRange();
/*if (!m_inPoint->isValid()) {
m_inPoint->blockSignals(true);
m_outPoint->blockSignals(true);
// m_inPoint->setRange(0, m_keyframeView->duration);
m_inPoint->setPosition(0);
m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt());
m_inPoint->blockSignals(false);
m_outPoint->blockSignals(false);
}*/
}
void KeyframeImport::updateRange()
{
int pos = m_sourceCombo->currentData().toInt();
m_alignCombo->setEnabled(pos == ImportRoles::Position);
QString rangeText;
if (m_limitRange->isChecked()) {
switch (pos) {
case ImportRoles::SimpleValue:
case ImportRoles::XOnly:
rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y());
break;
case ImportRoles::YOnly:
rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y());
break;
case ImportRoles::WidthOnly:
rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y());
break;
case ImportRoles::HeightOnly:
rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y());
break;
default:
rangeText = i18n("Source range: (%1-%2), (%3-%4)", m_maximas.at(0).x(), m_maximas.at(0).y(), m_maximas.at(1).x(), m_maximas.at(1).y());
break;
}
} else {
int profileWidth = pCore->getCurrentProfile()->width();
int profileHeight = pCore->getCurrentProfile()->height();
switch (pos) {
case ImportRoles::SimpleValue:
rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
break;
case ImportRoles::XOnly:
rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
break;
case ImportRoles::YOnly:
rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
break;
case ImportRoles::WidthOnly:
rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
break;
case ImportRoles::HeightOnly:
rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
break;
default:
rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()),
qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
break;
}
}
m_sourceRangeLabel->setText(rangeText);
updateDisplay();
}
void KeyframeImport::updateDestinationRange()
{
if (m_simpleTargets.contains(m_targetCombo->currentText())) {
// 1 dimension target
m_destMin.setEnabled(true);
m_destMax.setEnabled(true);
m_limitRange->setEnabled(true);
QString tag = m_targetCombo->currentData().toString();
double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble();
double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble();
m_destMin.setRange(min, max);
m_destMax.setRange(min, max);
m_destMin.setValue(min);
m_destMax.setValue(max);
} else {
int profileWidth = pCore->getCurrentProfile()->width();
m_destMin.setRange(-2 * profileWidth, 2 * profileWidth);
m_destMax.setRange(-2 * profileWidth, 2 * profileWidth);
m_destMin.setEnabled(false);
m_destMax.setEnabled(false);
m_limitRange->setEnabled(false);
}
}
void KeyframeImport::updateDisplay()
{
if (!m_isReady) {
// Not ready
return;
}
QPixmap pix(m_previewLabel->width(), m_previewLabel->height());
pix.fill(Qt::transparent);
QList maximas;
int selectedtarget = m_sourceCombo->currentData().toInt();
int profileWidth = pCore->getCurrentProfile()->width();
int profileHeight = pCore->getCurrentProfile()->height();
if (!m_maximas.isEmpty()) {
if (m_maximas.at(0).x() == m_maximas.at(0).y() || (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
maximas << QPoint();
} else {
if (m_limitRange->isChecked()) {
maximas << m_maximas.at(0);
} else {
QPoint p1;
if (selectedtarget == ImportRoles::SimpleValue) {
p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
} else {
p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
}
maximas << p1;
}
}
}
if (m_maximas.count() > 1) {
if (m_maximas.at(1).x() == m_maximas.at(1).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
maximas << QPoint();
} else {
if (m_limitRange->isChecked()) {
maximas << m_maximas.at(1);
} else {
QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
maximas << p2;
}
}
}
if (m_maximas.count() > 2) {
if (m_maximas.at(2).x() == m_maximas.at(2).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) {
maximas << QPoint();
} else {
if (m_limitRange->isChecked()) {
maximas << m_maximas.at(2);
} else {
QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
maximas << p3;
}
}
}
if (m_maximas.count() > 3) {
if (m_maximas.at(3).x() == m_maximas.at(3).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) {
maximas << QPoint();
} else {
if (m_limitRange->isChecked()) {
maximas << m_maximas.at(3);
} else {
QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
maximas << p4;
}
}
}
drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0,
palette().text().color());
m_previewLabel->setPixmap(pix);
}
QString KeyframeImport::selectedData() const
{
// return serialized keyframes
if (m_simpleTargets.contains(m_targetCombo->currentText())) {
// Exporting a 1 dimension animation
int ix = m_sourceCombo->currentData().toInt();
QPoint maximas;
if (m_limitRange->isChecked()) {
maximas = m_maximas.at(ix);
} else if (ix == ImportRoles::WidthOnly) {
// Width maximas
maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width()));
} else {
// Height maximas
maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height()));
}
std::shared_ptr animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
std::shared_ptr anim(new Mlt::Animation(animData->get_animation("key")));
animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition());
return anim->serialize_cut();
// m_keyframeView->getSingleAnimation(ix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_offsetPoint->getPosition(),
// m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0, maximas, m_destMin.value(), m_destMax.value());
}
//return QString();
std::shared_ptr animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
std::shared_ptr anim(new Mlt::Animation(animData->get_animation("key")));
animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition());
return anim->serialize_cut();
/*int pos = m_sourceCombo->currentData().toInt();
m_keyframeView->getOffsetAnimation(m_inPoint->getPosition(), m_outPoint->getPosition(), m_offsetPoint->getPosition(), m_limitKeyframes->isChecked() ?*/
// m_limitNumber->value() : 0, m_supportsAnim, pos == 11, rectOffset);
}
QString KeyframeImport::selectedTarget() const
{
return m_targetCombo->currentData().toString();
}
void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor)
{
qDebug()<<"============= DRAWING KFR CHANNS: "<currentData().toString();
std::shared_ptr animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
QRect br(0, 0, pix.width(), pix.height());
double frameFactor = (double)(out - in) / br.width();
int offset = 1;
if (limitKeyframes > 0) {
offset = (out - in) / limitKeyframes / frameFactor;
}
double min = m_dataCombo->currentData(Qt::UserRole + 2).toDouble();
double max = m_dataCombo->currentData(Qt::UserRole + 3).toDouble();
double xDist;
if (max > min) {
xDist = max - min;
} else {
xDist = m_maximas.at(0).y() - m_maximas.at(0).x();
}
double yDist = m_maximas.at(1).y() - m_maximas.at(1).x();
double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
double xOffset = m_maximas.at(0).x();
double yOffset = m_maximas.at(1).x();
double wOffset = m_maximas.at(2).x();
double hOffset = m_maximas.at(3).x();
QColor cX(255, 0, 0, 100);
QColor cY(0, 255, 0, 100);
QColor cW(0, 0, 255, 100);
QColor cH(255, 255, 0, 100);
// Draw curves labels
QPainter painter;
painter.begin(&pix);
QRectF txtRect = painter.boundingRect(br, QStringLiteral("t"));
txtRect.setX(2);
txtRect.setWidth(br.width() - 4);
txtRect.moveTop(br.height() - txtRect.height());
QRectF drawnText;
int maxHeight = br.height() - txtRect.height() - 2;
painter.setPen(textColor);
int rectSize = txtRect.height() / 2;
if (xDist > 0) {
painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cX);
txtRect.setX(txtRect.x() + rectSize * 2);
painter.drawText(txtRect, 0, i18nc("X as in x coordinate", "X") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(0).x()).arg(m_maximas.at(0).y()),
&drawnText);
}
if (yDist > 0) {
if (drawnText.isValid()) {
txtRect.setX(drawnText.right() + rectSize);
}
painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cY);
txtRect.setX(txtRect.x() + rectSize * 2);
painter.drawText(txtRect, 0, i18nc("Y as in y coordinate", "Y") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(1).x()).arg(m_maximas.at(1).y()),
&drawnText);
}
if (wDist > 0) {
if (drawnText.isValid()) {
txtRect.setX(drawnText.right() + rectSize);
}
painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cW);
txtRect.setX(txtRect.x() + rectSize * 2);
painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText);
}
if (hDist > 0) {
if (drawnText.isValid()) {
txtRect.setX(drawnText.right() + rectSize);
}
painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cH);
txtRect.setX(txtRect.x() + rectSize * 2);
painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText);
}
// Draw curves
for (int i = 0; i < br.width(); i++) {
mlt_rect rect = animData->anim_get_rect("key", (int)(i * frameFactor) + in);
if (xDist > 0) {
painter.setPen(cX);
int val = (rect.x - xOffset) * maxHeight / xDist;
qDebug() << "// DRAWINC CURVE : " << rect.x <<", POS: "<<((int)(i * frameFactor) + in)<< ", RESULT: " << val;
painter.drawLine(i, maxHeight - val, i, maxHeight);
}
if (yDist > 0) {
painter.setPen(cY);
int val = (rect.y - yOffset) * maxHeight / yDist;
painter.drawLine(i, maxHeight - val, i, maxHeight);
}
if (wDist > 0) {
painter.setPen(cW);
int val = (rect.w - wOffset) * maxHeight / wDist;
qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val;
painter.drawLine(i, maxHeight - val, i, maxHeight);
}
if (hDist > 0) {
painter.setPen(cH);
int val = (rect.h - hOffset) * maxHeight / hDist;
painter.drawLine(i, maxHeight - val, i, maxHeight);
}
}
if (offset > 1) {
// Overlay limited keyframes curve
cX.setAlpha(255);
cY.setAlpha(255);
cW.setAlpha(255);
cH.setAlpha(255);
mlt_rect rect1 = animData->anim_get_rect("key", in);
int prevPos = 0;
for (int i = offset; i < br.width(); i += offset) {
mlt_rect rect2 = animData->anim_get_rect("key", (int)(i * frameFactor) + in);
if (xDist > 0) {
painter.setPen(cX);
int val1 = (rect1.x - xOffset) * maxHeight / xDist;
int val2 = (rect2.x - xOffset) * maxHeight / xDist;
painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
}
if (yDist > 0) {
painter.setPen(cY);
int val1 = (rect1.y - yOffset) * maxHeight / yDist;
int val2 = (rect2.y - yOffset) * maxHeight / yDist;
painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
}
if (wDist > 0) {
painter.setPen(cW);
int val1 = (rect1.w - wOffset) * maxHeight / wDist;
int val2 = (rect2.w - wOffset) * maxHeight / wDist;
painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
}
if (hDist > 0) {
painter.setPen(cH);
int val1 = (rect1.h - hOffset) * maxHeight / hDist;
int val2 = (rect2.h - hOffset) * maxHeight / hDist;
painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
}
rect1 = rect2;
prevPos = i;
}
}
}
void KeyframeImport::importSelectedData()
{
// Simple double value
std::shared_ptr animData = KeyframeModel::getAnimation(m_model, selectedData());
std::shared_ptr anim(new Mlt::Animation(animData->get_animation("key")));
std::shared_ptr kfrModel = m_model->getKeyframeModel();
Fun undo = []() { return true; };
Fun redo = []() { return true; };
// Geometry target
QPoint rectOffset;
int finalAlign = m_alignCombo->currentIndex();
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (const auto &ix : m_indexes) {
// update keyframes in other indexes
KeyframeModel *km = kfrModel->getKeyModel(ix);
qDebug()<<"== "<currentData().toModelIndex();
if (ix == m_targetCombo->currentData().toModelIndex()) {
qDebug()<<"= = = \n\nPROCESSING KF IMPORT LOP: "<key_count()<<"\n\n===";
// Import our keyframes
int frame = 0;
KeyframeImport::ImportRoles convertMode = static_cast (m_sourceCombo->currentData().toInt());
for (int i = 0; i < anim->key_count(); i++) {
frame = anim->key_get_frame(i);
QVariant current = km->getInterpolatedValue(frame);
if (convertMode == ImportRoles::SimpleValue) {
double dval = animData->anim_get_double("key", frame);
km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), dval, true, undo, redo);
continue;
}
QStringList kfrData = current.toString().split(QLatin1Char(' '));
// Safety check
int size = kfrData.size();
switch (convertMode) {
case ImportRoles::FullGeometry:
case ImportRoles::HeightOnly:
case ImportRoles::WidthOnly:
if (size < 4) {
continue;
}
break;
case ImportRoles::Position:
case ImportRoles::YOnly:
if (size < 2) {
continue;
}
break;
default:
if (size == 0) {
continue;
}
break;
}
mlt_rect rect = animData->anim_get_rect("key", frame);
if (convertMode == ImportRoles::Position) {
switch (finalAlign) {
case 1:
// Align center
rect.x += rect.w / 2;
rect.y += rect.h / 2;
break;
case 2:
//Align bottom right
rect.x += rect.w;
rect.y += rect.h;
break;
default:
break;
}
}
switch (convertMode) {
case ImportRoles::FullGeometry:
- kfrData[0] = locale.toString(rect.x);
- kfrData[1] = locale.toString(rect.y);
- kfrData[2] = locale.toString(rect.w);
- kfrData[3] = locale.toString(rect.h);
+ kfrData[0] = locale.toString((int)rect.x);
+ kfrData[1] = locale.toString((int)rect.y);
+ kfrData[2] = locale.toString((int)rect.w);
+ kfrData[3] = locale.toString((int)rect.h);
break;
case ImportRoles::Position:
- kfrData[0] = locale.toString(rect.x);
- kfrData[1] = locale.toString(rect.y);
+ kfrData[0] = locale.toString((int)rect.x);
+ kfrData[1] = locale.toString((int)rect.y);
break;
case ImportRoles::SimpleValue:
case ImportRoles::XOnly:
- kfrData[0] = locale.toString(rect.x);
+ kfrData[0] = locale.toString((int)rect.x);
break;
case ImportRoles::YOnly:
- kfrData[1] = locale.toString(rect.y);
+ kfrData[1] = locale.toString((int)rect.y);
break;
case ImportRoles::WidthOnly:
- kfrData[2] = locale.toString(rect.w);
+ kfrData[2] = locale.toString((int)rect.w);
break;
case ImportRoles::HeightOnly:
- kfrData[3] = locale.toString(rect.h);
+ kfrData[3] = locale.toString((int)rect.h);
break;
default:
break;
}
current = kfrData.join(QLatin1Char(' '));
km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), current, true, undo, redo);
}
} else {
int frame = 0;
for (int i = 0; i < anim->key_count(); i++) {
frame = anim->key_get_frame(i);
//frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition());
QVariant current = km->getInterpolatedValue(frame);
km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), current, true, undo, redo);
}
}
}
pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard"));
}
int KeyframeImport::getImportType() const
{
if (m_simpleTargets.contains(m_targetCombo->currentText())) {
return -1;
}
return m_sourceCombo->currentData().toInt();
}
diff --git a/src/assets/view/widgets/listparamwidget.cpp b/src/assets/view/widgets/listparamwidget.cpp
index 9a624ec86..fa8144f82 100644
--- a/src/assets/view/widgets/listparamwidget.cpp
+++ b/src/assets/view/widgets/listparamwidget.cpp
@@ -1,149 +1,149 @@
/***************************************************************************
* Copyright (C) 2016 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 "listparamwidget.h"
#include "assets/model/assetparametermodel.hpp"
#include "core.h"
#include "mainwindow.h"
ListParamWidget::ListParamWidget(std::shared_ptr model, QModelIndex index, QWidget *parent)
: AbstractParamWidget(std::move(model), index, parent)
{
setupUi(this);
// Get data from model
QString name = m_model->data(m_index, AssetParameterModel::NameRole).toString();
QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString();
// setup the comment
setToolTip(comment);
m_labelComment->setText(comment);
m_widgetComment->setHidden(true);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
m_list->setIconSize(QSize(50, 30));
setMinimumHeight(m_list->sizeHint().height());
// setup the name
m_labelName->setText(m_model->data(m_index, Qt::DisplayRole).toString());
slotRefresh();
- QLocale locale;
// emit the signal of the base class when appropriate
// The connection is ugly because the signal "currentIndexChanged" is overloaded in QComboBox
connect(this->m_list, static_cast(&QComboBox::currentIndexChanged),
[this](int) {
emit valueChanged(m_index, m_list->itemData(m_list->currentIndex()).toString(), true);
});
}
void ListParamWidget::setCurrentIndex(int index)
{
m_list->setCurrentIndex(index);
}
void ListParamWidget::setCurrentText(const QString &text)
{
m_list->setCurrentText(text);
}
void ListParamWidget::addItem(const QString &text, const QVariant &value)
{
m_list->addItem(text, value);
}
void ListParamWidget::setItemIcon(int index, const QIcon &icon)
{
m_list->setItemIcon(index, icon);
}
void ListParamWidget::setIconSize(const QSize &size)
{
m_list->setIconSize(size);
}
void ListParamWidget::slotShowComment(bool show)
{
if (!m_labelComment->text().isEmpty()) {
m_widgetComment->setVisible(show);
}
}
QString ListParamWidget::getValue()
{
return m_list->currentData().toString();
}
void ListParamWidget::slotRefresh()
{
const QSignalBlocker bk(m_list);
m_list->clear();
QStringList names = m_model->data(m_index, AssetParameterModel::ListNamesRole).toStringList();
QStringList values = m_model->data(m_index, AssetParameterModel::ListValuesRole).toStringList();
QString value = m_model->data(m_index, AssetParameterModel::ValueRole).toString();
if (values.first() == QLatin1String("%lumaPaths")) {
// Special case: Luma files
// Create thumbnails
if (pCore->getCurrentFrameSize().width() > 1000) {
// HD project
values = MainWindow::m_lumaFiles.value(QStringLiteral("16_9"));
} else if (pCore->getCurrentFrameSize().height() > 1000) {
values = MainWindow::m_lumaFiles.value(QStringLiteral("9_16"));
} else if (pCore->getCurrentFrameSize().height() == pCore->getCurrentFrameSize().width()) {
values = MainWindow::m_lumaFiles.value(QStringLiteral("square"));
} else if (pCore->getCurrentFrameSize().height() == 480) {
values = MainWindow::m_lumaFiles.value(QStringLiteral("NTSC"));
} else {
values = MainWindow::m_lumaFiles.value(QStringLiteral("PAL"));
}
m_list->addItem(i18n("None (Dissolve)"));
for (int j = 0; j < values.count(); ++j) {
const QString &entry = values.at(j);
m_list->addItem(values.at(j).section(QLatin1Char('/'), -1), entry);
if (!entry.isEmpty() && (entry.endsWith(QLatin1String(".png")) || entry.endsWith(QLatin1String(".pgm")))) {
if (MainWindow::m_lumacache.contains(entry)) {
m_list->setItemIcon(j + 1, QPixmap::fromImage(MainWindow::m_lumacache.value(entry)));
}
}
}
if (!value.isEmpty() && values.contains(value)) {
m_list->setCurrentIndex(values.indexOf(value) + 1);
}
} else {
if (names.count() != values.count()) {
names = values;
}
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (int i = 0; i < names.count(); i++) {
QString val = values.at(i);
bool ok;
double num = val.toDouble(&ok);
if (ok) {
val = locale.toString(num);
}
m_list->addItem(names.at(i), val);
}
if (!value.isEmpty()) {
int ix = m_list->findData(value);
if (ix > -1) {
m_list->setCurrentIndex(ix);
}
}
}
}
diff --git a/src/doc/documentvalidator.cpp b/src/doc/documentvalidator.cpp
index 5f45b0142..3e4b3831b 100644
--- a/src/doc/documentvalidator.cpp
+++ b/src/doc/documentvalidator.cpp
@@ -1,2383 +1,2385 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "documentvalidator.h"
#include "bin/binplaylist.hpp"
#include "core.h"
#include "definitions.h"
#include "effects/effectsrepository.hpp"
#include "mainwindow.h"
#include "transitions/transitionsrepository.hpp"
#include "xml/xml.hpp"
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_MAC
#include
#endif
#include
#include
DocumentValidator::DocumentValidator(const QDomDocument &doc, QUrl documentUrl)
: m_doc(doc)
, m_url(std::move(documentUrl))
, m_modified(false)
{
}
bool DocumentValidator::validate(const double currentVersion)
{
QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
// At least the root element must be there
if (mlt.isNull()) {
return false;
}
QDomElement kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
QString rootDir = mlt.attribute(QStringLiteral("root"));
if (rootDir == QLatin1String("$CURRENTPATH")) {
// The document was extracted from a Kdenlive archived project, fix root directory
QString playlist = m_doc.toString();
playlist.replace(QLatin1String("$CURRENTPATH"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
m_doc.setContent(playlist);
mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
} else if (rootDir.isEmpty()) {
mlt.setAttribute(QStringLiteral("root"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
}
// Previous MLT / Kdenlive versions used C locale by default
QLocale documentLocale = QLocale::c();
if (mlt.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
// Check document numeric separator (added in Kdenlive 16.12.1
QDomElement main_playlist = mlt.firstChildElement(QStringLiteral("playlist"));
QString sep = Xml::getXmlProperty(main_playlist, "kdenlive:docproperties.decimalPoint", QString());
QChar numericalSeparator;
if (!sep.isEmpty()) {
numericalSeparator = sep.at(0);
}
bool error = false;
if (!numericalSeparator.isNull() && numericalSeparator != QLocale().decimalPoint()) {
qCDebug(KDENLIVE_LOG) << " * ** LOCALE CHANGE REQUIRED: " << numericalSeparator << "!=" << QLocale().decimalPoint() << " / "
<< QLocale::system().decimalPoint();
// Change locale to match document
QString requestedLocale = mlt.attribute(QStringLiteral("LC_NUMERIC"));
documentLocale = QLocale(requestedLocale);
#ifdef Q_OS_WIN
// Most locales don't work on windows, so use C whenever possible
if (numericalSeparator == QLatin1Char('.')) {
#else
if (numericalSeparator != documentLocale.decimalPoint() && numericalSeparator == QLatin1Char('.')) {
#endif
requestedLocale = QStringLiteral("C");
documentLocale = QLocale::c();
}
#ifdef Q_OS_MAC
setlocale(LC_NUMERIC_MASK, requestedLocale.toUtf8().constData());
#elif defined(Q_OS_WIN)
std::locale::global(std::locale(requestedLocale.toUtf8().constData()));
#else
setlocale(LC_NUMERIC, requestedLocale.toUtf8().constData());
#endif
if (numericalSeparator != documentLocale.decimalPoint()) {
// Parse installed locales to find one matching
const QList list = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale().script(), QLocale::AnyCountry);
QLocale matching;
for (const QLocale &loc : list) {
if (loc.decimalPoint() == numericalSeparator) {
matching = loc;
qCDebug(KDENLIVE_LOG) << "Warning, document locale: " << mlt.attribute(QStringLiteral("LC_NUMERIC"))
<< " is not available, using: " << loc.name();
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, loc.name().toUtf8().constData());
#else
setlocale(LC_NUMERIC_MASK, loc.name().toUtf8().constData());
#endif
documentLocale = matching;
break;
}
}
error = numericalSeparator != documentLocale.decimalPoint();
}
} else if (numericalSeparator.isNull()) {
// Change locale to match document
#ifndef Q_OS_MAC
const QString newloc = QString::fromLatin1(setlocale(LC_NUMERIC, mlt.attribute(QStringLiteral("LC_NUMERIC")).toUtf8().constData()));
#else
const QString newloc = setlocale(LC_NUMERIC_MASK, mlt.attribute("LC_NUMERIC").toUtf8().constData());
#endif
documentLocale = QLocale(mlt.attribute(QStringLiteral("LC_NUMERIC")));
error = newloc.isEmpty();
} else {
// Document separator matching system separator
documentLocale = QLocale();
}
if (error) {
// Requested locale not available, ask for install
KMessageBox::sorry(QApplication::activeWindow(),
i18n("The document was created in \"%1\" locale, which is not installed on your system. Please install that language pack. "
"Until then, Kdenlive might not be able to correctly open the document.",
mlt.attribute(QStringLiteral("LC_NUMERIC"))));
}
// Make sure Qt locale and C++ locale have the same numeric separator, might not be the case
// With some locales since C++ and Qt use a different database for locales
// localeconv()->decimal_point does not give reliable results on Windows
#ifndef Q_OS_WIN
char *separator = localeconv()->decimal_point;
if (QString::fromUtf8(separator) != QString(documentLocale.decimalPoint())) {
KMessageBox::sorry(QApplication::activeWindow(),
i18n("There is a locale conflict on your system. The document uses locale %1 which uses a \"%2\" as numeric separator (in "
"system libraries) but Qt expects \"%3\". You might not be able to correctly open the project.",
mlt.attribute(QStringLiteral("LC_NUMERIC")), documentLocale.decimalPoint(), separator));
// qDebug()<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
// HACK: There is a locale conflict, so set locale to at least have correct decimal point
if (strncmp(separator, ".", 1) == 0) {
documentLocale = QLocale::c();
} else if (strncmp(separator, ",", 1) == 0) {
documentLocale = QLocale(QStringLiteral("fr_FR.UTF-8"));
}
}
#endif
}
documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
// If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default
if (!mlt.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, "C");
#else
setlocale(LC_NUMERIC_MASK, "C");
#endif
}
QLocale::setDefault(documentLocale);
if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
KMessageBox::sorry(QApplication::activeWindow(),
i18n("There is a locale conflict. The document uses a \"%1\" as numeric separator, but your computer is configured to use "
"\"%2\". Change your computer settings or you might not be able to correctly open the project.",
documentLocale.decimalPoint(), QLocale().decimalPoint()));
}
// locale conversion might need to be redone
// TODO reload repositories
/*#ifndef Q_OS_MAC
initEffects::parseEffectFiles(pCore->getMltRepository(), QString::fromLatin1(setlocale(LC_NUMERIC, nullptr)));
#else
initEffects::parseEffectFiles(pCore->getMltRepository(), QString::fromLatin1(setlocale(LC_NUMERIC_MASK, nullptr)));
#endif
*/
}
double version = -1;
if (kdenliveDoc.isNull() || !kdenliveDoc.hasAttribute(QStringLiteral("version"))) {
// Newer Kdenlive document version
QDomElement main = mlt.firstChildElement(QStringLiteral("playlist"));
version = Xml::getXmlProperty(main, QStringLiteral("kdenlive:docproperties.version")).toDouble();
} else {
bool ok;
version = documentLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok);
if (!ok) {
// Could not parse version number, there is probably a conflict in decimal separator
QLocale tempLocale = QLocale(mlt.attribute(QStringLiteral("LC_NUMERIC")));
version = tempLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok);
if (!ok) {
version = kdenliveDoc.attribute(QStringLiteral("version")).toDouble(&ok);
}
if (!ok) {
// Last try: replace comma with a dot
QString versionString = kdenliveDoc.attribute(QStringLiteral("version"));
if (versionString.contains(QLatin1Char(','))) {
versionString.replace(QLatin1Char(','), QLatin1Char('.'));
}
version = versionString.toDouble(&ok);
if (!ok) {
qCDebug(KDENLIVE_LOG) << "// CANNOT PARSE VERSION NUMBER, ERROR!";
}
}
}
}
// Upgrade the document to the latest version
if (!upgrade(version, currentVersion)) {
return false;
}
if (version < 0.97) {
checkOrphanedProducers();
}
return true;
/*
// Check the syntax (this will be replaced by XSD validation with Qt 4.6)
// and correct some errors
{
// Return (or create) the tractor
QDomElement tractor = mlt.firstChildElement("tractor");
if (tractor.isNull()) {
m_modified = true;
tractor = m_doc.createElement("tractor");
tractor.setAttribute("global_feed", "1");
tractor.setAttribute("in", "0");
tractor.setAttribute("out", "-1");
tractor.setAttribute("id", "maintractor");
mlt.appendChild(tractor);
}
// Make sure at least one track exists, and they're equal in number to
// to the maximum between MLT and Kdenlive playlists and tracks
//
// In older Kdenlive project files, one playlist is not a real track (the black track), we have: track count = playlist count- 1
// In newer Qt5 Kdenlive, the Bin playlist should not appear as a track. So we should have: track count = playlist count- 2
int trackOffset = 1;
QDomNodeList playlists = m_doc.elementsByTagName("playlist");
// Remove "main bin" playlist that simply holds the bin's clips and is not a real playlist
for (int i = 0; i < playlists.count(); ++i) {
QString playlistId = playlists.at(i).toElement().attribute("id");
if (playlistId == BinController::binPlaylistId()) {
// remove pseudo-playlist
//playlists.at(i).parentNode().removeChild(playlists.at(i));
trackOffset = 2;
break;
}
}
int tracksMax = playlists.count() - trackOffset; // Remove the black track and bin track
QDomNodeList tracks = tractor.elementsByTagName("track");
tracksMax = qMax(tracks.count() - 1, tracksMax);
QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
tracksMax = qMax(tracksinfo.count(), tracksMax);
tracksMax = qMax(1, tracksMax); // Force existence of one track
if (playlists.count() - trackOffset < tracksMax ||
tracks.count() < tracksMax ||
tracksinfo.count() < tracksMax) {
qCDebug(KDENLIVE_LOG) << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
m_modified = true;
int difference;
// use the MLT tracks as reference
if (tracks.count() - 1 < tracksMax) {
// Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
if (tracksinfo.count() != tracks.count() - 1) {
// The Kdenlive tracks are not ok, clear and rebuild them
QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
QDomNode tnode = tinfo.firstChild();
while (!tnode.isNull()) {
tinfo.removeChild(tnode);
tnode = tinfo.firstChild();
}
for (int i = 1; i < tracks.count(); ++i) {
QString hide = tracks.at(i).toElement().attribute("hide");
QDomElement newTrack = m_doc.createElement("trackinfo");
if (hide == "video") {
// audio track;
newTrack.setAttribute("type", "audio");
newTrack.setAttribute("blind", 1);
newTrack.setAttribute("mute", 0);
newTrack.setAttribute("lock", 0);
} else {
newTrack.setAttribute("blind", 0);
newTrack.setAttribute("mute", 0);
newTrack.setAttribute("lock", 0);
}
tinfo.appendChild(newTrack);
}
}
}
if (playlists.count() - 1 < tracksMax) {
difference = tracksMax - (playlists.count() - 1);
for (int i = 0; i < difference; ++i) {
QDomElement playlist = m_doc.createElement("playlist");
mlt.appendChild(playlist);
}
}
if (tracks.count() - 1 < tracksMax) {
difference = tracksMax - (tracks.count() - 1);
for (int i = 0; i < difference; ++i) {
QDomElement track = m_doc.createElement("track");
tractor.appendChild(track);
}
}
if (tracksinfo.count() < tracksMax) {
QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
if (tracksinfoElm.isNull()) {
tracksinfoElm = m_doc.createElement("tracksinfo");
kdenliveDoc.appendChild(tracksinfoElm);
}
difference = tracksMax - tracksinfo.count();
for (int i = 0; i < difference; ++i) {
QDomElement trackinfo = m_doc.createElement("trackinfo");
trackinfo.setAttribute("mute", "0");
trackinfo.setAttribute("locked", "0");
tracksinfoElm.appendChild(trackinfo);
}
}
}
// TODO: check the tracks references
// TODO: check internal mix transitions
}
updateEffects();
return true;
*/
}
bool DocumentValidator::upgrade(double version, const double currentVersion)
{
qCDebug(KDENLIVE_LOG) << "Opening a document with version " << version << " / " << currentVersion;
// No conversion needed
if (qFuzzyCompare(version, currentVersion)) {
return true;
}
// The document is too new
if (version > currentVersion) {
// qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version;
KMessageBox::sorry(
QApplication::activeWindow(),
i18n("This project type is unsupported (version %1) and cannot be loaded.\nPlease consider upgrading your Kdenlive version.", version),
i18n("Unable to open project"));
return false;
}
// Unsupported document versions
if (qFuzzyCompare(version, 0.5) || qFuzzyCompare(version, 0.7)) {
// 0.7 is unsupported
// qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version;
KMessageBox::sorry(QApplication::activeWindow(), i18n("This project type is unsupported (version %1) and cannot be loaded.", version),
i18n("Unable to open project"));
return false;
}
//
QDomNode infoXmlNode;
QDomElement infoXml;
QDomNodeList docs = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc"));
if (!docs.isEmpty()) {
infoXmlNode = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")).at(0);
infoXml = infoXmlNode.toElement();
infoXml.setAttribute(QStringLiteral("upgraded"), 1);
}
m_doc.documentElement().setAttribute(QStringLiteral("upgraded"), 1);
if (version <= 0.6) {
QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
QDomNode westley = m_doc.elementsByTagName(QStringLiteral("westley")).at(1);
QDomNode tractor = m_doc.elementsByTagName(QStringLiteral("tractor")).at(0);
QDomNode multitrack = m_doc.elementsByTagName(QStringLiteral("multitrack")).at(0);
QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
QDomNode props = m_doc.elementsByTagName(QStringLiteral("properties")).at(0).toElement();
QString profile = props.toElement().attribute(QStringLiteral("videoprofile"));
int startPos = props.toElement().attribute(QStringLiteral("timeline_position")).toInt();
if (profile == QLatin1String("dv_wide")) {
profile = QStringLiteral("dv_pal_wide");
}
// move playlists outside of tractor and add the tracks instead
int max = playlists.count();
if (westley.isNull()) {
westley = m_doc.createElement(QStringLiteral("westley"));
m_doc.documentElement().appendChild(westley);
}
if (tractor.isNull()) {
// qCDebug(KDENLIVE_LOG) << "// NO MLT PLAYLIST, building empty one";
QDomElement blank_tractor = m_doc.createElement(QStringLiteral("tractor"));
westley.appendChild(blank_tractor);
QDomElement blank_playlist = m_doc.createElement(QStringLiteral("playlist"));
blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track"));
westley.insertBefore(blank_playlist, QDomNode());
QDomElement blank_track = m_doc.createElement(QStringLiteral("track"));
blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track"));
blank_tractor.appendChild(blank_track);
QDomNodeList kdenlivetracks = m_doc.elementsByTagName(QStringLiteral("kdenlivetrack"));
for (int i = 0; i < kdenlivetracks.count(); ++i) {
blank_playlist = m_doc.createElement(QStringLiteral("playlist"));
blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("playlist") + QString::number(i));
westley.insertBefore(blank_playlist, QDomNode());
blank_track = m_doc.createElement(QStringLiteral("track"));
blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("playlist") + QString::number(i));
blank_tractor.appendChild(blank_track);
if (kdenlivetracks.at(i).toElement().attribute(QStringLiteral("cliptype")) == QLatin1String("Sound")) {
blank_playlist.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
blank_track.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
}
}
} else
for (int i = 0; i < max; ++i) {
QDomNode n = playlists.at(i);
westley.insertBefore(n, QDomNode());
QDomElement pl = n.toElement();
QDomElement track = m_doc.createElement(QStringLiteral("track"));
QString trackType = pl.attribute(QStringLiteral("hide"));
if (!trackType.isEmpty()) {
track.setAttribute(QStringLiteral("hide"), trackType);
}
QString playlist_id = pl.attribute(QStringLiteral("id"));
if (playlist_id.isEmpty()) {
playlist_id = QStringLiteral("black_track");
pl.setAttribute(QStringLiteral("id"), playlist_id);
}
track.setAttribute(QStringLiteral("producer"), playlist_id);
// tractor.appendChild(track);
#define KEEP_TRACK_ORDER 1
#ifdef KEEP_TRACK_ORDER
tractor.insertAfter(track, QDomNode());
#else
// Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
// insertion sort - O( tracks*tracks )
// Note, this breaks _all_ transitions - but you can move them up and down afterwards.
QDomElement tractor_elem = tractor.toElement();
if (!tractor_elem.isNull()) {
QDomNodeList tracks = tractor_elem.elementsByTagName("track");
int size = tracks.size();
if (size == 0) {
tractor.insertAfter(track, QDomNode());
} else {
bool inserted = false;
for (int i = 0; i < size; ++i) {
QDomElement track_elem = tracks.at(i).toElement();
if (track_elem.isNull()) {
tractor.insertAfter(track, QDomNode());
inserted = true;
break;
} else {
// qCDebug(KDENLIVE_LOG) << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
if (playlist_id < track_elem.attribute("producer")) {
tractor.insertBefore(track, track_elem);
inserted = true;
break;
}
}
}
// Reach here, no insertion, insert last
if (!inserted) {
tractor.insertAfter(track, QDomNode());
}
}
} else {
qCWarning(KDENLIVE_LOG) << "tractor was not a QDomElement";
tractor.insertAfter(track, QDomNode());
}
#endif
}
tractor.removeChild(multitrack);
// audio track mixing transitions should not be added to track view, so add required attribute
QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
max = transitions.count();
for (int i = 0; i < max; ++i) {
QDomElement tr = transitions.at(i).toElement();
if (tr.attribute(QStringLiteral("combine")) == QLatin1String("1") && tr.attribute(QStringLiteral("mlt_service")) == QLatin1String("mix")) {
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added"));
QDomText value = m_doc.createTextNode(QStringLiteral("237"));
property.appendChild(value);
tr.appendChild(property);
property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
value = m_doc.createTextNode(QStringLiteral("mix"));
property.appendChild(value);
tr.appendChild(property);
} else {
// convert transition
QDomNamedNodeMap attrs = tr.attributes();
for (int j = 0; j < attrs.count(); ++j) {
QString attrName = attrs.item(j).nodeName();
if (attrName != QLatin1String("in") && attrName != QLatin1String("out") && attrName != QLatin1String("id")) {
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), attrName);
QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
property.appendChild(value);
tr.appendChild(property);
}
}
}
}
// move transitions after tracks
for (int i = 0; i < max; ++i) {
tractor.insertAfter(transitions.at(0), QDomNode());
}
// Fix filters format
QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
max = entries.count();
for (int i = 0; i < max; ++i) {
QString last_id;
int effectix = 0;
QDomNode m = entries.at(i).firstChild();
while (!m.isNull()) {
if (m.toElement().tagName() == QLatin1String("filter")) {
QDomElement filt = m.toElement();
QDomNamedNodeMap attrs = filt.attributes();
QString current_id = filt.attribute(QStringLiteral("kdenlive_id"));
if (current_id != last_id) {
effectix++;
last_id = current_id;
}
QDomElement e = m_doc.createElement(QStringLiteral("property"));
e.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive_ix"));
QDomText value = m_doc.createTextNode(QString::number(effectix));
e.appendChild(value);
filt.appendChild(e);
for (int j = 0; j < attrs.count(); ++j) {
QDomAttr a = attrs.item(j).toAttr();
if (!a.isNull()) {
// qCDebug(KDENLIVE_LOG) << " FILTER; adding :" << a.name() << ':' << a.value();
auto property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), a.name());
auto property_value = m_doc.createTextNode(a.value());
property.appendChild(property_value);
filt.appendChild(property);
}
}
}
m = m.nextSibling();
}
}
/*
QDomNodeList filters = m_doc.elementsByTagName("filter");
max = filters.count();
QString last_id;
int effectix = 0;
for (int i = 0; i < max; ++i) {
QDomElement filt = filters.at(i).toElement();
QDomNamedNodeMap attrs = filt.attributes();
QString current_id = filt.attribute("kdenlive_id");
if (current_id != last_id) {
effectix++;
last_id = current_id;
}
QDomElement e = m_doc.createElement("property");
e.setAttribute("name", "kdenlive_ix");
QDomText value = m_doc.createTextNode(QString::number(1));
e.appendChild(value);
filt.appendChild(e);
for (int j = 0; j < attrs.count(); ++j) {
QDomAttr a = attrs.item(j).toAttr();
if (!a.isNull()) {
//qCDebug(KDENLIVE_LOG) << " FILTER; adding :" << a.name() << ':' << a.value();
QDomElement e = m_doc.createElement("property");
e.setAttribute("name", a.name());
QDomText value = m_doc.createTextNode(a.value());
e.appendChild(value);
filt.appendChild(e);
}
}
}*/
// fix slowmotion
QDomNodeList producers = westley.toElement().elementsByTagName(QStringLiteral("producer"));
max = producers.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(i).toElement();
if (prod.attribute(QStringLiteral("mlt_service")) == QLatin1String("framebuffer")) {
QString slowmotionprod = prod.attribute(QStringLiteral("resource"));
slowmotionprod.replace(QLatin1Char(':'), QLatin1Char('?'));
// qCDebug(KDENLIVE_LOG) << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
prod.setAttribute(QStringLiteral("resource"), slowmotionprod);
}
}
// move producers to correct place, markers to a global list, fix clip descriptions
QDomElement markers = m_doc.createElement(QStringLiteral("markers"));
// This will get the xml producers:
producers = m_doc.elementsByTagName(QStringLiteral("producer"));
max = producers.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(0).toElement();
// add resource also as a property (to allow path correction in setNewResource())
// TODO: will it work with slowmotion? needs testing
/*if (!prod.attribute("resource").isEmpty()) {
QDomElement prop_resource = m_doc.createElement("property");
prop_resource.setAttribute("name", "resource");
QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
prop_resource.appendChild(resource);
prod.appendChild(prop_resource);
}*/
QDomNode m = prod.firstChild();
if (!m.isNull()) {
if (m.toElement().tagName() == QLatin1String("markers")) {
QDomNodeList prodchilds = m.childNodes();
int maxchild = prodchilds.count();
for (int k = 0; k < maxchild; ++k) {
QDomElement mark = prodchilds.at(0).toElement();
mark.setAttribute(QStringLiteral("id"), prod.attribute(QStringLiteral("id")));
markers.insertAfter(mark, QDomNode());
}
prod.removeChild(m);
} else if (prod.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) {
// convert title clip
if (m.toElement().tagName() == QLatin1String("textclip")) {
QDomDocument tdoc;
QDomElement titleclip = m.toElement();
QDomElement title = tdoc.createElement(QStringLiteral("kdenlivetitle"));
tdoc.appendChild(title);
QDomNodeList objects = titleclip.childNodes();
int maxchild = objects.count();
for (int k = 0; k < maxchild; ++k) {
QDomElement ob = objects.at(k).toElement();
if (ob.attribute(QStringLiteral("type")) == QLatin1String("3")) {
// text object - all of this goes into "xmldata"...
QDomElement item = tdoc.createElement(QStringLiteral("item"));
item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z")));
item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem"));
QDomElement position = tdoc.createElement(QStringLiteral("position"));
position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x")));
position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y")));
QDomElement content = tdoc.createElement(QStringLiteral("content"));
content.setAttribute(QStringLiteral("font"), ob.attribute(QStringLiteral("font_family")));
content.setAttribute(QStringLiteral("font-size"), ob.attribute(QStringLiteral("font_size")));
content.setAttribute(QStringLiteral("font-bold"), ob.attribute(QStringLiteral("bold")));
content.setAttribute(QStringLiteral("font-italic"), ob.attribute(QStringLiteral("italic")));
content.setAttribute(QStringLiteral("font-underline"), ob.attribute(QStringLiteral("underline")));
QString col = ob.attribute(QStringLiteral("color"));
QColor c(col);
content.setAttribute(QStringLiteral("font-color"), colorToString(c));
// todo: These fields are missing from the newly generated xmldata:
// transform, startviewport, endviewport, background
QDomText conttxt = tdoc.createTextNode(ob.attribute(QStringLiteral("text")));
content.appendChild(conttxt);
item.appendChild(position);
item.appendChild(content);
title.appendChild(item);
} else if (ob.attribute(QStringLiteral("type")) == QLatin1String("5")) {
// rectangle object
QDomElement item = tdoc.createElement(QStringLiteral("item"));
item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z")));
item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem"));
QDomElement position = tdoc.createElement(QStringLiteral("position"));
position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x")));
position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y")));
QDomElement content = tdoc.createElement(QStringLiteral("content"));
QString col = ob.attribute(QStringLiteral("color"));
QColor c(col);
content.setAttribute(QStringLiteral("brushcolor"), colorToString(c));
QString rect = QStringLiteral("0,0,");
rect.append(ob.attribute(QStringLiteral("width")));
rect.append(QLatin1String(","));
rect.append(ob.attribute(QStringLiteral("height")));
content.setAttribute(QStringLiteral("rect"), rect);
item.appendChild(position);
item.appendChild(content);
title.appendChild(item);
}
}
prod.setAttribute(QStringLiteral("xmldata"), tdoc.toString());
// mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
// QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
// prod.setAttribute("titlename", titleInfo.at(0));
// prod.setAttribute("resource", titleInfo.at(1));
////qCDebug(KDENLIVE_LOG)<<"TITLE DATA:\n"< 0) {
prod.setAttribute(QStringLiteral("out"), QString::number(duration));
}
// The clip goes back in, but text clips should not go back in, at least not modified
westley.insertBefore(prod, QDomNode());
}
QDomNode westley0 = m_doc.elementsByTagName(QStringLiteral("westley")).at(0);
if (!markers.firstChild().isNull()) {
westley0.appendChild(markers);
}
/*
* Convert as much of the kdenlivedoc as possible. Use the producer in
* westley. First, remove the old stuff from westley, and add a new
* empty one. Also, track the max id in order to use it for the adding
* of groups/folders
*/
int max_kproducer_id = 0;
westley0.removeChild(infoXmlNode);
QDomElement infoXml_new = m_doc.createElement(QStringLiteral("kdenlivedoc"));
infoXml_new.setAttribute(QStringLiteral("profile"), profile);
infoXml.setAttribute(QStringLiteral("position"), startPos);
// Add all the producers that has a resource in westley
QDomElement westley_element = westley0.toElement();
if (westley_element.isNull()) {
qCWarning(KDENLIVE_LOG) << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
} else {
QDomNodeList wproducers = westley_element.elementsByTagName(QStringLiteral("producer"));
int kmax = wproducers.count();
for (int i = 0; i < kmax; ++i) {
QDomElement wproducer = wproducers.at(i).toElement();
if (wproducer.isNull()) {
qCWarning(KDENLIVE_LOG) << "Found producer in westley0, that was not a QDomElement";
continue;
}
if (wproducer.attribute(QStringLiteral("id")) == QLatin1String("black")) {
continue;
}
// We have to do slightly different things, depending on the type
// qCDebug(KDENLIVE_LOG) << "Converting producer element with type" << wproducer.attribute("type");
if (wproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) {
// qCDebug(KDENLIVE_LOG) << "Found TEXT element in producer" << endl;
QDomElement kproducer = wproducer.cloneNode(true).toElement();
kproducer.setTagName(QStringLiteral("kdenlive_producer"));
infoXml_new.appendChild(kproducer);
/*
* TODO: Perhaps needs some more changes here to
* "frequency", aspect ratio as a float, frame_size,
* channels, and later, resource and title name
*/
} else {
QDomElement kproducer = m_doc.createElement(QStringLiteral("kdenlive_producer"));
kproducer.setAttribute(QStringLiteral("id"), wproducer.attribute(QStringLiteral("id")));
if (!wproducer.attribute(QStringLiteral("description")).isEmpty()) {
kproducer.setAttribute(QStringLiteral("description"), wproducer.attribute(QStringLiteral("description")));
}
kproducer.setAttribute(QStringLiteral("resource"), wproducer.attribute(QStringLiteral("resource")));
kproducer.setAttribute(QStringLiteral("type"), wproducer.attribute(QStringLiteral("type")));
// Testing fix for 358
if (!wproducer.attribute(QStringLiteral("aspect_ratio")).isEmpty()) {
kproducer.setAttribute(QStringLiteral("aspect_ratio"), wproducer.attribute(QStringLiteral("aspect_ratio")));
}
if (!wproducer.attribute(QStringLiteral("source_fps")).isEmpty()) {
kproducer.setAttribute(QStringLiteral("fps"), wproducer.attribute(QStringLiteral("source_fps")));
}
if (!wproducer.attribute(QStringLiteral("length")).isEmpty()) {
kproducer.setAttribute(QStringLiteral("duration"), wproducer.attribute(QStringLiteral("length")));
}
infoXml_new.appendChild(kproducer);
}
if (wproducer.attribute(QStringLiteral("id")).toInt() > max_kproducer_id) {
max_kproducer_id = wproducer.attribute(QStringLiteral("id")).toInt();
}
}
}
#define LOOKUP_FOLDER 1
#ifdef LOOKUP_FOLDER
/*
* Look through all the folder elements of the old doc, for each folder,
* for each producer, get the id, look it up in the new doc, set the
* groupname and groupid. Note, this does not work at the moment - at
* least one folder shows up missing, and clips with no folder does not
* show up.
*/
// QDomElement infoXml_old = infoXmlNode.toElement();
if (!infoXml_old.isNull()) {
QDomNodeList folders = infoXml_old.elementsByTagName(QStringLiteral("folder"));
int fsize = folders.size();
int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
for (int i = 0; i < fsize; ++i) {
QDomElement folder = folders.at(i).toElement();
if (!folder.isNull()) {
QString groupName = folder.attribute(QStringLiteral("name"));
// qCDebug(KDENLIVE_LOG) << "groupName: " << groupName << " with groupId: " << groupId;
QDomNodeList fproducers = folder.elementsByTagName(QStringLiteral("producer"));
int psize = fproducers.size();
for (int j = 0; j < psize; ++j) {
QDomElement fproducer = fproducers.at(j).toElement();
if (!fproducer.isNull()) {
QString id = fproducer.attribute(QStringLiteral("id"));
// This is not very effective, but compared to loading the clips, its a breeze
QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName(QStringLiteral("kdenlive_producer"));
int kpsize = kdenlive_producers.size();
for (int k = 0; k < kpsize; ++k) {
QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
if (id == kproducer.attribute(QStringLiteral("id"))) {
// We do not check that it already is part of a folder
kproducer.setAttribute(QStringLiteral("groupid"), groupId);
kproducer.setAttribute(QStringLiteral("groupname"), groupName);
break;
}
}
}
}
++groupId;
}
}
}
#endif
QDomNodeList elements = westley.childNodes();
max = elements.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = elements.at(0).toElement();
westley0.insertAfter(prod, QDomNode());
}
westley0.appendChild(infoXml_new);
westley0.removeChild(westley);
// adds information to
QDomNodeList kproducers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
QDomNodeList avfiles = infoXml_old.elementsByTagName(QStringLiteral("avfile"));
// qCDebug(KDENLIVE_LOG) << "found" << avfiles.count() << "s and" << kproducers.count() << "s";
for (int i = 0; i < avfiles.count(); ++i) {
QDomElement avfile = avfiles.at(i).toElement();
QDomElement kproducer;
if (avfile.isNull()) {
qCWarning(KDENLIVE_LOG) << "found an that is not a QDomElement";
} else {
QString id = avfile.attribute(QStringLiteral("id"));
// this is horrible, must be rewritten, it's just for test
for (int j = 0; j < kproducers.count(); ++j) {
////qCDebug(KDENLIVE_LOG) << "checking with id" << kproducers.at(j).toElement().attribute("id");
if (kproducers.at(j).toElement().attribute(QStringLiteral("id")) == id) {
kproducer = kproducers.at(j).toElement();
break;
}
}
if (kproducer == QDomElement()) {
qCWarning(KDENLIVE_LOG) << "no match for with id =" << id;
} else {
////qCDebug(KDENLIVE_LOG) << "ready to set additional 's attributes (id =" << id << ')';
kproducer.setAttribute(QStringLiteral("channels"), avfile.attribute(QStringLiteral("channels")));
kproducer.setAttribute(QStringLiteral("duration"), avfile.attribute(QStringLiteral("duration")));
kproducer.setAttribute(QStringLiteral("frame_size"),
avfile.attribute(QStringLiteral("width")) + QLatin1Char('x') + avfile.attribute(QStringLiteral("height")));
kproducer.setAttribute(QStringLiteral("frequency"), avfile.attribute(QStringLiteral("frequency")));
if (kproducer.attribute(QStringLiteral("description")).isEmpty() && !avfile.attribute(QStringLiteral("description")).isEmpty()) {
kproducer.setAttribute(QStringLiteral("description"), avfile.attribute(QStringLiteral("description")));
}
}
}
}
infoXml = infoXml_new;
}
if (version <= 0.81) {
// Add the tracks information
QString tracksOrder = infoXml.attribute(QStringLiteral("tracks"));
if (tracksOrder.isEmpty()) {
QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track"));
for (int i = 0; i < tracks.count(); ++i) {
QDomElement track = tracks.at(i).toElement();
if (track.attribute(QStringLiteral("producer")) != QLatin1String("black_track")) {
if (track.attribute(QStringLiteral("hide")) == QLatin1String("video")) {
tracksOrder.append(QLatin1Char('a'));
} else {
tracksOrder.append(QLatin1Char('v'));
}
}
}
}
QDomElement tracksinfo = m_doc.createElement(QStringLiteral("tracksinfo"));
for (int i = 0; i < tracksOrder.size(); ++i) {
QDomElement trackinfo = m_doc.createElement(QStringLiteral("trackinfo"));
if (tracksOrder.data()[i] == QLatin1Char('a')) {
trackinfo.setAttribute(QStringLiteral("type"), QStringLiteral("audio"));
trackinfo.setAttribute(QStringLiteral("blind"), 1);
} else {
trackinfo.setAttribute(QStringLiteral("blind"), 0);
}
trackinfo.setAttribute(QStringLiteral("mute"), 0);
trackinfo.setAttribute(QStringLiteral("locked"), 0);
tracksinfo.appendChild(trackinfo);
}
infoXml.appendChild(tracksinfo);
}
if (version <= 0.82) {
// Convert s in s (MLT extreme makeover)
QDomNodeList westleyNodes = m_doc.elementsByTagName(QStringLiteral("westley"));
for (int i = 0; i < westleyNodes.count(); ++i) {
QDomElement westley = westleyNodes.at(i).toElement();
westley.setTagName(QStringLiteral("mlt"));
}
}
if (version <= 0.83) {
// Replace point size with pixel size in text titles
if (m_doc.toString().contains(QStringLiteral("font-size"))) {
KMessageBox::ButtonCode convert = KMessageBox::Continue;
QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
QDomElement kproducer = kproducerNodes.at(i).toElement();
if (kproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) {
QDomDocument data;
data.setContent(kproducer.attribute(QStringLiteral("xmldata")));
QDomNodeList items = data.firstChild().childNodes();
for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
if (items.at(j).attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) {
QDomNamedNodeMap textProperties = items.at(j).namedItem(QStringLiteral("content")).attributes();
if (textProperties.namedItem(QStringLiteral("font-pixel-size")).isNull() &&
!textProperties.namedItem(QStringLiteral("font-size")).isNull()) {
// Ask the user if he wants to convert
if (convert != KMessageBox::Yes && convert != KMessageBox::No) {
convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(
QApplication::activeWindow(),
i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do "
"you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they "
"were first created on, or you could have to adjust their size."),
i18n("Update Text Clips"));
}
if (convert == KMessageBox::Yes) {
QFont font;
font.setPointSize(textProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt());
QDomElement content = items.at(j).namedItem(QStringLiteral("content")).toElement();
content.setAttribute(QStringLiteral("font-pixel-size"), QFontInfo(font).pixelSize());
content.removeAttribute(QStringLiteral("font-size"));
kproducer.setAttribute(QStringLiteral("xmldata"), data.toString());
/*
* You may be tempted to delete the preview file
* to force its recreation: bad idea (see
* http://www.kdenlive.org/mantis/view.php?id=749)
*/
}
}
}
}
}
}
}
// Fill the element
QDomElement docProperties = infoXml.firstChildElement(QStringLiteral("documentproperties"));
if (docProperties.isNull()) {
docProperties = m_doc.createElement(QStringLiteral("documentproperties"));
docProperties.setAttribute(QStringLiteral("zonein"), infoXml.attribute(QStringLiteral("zonein")));
docProperties.setAttribute(QStringLiteral("zoneout"), infoXml.attribute(QStringLiteral("zoneout")));
docProperties.setAttribute(QStringLiteral("zoom"), infoXml.attribute(QStringLiteral("zoom")));
docProperties.setAttribute(QStringLiteral("position"), infoXml.attribute(QStringLiteral("position")));
infoXml.appendChild(docProperties);
}
}
if (version <= 0.84) {
// update the title clips to use the new MLT kdenlivetitle producer
QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
for (int i = 0; i < kproducerNodes.count(); ++i) {
QDomElement kproducer = kproducerNodes.at(i).toElement();
if (kproducer.attribute(QStringLiteral("type")).toInt() == (int)ClipType::Text) {
QString data = kproducer.attribute(QStringLiteral("xmldata"));
QString datafile = kproducer.attribute(QStringLiteral("resource"));
if (!datafile.endsWith(QLatin1String(".kdenlivetitle"))) {
datafile = QString();
kproducer.setAttribute(QStringLiteral("resource"), QString());
}
QString id = kproducer.attribute(QStringLiteral("id"));
QDomNodeList mltproducers = m_doc.elementsByTagName(QStringLiteral("producer"));
bool foundData = false;
bool foundResource = false;
bool foundService = false;
for (int j = 0; j < mltproducers.count(); ++j) {
QDomElement wproducer = mltproducers.at(j).toElement();
if (wproducer.attribute(QStringLiteral("id")) == id) {
QDomNodeList props = wproducer.childNodes();
for (int k = 0; k < props.count(); ++k) {
if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("xmldata")) {
props.at(k).firstChild().setNodeValue(data);
foundData = true;
} else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("mlt_service")) {
props.at(k).firstChild().setNodeValue(QStringLiteral("kdenlivetitle"));
foundService = true;
} else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("resource")) {
props.at(k).firstChild().setNodeValue(datafile);
foundResource = true;
}
}
if (!foundData) {
QDomElement e = m_doc.createElement(QStringLiteral("property"));
e.setAttribute(QStringLiteral("name"), QStringLiteral("xmldata"));
QDomText value = m_doc.createTextNode(data);
e.appendChild(value);
wproducer.appendChild(e);
}
if (!foundService) {
QDomElement e = m_doc.createElement(QStringLiteral("property"));
e.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
QDomText value = m_doc.createTextNode(QStringLiteral("kdenlivetitle"));
e.appendChild(value);
wproducer.appendChild(e);
}
if (!foundResource) {
QDomElement e = m_doc.createElement(QStringLiteral("property"));
e.setAttribute(QStringLiteral("name"), QStringLiteral("resource"));
QDomText value = m_doc.createTextNode(datafile);
e.appendChild(value);
wproducer.appendChild(e);
}
break;
}
}
}
}
}
if (version <= 0.85) {
// update the LADSPA effects to use the new ladspa.id format instead of external xml file
QDomNodeList effectNodes = m_doc.elementsByTagName(QStringLiteral("filter"));
for (int i = 0; i < effectNodes.count(); ++i) {
QDomElement effect = effectNodes.at(i).toElement();
if (Xml::getXmlProperty(effect, QStringLiteral("mlt_service")) == QLatin1String("ladspa")) {
// Needs to be converted
QStringList info = getInfoFromEffectName(Xml::getXmlProperty(effect, QStringLiteral("kdenlive_id")));
if (info.isEmpty()) {
continue;
}
// info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
Xml::setXmlProperty(effect, QStringLiteral("kdenlive_id"), info.at(0));
Xml::setXmlProperty(effect, QStringLiteral("tag"), info.at(0));
Xml::setXmlProperty(effect, QStringLiteral("mlt_service"), info.at(0));
Xml::removeXmlProperty(effect, QStringLiteral("src"));
for (int j = 1; j < info.size(); ++j) {
QString value = Xml::getXmlProperty(effect, info.at(j).section(QLatin1Char('='), 0, 0));
if (!value.isEmpty()) {
// update parameter name
Xml::renameXmlProperty(effect, info.at(j).section(QLatin1Char('='), 0, 0), info.at(j).section(QLatin1Char('='), 1, 1));
}
}
}
}
}
if (version <= 0.86) {
// Make sure we don't have avformat-novalidate producers, since it caused crashes
QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
int max = producers.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(i).toElement();
if (Xml::getXmlProperty(prod, QStringLiteral("mlt_service")) == QLatin1String("avformat-novalidate")) {
Xml::setXmlProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("avformat"));
}
}
// There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last
// keyframe to real end of transition
// Get profile info (width / height)
int profileWidth;
int profileHeight;
QDomElement profile = m_doc.firstChildElement(QStringLiteral("profile"));
if (profile.isNull()) {
profile = infoXml.firstChildElement(QStringLiteral("profileinfo"));
if (!profile.isNull()) {
// old MLT format, we need to add profile
QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
QDomElement pr = profile.cloneNode().toElement();
pr.setTagName(QStringLiteral("profile"));
mlt.insertBefore(pr, firstProd);
}
}
if (profile.isNull()) {
// could not find profile info, set PAL
profileWidth = 720;
profileHeight = 576;
} else {
profileWidth = profile.attribute(QStringLiteral("width")).toInt();
profileHeight = profile.attribute(QStringLiteral("height")).toInt();
}
QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
max = transitions.count();
for (int i = 0; i < max; ++i) {
QDomElement trans = transitions.at(i).toElement();
int out = trans.attribute(QStringLiteral("out")).toInt() - trans.attribute(QStringLiteral("in")).toInt();
QString geom = Xml::getXmlProperty(trans, QStringLiteral("geometry"));
Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
Mlt::GeometryItem item;
if (g->next_key(&item, out) == 0) {
// We have a keyframe just after last frame, try to move it to last frame
if (item.frame() == out + 1) {
item.frame(out);
g->insert(item);
g->remove(out + 1);
Xml::setXmlProperty(trans, QStringLiteral("geometry"), QString::fromLatin1(g->serialise()));
}
}
delete g;
}
}
if (version <= 0.87) {
if (!m_doc.firstChildElement(QStringLiteral("mlt")).hasAttribute(QStringLiteral("LC_NUMERIC"))) {
m_doc.firstChildElement(QStringLiteral("mlt")).setAttribute(QStringLiteral("LC_NUMERIC"), QStringLiteral("C"));
}
}
if (version <= 0.88) {
// convert to new MLT-only format
QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
QDomDocumentFragment frag = m_doc.createDocumentFragment();
// Create Bin Playlist
QDomElement main_playlist = m_doc.createElement(QStringLiteral("playlist"));
QDomElement prop = m_doc.createElement(QStringLiteral("property"));
prop.setAttribute(QStringLiteral("name"), QStringLiteral("xml_retain"));
QDomText val = m_doc.createTextNode(QStringLiteral("1"));
prop.appendChild(val);
main_playlist.appendChild(prop);
// Move markers
QDomNodeList markers = m_doc.elementsByTagName(QStringLiteral("marker"));
for (int i = 0; i < markers.count(); ++i) {
QDomElement marker = markers.at(i).toElement();
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:marker.") + marker.attribute(QStringLiteral("id")) + QLatin1Char(':') +
marker.attribute(QStringLiteral("time")));
QDomText val_node = m_doc.createTextNode(marker.attribute(QStringLiteral("type")) + QLatin1Char(':') + marker.attribute(QStringLiteral("comment")));
property.appendChild(val_node);
main_playlist.appendChild(property);
}
// Move guides
QDomNodeList guides = m_doc.elementsByTagName(QStringLiteral("guide"));
for (int i = 0; i < guides.count(); ++i) {
QDomElement guide = guides.at(i).toElement();
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:guide.") + guide.attribute(QStringLiteral("time")));
QDomText val_node = m_doc.createTextNode(guide.attribute(QStringLiteral("comment")));
property.appendChild(val_node);
main_playlist.appendChild(property);
}
// Move folders
QDomNodeList folders = m_doc.elementsByTagName(QStringLiteral("folder"));
for (int i = 0; i < folders.count(); ++i) {
QDomElement folder = folders.at(i).toElement();
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folder.-1.") + folder.attribute(QStringLiteral("id")));
QDomText val_node = m_doc.createTextNode(folder.attribute(QStringLiteral("name")));
property.appendChild(val_node);
main_playlist.appendChild(property);
}
QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
main_playlist.setAttribute(QStringLiteral("id"), BinPlaylist::binPlaylistId);
mlt.toElement().setAttribute(QStringLiteral("producer"), BinPlaylist::binPlaylistId);
QStringList ids;
QStringList slowmotionIds;
QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
QDomNodeList kdenlive_producers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
// Rename all track producers to correct name: "id_playlistName" instead of "id_trackNumber"
QMap trackRenaming;
// Create a list of which producers / track on which the producer is
QMap playlistForId;
QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
for (int i = 0; i < entries.count(); i++) {
QDomElement entry = entries.at(i).toElement();
QString entryId = entry.attribute(QStringLiteral("producer"));
if (entryId == QLatin1String("black")) {
continue;
}
bool audioOnlyProducer = false;
if (trackRenaming.contains(entryId)) {
// rename
entry.setAttribute(QStringLiteral("producer"), trackRenaming.value(entryId));
continue;
}
if (entryId.endsWith(QLatin1String("_video"))) {
// Video only producers are not track aware
continue;
}
if (entryId.endsWith(QLatin1String("_audio"))) {
// Audio only producer
audioOnlyProducer = true;
entryId = entryId.section(QLatin1Char('_'), 0, -2);
}
if (!entryId.contains(QLatin1Char('_'))) {
// not a track producer
playlistForId.insert(entryId, entry.parentNode().toElement().attribute(QStringLiteral("id")));
continue;
}
if (entryId.startsWith(QLatin1String("slowmotion:"))) {
// Check broken slowmotion producers (they should not be track aware)
QString newId = QStringLiteral("slowmotion:") + entryId.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0) + QLatin1Char(':') +
entryId.section(QLatin1Char(':'), 2);
trackRenaming.insert(entryId, newId);
entry.setAttribute(QStringLiteral("producer"), newId);
continue;
}
QString track = entryId.section(QLatin1Char('_'), 1, 1);
QString playlistId = entry.parentNode().toElement().attribute(QStringLiteral("id"));
if (track == playlistId) {
continue;
}
QString newId = entryId.section(QLatin1Char('_'), 0, 0) + QLatin1Char('_') + playlistId;
if (audioOnlyProducer) {
newId.append(QStringLiteral("_audio"));
trackRenaming.insert(entryId + QStringLiteral("_audio"), newId);
} else {
trackRenaming.insert(entryId, newId);
}
entry.setAttribute(QStringLiteral("producer"), newId);
}
if (!trackRenaming.isEmpty()) {
for (int i = 0; i < producers.count(); ++i) {
QDomElement prod = producers.at(i).toElement();
QString id = prod.attribute(QStringLiteral("id"));
if (trackRenaming.contains(id)) {
prod.setAttribute(QStringLiteral("id"), trackRenaming.value(id));
}
}
}
// Create easily searchable index of original producers
QMap m_source_producers;
for (int j = 0; j < kdenlive_producers.count(); j++) {
QDomElement prod = kdenlive_producers.at(j).toElement();
QString id = prod.attribute(QStringLiteral("id"));
m_source_producers.insert(id, prod);
}
for (int i = 0; i < producers.count(); ++i) {
QDomElement prod = producers.at(i).toElement();
QString id = prod.attribute(QStringLiteral("id"));
if (id == QLatin1String("black")) {
continue;
}
if (id.startsWith(QLatin1String("slowmotion"))) {
// No need to process slowmotion producers
QString slowmo = id.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0);
if (!slowmotionIds.contains(slowmo)) {
slowmotionIds << slowmo;
}
continue;
}
QString prodId = id.section(QLatin1Char('_'), 0, 0);
if (ids.contains(prodId)) {
// Make sure we didn't create a duplicate
if (ids.contains(id)) {
// we have a duplicate, check if this needs to be a track producer
QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
int a_ix = Xml::getXmlProperty(prod, QStringLiteral("audio_index")).toInt();
if (service == QLatin1String("xml") || service == QLatin1String("consumer") ||
(service.contains(QStringLiteral("avformat")) && a_ix != -1)) {
// This should be a track producer, rename
QString newId = id + QLatin1Char('_') + playlistForId.value(id);
prod.setAttribute(QStringLiteral("id"), newId);
for (int j = 0; j < entries.count(); j++) {
QDomElement entry = entries.at(j).toElement();
QString entryId = entry.attribute(QStringLiteral("producer"));
if (entryId == id) {
entry.setAttribute(QStringLiteral("producer"), newId);
}
}
} else {
// This is a duplicate, remove
mlt.removeChild(prod);
i--;
}
}
// Already processed, continue
continue;
}
if (id == prodId) {
// This is an original producer, move it to the main playlist
QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
entry.setAttribute(QStringLiteral("producer"), id);
main_playlist.appendChild(entry);
QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
if (service == QLatin1String("kdenlivetitle")) {
fixTitleProducerLocale(prod);
}
QDomElement source = m_source_producers.value(id);
if (!source.isNull()) {
updateProducerInfo(prod, source);
entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1));
}
frag.appendChild(prod);
// Changing prod parent removes it from list, so rewind index
i--;
} else {
QDomElement originalProd = prod.cloneNode().toElement();
originalProd.setAttribute(QStringLiteral("id"), prodId);
if (id.endsWith(QLatin1String("_audio"))) {
Xml::removeXmlProperty(originalProd, QStringLiteral("video_index"));
} else if (id.endsWith(QLatin1String("_video"))) {
Xml::removeXmlProperty(originalProd, QStringLiteral("audio_index"));
}
QDomElement source = m_source_producers.value(prodId);
QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
if (!source.isNull()) {
updateProducerInfo(originalProd, source);
entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1));
}
frag.appendChild(originalProd);
entry.setAttribute(QStringLiteral("producer"), prodId);
main_playlist.appendChild(entry);
}
ids.append(prodId);
}
// Make sure to include producers that were not in timeline
for (int j = 0; j < kdenlive_producers.count(); j++) {
QDomElement prod = kdenlive_producers.at(j).toElement();
QString id = prod.attribute(QStringLiteral("id"));
if (!ids.contains(id)) {
// Clip was not in timeline, create it
QDomElement originalProd = prod.cloneNode().toElement();
originalProd.setTagName(QStringLiteral("producer"));
Xml::setXmlProperty(originalProd, QStringLiteral("resource"), originalProd.attribute(QStringLiteral("resource")));
updateProducerInfo(originalProd, prod);
originalProd.removeAttribute(QStringLiteral("proxy"));
originalProd.removeAttribute(QStringLiteral("type"));
originalProd.removeAttribute(QStringLiteral("file_hash"));
originalProd.removeAttribute(QStringLiteral("file_size"));
originalProd.removeAttribute(QStringLiteral("frame_size"));
originalProd.removeAttribute(QStringLiteral("zone_out"));
originalProd.removeAttribute(QStringLiteral("zone_in"));
originalProd.removeAttribute(QStringLiteral("name"));
originalProd.removeAttribute(QStringLiteral("type"));
originalProd.removeAttribute(QStringLiteral("duration"));
originalProd.removeAttribute(QStringLiteral("cutzones"));
int type = prod.attribute(QStringLiteral("type")).toInt();
QString mltService;
switch (type) {
case 4:
mltService = QStringLiteral("colour");
break;
case 5:
case 7:
mltService = QStringLiteral("qimage");
break;
case 6:
mltService = QStringLiteral("kdenlivetitle");
break;
case 9:
mltService = QStringLiteral("xml");
break;
default:
mltService = QStringLiteral("avformat");
break;
}
Xml::setXmlProperty(originalProd, QStringLiteral("mlt_service"), mltService);
Xml::setXmlProperty(originalProd, QStringLiteral("mlt_type"), QStringLiteral("producer"));
QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
entry.setAttribute(QStringLiteral("out"), QString::number(prod.attribute(QStringLiteral("duration")).toInt() - 1));
entry.setAttribute(QStringLiteral("producer"), id);
main_playlist.appendChild(entry);
if (type == 6) {
fixTitleProducerLocale(originalProd);
}
frag.appendChild(originalProd);
ids << id;
}
}
// Set clip folders
for (int j = 0; j < kdenlive_producers.count(); j++) {
QDomElement prod = kdenlive_producers.at(j).toElement();
QString id = prod.attribute(QStringLiteral("id"));
QString folder = prod.attribute(QStringLiteral("groupid"));
QDomNodeList mlt_producers = frag.childNodes();
for (int k = 0; k < mlt_producers.count(); k++) {
QDomElement mltprod = mlt_producers.at(k).toElement();
if (mltprod.tagName() != QLatin1String("producer")) {
continue;
}
if (mltprod.attribute(QStringLiteral("id")) == id) {
if (!folder.isEmpty()) {
// We have found our producer, set folder info
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folderid"));
QDomText val_node = m_doc.createTextNode(folder);
property.appendChild(val_node);
mltprod.appendChild(property);
}
break;
}
}
}
// Make sure all slowmotion producers have a master clip
for (int i = 0; i < slowmotionIds.count(); i++) {
const QString &slo = slowmotionIds.at(i);
if (!ids.contains(slo)) {
// rebuild producer from Kdenlive's old xml format
for (int j = 0; j < kdenlive_producers.count(); j++) {
QDomElement prod = kdenlive_producers.at(j).toElement();
QString id = prod.attribute(QStringLiteral("id"));
if (id == slo) {
// We found the kdenlive_producer, build MLT producer
QDomElement original = m_doc.createElement(QStringLiteral("producer"));
original.setAttribute(QStringLiteral("in"), 0);
original.setAttribute(QStringLiteral("out"), prod.attribute(QStringLiteral("duration")).toInt() - 1);
original.setAttribute(QStringLiteral("id"), id);
QDomElement property = m_doc.createElement(QStringLiteral("property"));
property.setAttribute(QStringLiteral("name"), QStringLiteral("resource"));
QDomText val_node = m_doc.createTextNode(prod.attribute(QStringLiteral("resource")));
property.appendChild(val_node);
original.appendChild(property);
QDomElement prop2 = m_doc.createElement(QStringLiteral("property"));
prop2.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
QDomText val2 = m_doc.createTextNode(QStringLiteral("avformat"));
prop2.appendChild(val2);
original.appendChild(prop2);
QDomElement prop3 = m_doc.createElement(QStringLiteral("property"));
prop3.setAttribute(QStringLiteral("name"), QStringLiteral("length"));
QDomText val3 = m_doc.createTextNode(prod.attribute(QStringLiteral("duration")));
prop3.appendChild(val3);
original.appendChild(prop3);
QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
entry.setAttribute(QStringLiteral("in"), original.attribute(QStringLiteral("in")));
entry.setAttribute(QStringLiteral("out"), original.attribute(QStringLiteral("out")));
entry.setAttribute(QStringLiteral("producer"), id);
main_playlist.appendChild(entry);
frag.appendChild(original);
ids << slo;
break;
}
}
}
}
frag.appendChild(main_playlist);
mlt.insertBefore(frag, firstProd);
}
if (version < 0.91) {
// Migrate track properties
QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
QDomNodeList old_tracks = m_doc.elementsByTagName(QStringLiteral("trackinfo"));
QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track"));
QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
for (int i = 0; i < old_tracks.count(); i++) {
QString playlistName = tracks.at(i + 1).toElement().attribute(QStringLiteral("producer"));
// find playlist for track
QDomElement trackPlaylist;
for (int j = 0; j < playlists.count(); j++) {
if (playlists.at(j).toElement().attribute(QStringLiteral("id")) == playlistName) {
trackPlaylist = playlists.at(j).toElement();
break;
}
}
if (!trackPlaylist.isNull()) {
QDomElement kdenliveTrack = old_tracks.at(i).toElement();
if (kdenliveTrack.attribute(QStringLiteral("type")) == QLatin1String("audio")) {
Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1"));
}
if (kdenliveTrack.attribute(QStringLiteral("locked")) == QLatin1String("1")) {
Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:locked_track"), QStringLiteral("1"));
}
Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:track_name"), kdenliveTrack.attribute(QStringLiteral("trackname")));
}
}
// Find bin playlist
playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
QDomElement playlist;
for (int i = 0; i < playlists.count(); i++) {
if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == BinPlaylist::binPlaylistId) {
playlist = playlists.at(i).toElement();
break;
}
}
// Migrate document notes
QDomNodeList notesList = m_doc.elementsByTagName(QStringLiteral("documentnotes"));
if (!notesList.isEmpty()) {
QDomElement notes_elem = notesList.at(0).toElement();
QString notes = notes_elem.firstChild().nodeValue();
Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:documentnotes"), notes);
}
// Migrate clip groups
QDomNodeList groupElement = m_doc.elementsByTagName(QStringLiteral("groups"));
if (!groupElement.isEmpty()) {
QDomElement groups = groupElement.at(0).toElement();
QDomDocument d2;
d2.importNode(groups, true);
Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:clipgroups"), d2.toString());
}
// Migrate custom effects
QDomNodeList effectsElement = m_doc.elementsByTagName(QStringLiteral("customeffects"));
if (!effectsElement.isEmpty()) {
QDomElement effects = effectsElement.at(0).toElement();
QDomDocument d2;
d2.importNode(effects, true);
Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:customeffects"), d2.toString());
}
Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:docproperties.version"), QString::number(currentVersion));
if (!infoXml.isNull()) {
Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:docproperties.projectfolder"), infoXml.attribute(QStringLiteral("projectfolder")));
}
// Remove deprecated Kdenlive extra info from xml doc before sending it to MLT
QDomElement docXml = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
if (!docXml.isNull()) {
mlt.removeChild(docXml);
}
}
if (version < 0.92) {
// Luma transition used for wipe is deprecated, we now use a composite, convert
QDomNodeList transitionList = m_doc.elementsByTagName(QStringLiteral("transition"));
QDomElement trans;
for (int i = 0; i < transitionList.count(); i++) {
trans = transitionList.at(i).toElement();
QString id = Xml::getXmlProperty(trans, QStringLiteral("kdenlive_id"));
if (id == QLatin1String("luma")) {
Xml::setXmlProperty(trans, QStringLiteral("kdenlive_id"), QStringLiteral("wipe"));
Xml::setXmlProperty(trans, QStringLiteral("mlt_service"), QStringLiteral("composite"));
bool reverse = Xml::getXmlProperty(trans, QStringLiteral("reverse")).toInt() != 0;
Xml::setXmlProperty(trans, QStringLiteral("luma_invert"), Xml::getXmlProperty(trans, QStringLiteral("invert")));
Xml::setXmlProperty(trans, QStringLiteral("luma"), Xml::getXmlProperty(trans, QStringLiteral("resource")));
Xml::removeXmlProperty(trans, QStringLiteral("invert"));
Xml::removeXmlProperty(trans, QStringLiteral("reverse"));
Xml::removeXmlProperty(trans, QStringLiteral("resource"));
if (reverse) {
Xml::setXmlProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100;-1=0%/0%:100%x100%:0"));
} else {
Xml::setXmlProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:0;-1=0%/0%:100%x100%:100"));
}
Xml::setXmlProperty(trans, QStringLiteral("aligned"), QStringLiteral("0"));
Xml::setXmlProperty(trans, QStringLiteral("fill"), QStringLiteral("1"));
}
}
}
if (version < 0.93) {
// convert old keyframe filters to animated
// these filters were "animated" by adding several instance of the filter, each one having a start and end tag.
// We convert by parsing the start and end tags vor values and adding all to the new animated parameter
QMap keyframeFilterToConvert;
keyframeFilterToConvert.insert(QStringLiteral("volume"), QStringList() << QStringLiteral("gain") << QStringLiteral("end") << QStringLiteral("level"));
keyframeFilterToConvert.insert(QStringLiteral("brightness"), QStringList()
<< QStringLiteral("start") << QStringLiteral("end") << QStringLiteral("level"));
QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
for (int i = 0; i < entries.count(); i++) {
QDomNode entry = entries.at(i);
QDomNodeList effects = entry.toElement().elementsByTagName(QStringLiteral("filter"));
QStringList parsedIds;
for (int j = 0; j < effects.count(); j++) {
QDomElement eff = effects.at(j).toElement();
QString id = Xml::getXmlProperty(eff, QStringLiteral("kdenlive_id"));
if (keyframeFilterToConvert.contains(id) && !parsedIds.contains(id)) {
parsedIds << id;
QMap values;
QStringList conversionParams = keyframeFilterToConvert.value(id);
int offset = eff.attribute(QStringLiteral("in")).toInt();
int out = eff.attribute(QStringLiteral("out")).toInt();
convertKeyframeEffect(eff, conversionParams, values, offset);
Xml::removeXmlProperty(eff, conversionParams.at(0));
Xml::removeXmlProperty(eff, conversionParams.at(1));
for (int k = j + 1; k < effects.count(); k++) {
QDomElement subEffect = effects.at(k).toElement();
QString subId = Xml::getXmlProperty(subEffect, QStringLiteral("kdenlive_id"));
if (subId == id) {
convertKeyframeEffect(subEffect, conversionParams, values, offset);
out = subEffect.attribute(QStringLiteral("out")).toInt();
entry.removeChild(subEffect);
k--;
}
}
QStringList parsedValues;
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
QMapIterator l(values);
if (id == QLatin1String("volume")) {
// convert old volume range (0-300) to new dB values (-60-60)
while (l.hasNext()) {
l.next();
double v = l.value();
if (v <= 0) {
v = -60;
} else {
v = log10(v) * 20;
}
parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(v);
}
} else {
while (l.hasNext()) {
l.next();
parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(l.value());
}
}
Xml::setXmlProperty(eff, conversionParams.at(2), parsedValues.join(QLatin1Char(';')));
// Xml::setXmlProperty(eff, QStringLiteral("kdenlive:sync_in_out"), QStringLiteral("1"));
eff.setAttribute(QStringLiteral("out"), out);
}
}
}
}
if (version < 0.94) {
// convert slowmotion effects/producers
QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
int max = producers.count();
QStringList slowmoIds;
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(i).toElement();
QString id = prod.attribute(QStringLiteral("id"));
if (id.startsWith(QLatin1String("slowmotion"))) {
QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
if (service == QLatin1String("framebuffer")) {
// convert to new timewarp producer
prod.setAttribute(QStringLiteral("id"), id + QStringLiteral(":1"));
slowmoIds << id;
Xml::setXmlProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("timewarp"));
QString resource = Xml::getXmlProperty(prod, QStringLiteral("resource"));
Xml::setXmlProperty(prod, QStringLiteral("warp_resource"), resource.section(QLatin1Char('?'), 0, 0));
Xml::setXmlProperty(prod, QStringLiteral("warp_speed"), resource.section(QLatin1Char('?'), 1).section(QLatin1Char(':'), 0, 0));
Xml::setXmlProperty(prod, QStringLiteral("resource"),
resource.section(QLatin1Char('?'), 1) + QLatin1Char(':') + resource.section(QLatin1Char('?'), 0, 0));
Xml::setXmlProperty(prod, QStringLiteral("audio_index"), QStringLiteral("-1"));
}
}
}
if (!slowmoIds.isEmpty()) {
producers = m_doc.elementsByTagName(QStringLiteral("entry"));
max = producers.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(i).toElement();
QString entryId = prod.attribute(QStringLiteral("producer"));
if (slowmoIds.contains(entryId)) {
prod.setAttribute(QStringLiteral("producer"), entryId + QStringLiteral(":1"));
}
}
}
// qCDebug(KDENLIVE_LOG)<<"------------------------\n"< markersList;
QLocale locale;
for (int i = 0; i < props.count(); ++i) {
QDomNode n = props.at(i);
QString prop = n.toElement().attribute(QStringLiteral("name"));
if (prop.startsWith(QLatin1String("kdenlive:guide."))) {
// Process guide
double guidePos = locale.toDouble(prop.section(QLatin1Char('.'), 1));
QJsonObject currentGuide;
currentGuide.insert(QStringLiteral("pos"), QJsonValue(GenTime(guidePos).frames(pCore->getCurrentFps())));
currentGuide.insert(QStringLiteral("comment"), QJsonValue(n.firstChild().nodeValue()));
currentGuide.insert(QStringLiteral("type"), QJsonValue(0));
// Clear entry in old format
n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_"));
guidesList.push_back(currentGuide);
} else if (prop.startsWith(QLatin1String("kdenlive:marker."))) {
// Process marker
double markerPos = locale.toDouble(prop.section(QLatin1Char(':'), -1));
QString markerBinClip = prop.section(QLatin1Char('.'), 1).section(QLatin1Char(':'), 0, 0);
QString markerData = n.firstChild().nodeValue();
int markerType = markerData.section(QLatin1Char(':'), 0, 0).toInt();
QString markerComment = markerData.section(QLatin1Char(':'), 1);
QJsonObject currentMarker;
currentMarker.insert(QStringLiteral("pos"), QJsonValue(GenTime(markerPos).frames(pCore->getCurrentFps())));
currentMarker.insert(QStringLiteral("comment"), QJsonValue(markerComment));
currentMarker.insert(QStringLiteral("type"), QJsonValue(markerType));
// Clear entry in old format
n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_"));
if (markersList.contains(markerBinClip)) {
// we already have a marker list for this clip
QJsonArray markerList = markersList.value(markerBinClip);
markerList.push_back(currentMarker);
markersList.insert(markerBinClip, markerList);
} else {
QJsonArray markerList;
markerList.push_back(currentMarker);
markersList.insert(markerBinClip, markerList);
}
}
}
if (!guidesList.isEmpty()) {
QJsonDocument json(guidesList);
Xml::setXmlProperty(main_playlist, QStringLiteral("kdenlive:docproperties.guides"), json.toJson());
}
// Update producers
QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
int max = producers.count();
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.at(i).toElement();
if (prod.isNull()) continue;
// Move to new kdenlive:id format
const QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:id"), id);
if (markersList.contains(id)) {
QJsonDocument json(markersList.value(id));
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:markers"), json.toJson());
}
// Check image sequences with buggy begin frame number
const QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
if (service == QLatin1String("pixbuf") || service == QLatin1String("qimage")) {
QString resource = Xml::getXmlProperty(prod, QStringLiteral("resource"));
if (resource.contains(QStringLiteral("?begin:"))) {
resource.replace(QStringLiteral("?begin:"), QStringLiteral("?begin="));
Xml::setXmlProperty(prod, QStringLiteral("resource"), resource);
}
}
}
}
if (version < 0.98) {
// rename main bin playlist, create extra tracks for old type AV clips, port groups to JSon
QJsonArray newGroups;
QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
QDomNodeList masterProducers = m_doc.elementsByTagName(QStringLiteral("producer"));
QDomElement playlist;
QDomNode mainplaylist;
QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
QDomNode tractor = mlt.firstChildElement(QStringLiteral("tractor"));
// Build start trackIndex
QMap trackIndex;
QDomNodeList tracks = tractor.toElement().elementsByTagName(QStringLiteral("track"));
for (int i = 0; i < tracks.count(); i++) {
trackIndex.insert(QString::number(i), tracks.at(i).toElement().attribute(QStringLiteral("producer")));
}
int trackOffset = 0;
// AV clips are not supported anymore. Check if we have some and add extra audio tracks if necessary
// Update the main bin name as well to be xml compliant
for (int i = 0; i < playlists.count(); i++) {
if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main bin")) {
playlists.at(i).toElement().setAttribute(QStringLiteral("id"), BinPlaylist::binPlaylistId);
mainplaylist = playlists.at(i);
QString oldGroups = Xml::getXmlProperty(mainplaylist.toElement(), QStringLiteral("kdenlive:clipgroups"));
QDomDocument groupsDoc;
groupsDoc.setContent(oldGroups);
QDomNodeList groups = groupsDoc.elementsByTagName(QStringLiteral("group"));
for (int g = 0; g < groups.count(); g++) {
QDomNodeList elements = groups.at(g).childNodes();
QJsonArray array;
for (int h = 0; h < elements.count(); h++) {
QJsonObject item;
item.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
item.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
QString pos = elements.at(h).toElement().attribute(QStringLiteral("position"));
QString track = trackIndex.value(elements.at(h).toElement().attribute(QStringLiteral("track")));
item.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(track).arg(pos)));
array.push_back(item);
}
QJsonObject currentGroup;
currentGroup.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Normal")));
currentGroup.insert(QLatin1String("children"), array);
newGroups.push_back(currentGroup);
}
} else {
if (Xml::getXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:audio_track")) == QLatin1String("1")) {
// Audio track, no need to process
continue;
}
const QString playlistName = playlists.at(i).toElement().attribute(QStringLiteral("id"));
QDomElement duplicate_playlist = m_doc.createElement(QStringLiteral("playlist"));
duplicate_playlist.setAttribute(QStringLiteral("id"), QString("%1_duplicate").arg(playlistName));
QDomElement pltype = m_doc.createElement(QStringLiteral("property"));
pltype.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:audio_track"));
pltype.setNodeValue(QStringLiteral("1"));
QDomText value1 = m_doc.createTextNode(QStringLiteral("1"));
pltype.appendChild(value1);
duplicate_playlist.appendChild(pltype);
QDomElement plname = m_doc.createElement(QStringLiteral("property"));
plname.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:track_name"));
QDomText value = m_doc.createTextNode(i18n("extra audio"));
plname.appendChild(value);
duplicate_playlist.appendChild(plname);
QDomNodeList producers = playlists.at(i).childNodes();
bool duplicationRequested = false;
int pos = 0;
for (int j = 0; j < producers.count(); j++) {
if (producers.at(j).nodeName() == QLatin1String("blank")) {
// blank, duplicate
duplicate_playlist.appendChild(producers.at(j).cloneNode());
pos += producers.at(j).toElement().attribute(QStringLiteral("length")).toInt();
} else if (producers.at(j).nodeName() == QLatin1String("filter")) {
// effect, duplicate
duplicate_playlist.appendChild(producers.at(j).cloneNode());
} else if (producers.at(j).nodeName() != QLatin1String("entry")) {
// property node, pass
continue;
} else if (producers.at(j).toElement().attribute(QStringLiteral("producer")).endsWith(playlistName)) {
// This is an AV clip
// Check master properties
bool hasAudio = true;
bool hasVideo = true;
const QString currentId = producers.at(j).toElement().attribute(QStringLiteral("producer"));
int in = producers.at(j).toElement().attribute(QStringLiteral("in")).toInt();
int out = producers.at(j).toElement().attribute(QStringLiteral("out")).toInt();
for (int k = 0; k < masterProducers.count(); k++) {
if (masterProducers.at(k).toElement().attribute(QStringLiteral("id")) == currentId) {
hasVideo = Xml::getXmlProperty(masterProducers.at(k).toElement(), QStringLiteral("video_index")) != QLatin1String("-1");
hasAudio = Xml::getXmlProperty(masterProducers.at(k).toElement(), QStringLiteral("audio_index")) != QLatin1String("-1");
break;
}
}
if (!hasAudio) {
// no duplication needed, replace with blank
QDomElement duplicate = m_doc.createElement(QStringLiteral("blank"));
duplicate.setAttribute(QStringLiteral("length"), QString::number(out - in + 1));
duplicate_playlist.appendChild(duplicate);
pos += out - in + 1;
continue;
}
QDomNode prod = producers.at(j).cloneNode();
Xml::setXmlProperty(prod.toElement(), QStringLiteral("set.test_video"), QStringLiteral("1"));
duplicate_playlist.appendChild(prod);
// Check if that is an audio clip on a video track
if (!hasVideo) {
// Audio clip on a video track, replace with blank and duplicate
producers.at(j).toElement().setTagName("blank");
producers.at(j).toElement().setAttribute("length", QString::number(out - in + 1));
} else {
// group newly created AVSplit group
// We temporarily store track with their playlist name since track index will change
// as we insert the duplicate tracks
QJsonArray array;
QJsonObject items;
items.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
items.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
items.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(playlistName).arg(pos)));
array.push_back(items);
QJsonObject itemb;
itemb.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
itemb.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
itemb.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(duplicate_playlist.attribute(QStringLiteral("id"))).arg(pos)));
array.push_back(itemb);
QJsonObject currentGroup;
currentGroup.insert(QLatin1String("type"), QJsonValue(QStringLiteral("AVSplit")));
currentGroup.insert(QLatin1String("children"), array);
newGroups.push_back(currentGroup);
}
duplicationRequested = true;
pos += out - in + 1;
} else {
// no duplication needed, replace with blank
QDomElement duplicate = m_doc.createElement(QStringLiteral("blank"));
int in = producers.at(j).toElement().attribute(QStringLiteral("in")).toInt();
int out = producers.at(j).toElement().attribute(QStringLiteral("out")).toInt();
duplicate.setAttribute(QStringLiteral("length"), QString::number(out - in + 1));
duplicate_playlist.appendChild(duplicate);
pos += out - in + 1;
}
}
if (duplicationRequested) {
// Plant the playlist at the end
mlt.insertBefore(duplicate_playlist, tractor);
QDomNode lastTrack = tractor.firstChildElement(QStringLiteral("track"));
QDomElement duplicate = m_doc.createElement(QStringLiteral("track"));
duplicate.setAttribute(QStringLiteral("producer"), QString("%1_duplicate").arg(playlistName));
duplicate.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
tractor.insertAfter(duplicate, lastTrack);
trackOffset++;
}
}
}
if (trackOffset > 0) {
// Some tracks were added, adjust compositions
QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
int max = transitions.count();
for (int i = 0; i < max; ++i) {
QDomElement t = transitions.at(i).toElement();
if (Xml::getXmlProperty(t, QStringLiteral("internal_added")).toInt() > 0) {
// internal transitions will be rebuilt, no need to correct
continue;
}
int a_track = Xml::getXmlProperty(t, QStringLiteral("a_track")).toInt();
int b_track = Xml::getXmlProperty(t, QStringLiteral("b_track")).toInt();
if (a_track > 0) {
Xml::setXmlProperty(t, QStringLiteral("a_track"), QString::number(a_track + trackOffset));
}
if (b_track > 0) {
Xml::setXmlProperty(t, QStringLiteral("b_track"), QString::number(b_track + trackOffset));
}
}
}
// Process groups data
QJsonDocument json(newGroups);
QString groupsData = QString(json.toJson());
tracks = tractor.toElement().elementsByTagName(QStringLiteral("track"));
for (int i = 0; i < tracks.count(); i++) {
// Replace track names with their current index in our view
const QString trackId = QString("%1:").arg(tracks.at(i).toElement().attribute(QStringLiteral("producer")));
groupsData.replace(trackId, QString("%1:").arg(i - 1));
}
Xml::setXmlProperty(mainplaylist.toElement(), QStringLiteral("kdenlive:docproperties.groups"), groupsData);
}
if (version < 0.99) {
// rename main bin playlist, create extra tracks for old type AV clips, port groups to JSon
QDomNodeList masterProducers = m_doc.elementsByTagName(QStringLiteral("producer"));
for (int i = 0; i < masterProducers.count(); i++) {
QMap map = Xml::getXmlPropertyByWildcard(masterProducers.at(i).toElement(), QLatin1String("kdenlive:clipzone."));
if (map.isEmpty()) {
continue;
}
QJsonArray list;
QMapIterator j(map);
while (j.hasNext()) {
j.next();
Xml::removeXmlProperty(masterProducers.at(i).toElement(), j.key());
QJsonObject currentZone;
currentZone.insert(QLatin1String("name"), QJsonValue(j.key().section(QLatin1Char('.'),1)));
if (!j.value().contains(QLatin1Char(';'))) {
// invalid zone
continue;
}
currentZone.insert(QLatin1String("in"), QJsonValue(j.value().section(QLatin1Char(';'), 0, 0).toInt()));
currentZone.insert(QLatin1String("out"), QJsonValue(j.value().section(QLatin1Char(';'), 1, 1).toInt()));
list.push_back(currentZone);
}
QJsonDocument json(list);
Xml::setXmlProperty(masterProducers.at(i).toElement(), QStringLiteral("kdenlive:clipzones"), QString(json.toJson()));
}
}
m_modified = true;
return true;
}
void DocumentValidator::convertKeyframeEffect(const QDomElement &effect, const QStringList ¶ms, QMap &values, int offset)
{
QLocale locale;
int in = effect.attribute(QStringLiteral("in")).toInt() - offset;
values.insert(in, locale.toDouble(Xml::getXmlProperty(effect, params.at(0))));
QString endValue = Xml::getXmlProperty(effect, params.at(1));
if (!endValue.isEmpty()) {
int out = effect.attribute(QStringLiteral("out")).toInt() - offset;
values.insert(out, locale.toDouble(endValue));
}
}
void DocumentValidator::updateProducerInfo(const QDomElement &prod, const QDomElement &source)
{
QString pxy = source.attribute(QStringLiteral("proxy"));
if (pxy.length() > 1) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:proxy"), pxy);
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:originalurl"), source.attribute(QStringLiteral("resource")));
}
if (source.hasAttribute(QStringLiteral("file_hash"))) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:file_hash"), source.attribute(QStringLiteral("file_hash")));
}
if (source.hasAttribute(QStringLiteral("file_size"))) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:file_size"), source.attribute(QStringLiteral("file_size")));
}
if (source.hasAttribute(QStringLiteral("name"))) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:clipname"), source.attribute(QStringLiteral("name")));
}
if (source.hasAttribute(QStringLiteral("zone_out"))) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:zone_out"), source.attribute(QStringLiteral("zone_out")));
}
if (source.hasAttribute(QStringLiteral("zone_in"))) {
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:zone_in"), source.attribute(QStringLiteral("zone_in")));
}
if (source.hasAttribute(QStringLiteral("cutzones"))) {
QString zoneData = source.attribute(QStringLiteral("cutzones"));
const QStringList zoneList = zoneData.split(QLatin1Char(';'));
int ct = 1;
for (const QString &data : zoneList) {
QString zoneName = data.section(QLatin1Char('-'), 2);
if (zoneName.isEmpty()) {
zoneName = i18n("Zone %1", ct++);
}
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:clipzone.") + zoneName,
data.section(QLatin1Char('-'), 0, 0) + QLatin1Char(';') + data.section(QLatin1Char('-'), 1, 1));
}
}
}
QStringList DocumentValidator::getInfoFromEffectName(const QString &oldName)
{
QStringList info;
// Returns a list to convert old Kdenlive ladspa effects
if (oldName == QLatin1String("pitch_shift")) {
info << QStringLiteral("ladspa.1433");
info << QStringLiteral("pitch=0");
} else if (oldName == QLatin1String("vinyl")) {
info << QStringLiteral("ladspa.1905");
info << QStringLiteral("year=0");
info << QStringLiteral("rpm=1");
info << QStringLiteral("warping=2");
info << QStringLiteral("crackle=3");
info << QStringLiteral("wear=4");
} else if (oldName == QLatin1String("room_reverb")) {
info << QStringLiteral("ladspa.1216");
info << QStringLiteral("room=0");
info << QStringLiteral("delay=1");
info << QStringLiteral("damp=2");
} else if (oldName == QLatin1String("reverb")) {
info << QStringLiteral("ladspa.1423");
info << QStringLiteral("room=0");
info << QStringLiteral("damp=1");
} else if (oldName == QLatin1String("rate_scale")) {
info << QStringLiteral("ladspa.1417");
info << QStringLiteral("rate=0");
} else if (oldName == QLatin1String("pitch_scale")) {
info << QStringLiteral("ladspa.1193");
info << QStringLiteral("coef=0");
} else if (oldName == QLatin1String("phaser")) {
info << QStringLiteral("ladspa.1217");
info << QStringLiteral("rate=0");
info << QStringLiteral("depth=1");
info << QStringLiteral("feedback=2");
info << QStringLiteral("spread=3");
} else if (oldName == QLatin1String("limiter")) {
info << QStringLiteral("ladspa.1913");
info << QStringLiteral("gain=0");
info << QStringLiteral("limit=1");
info << QStringLiteral("release=2");
} else if (oldName == QLatin1String("equalizer_15")) {
info << QStringLiteral("ladspa.1197");
info << QStringLiteral("1=0");
info << QStringLiteral("2=1");
info << QStringLiteral("3=2");
info << QStringLiteral("4=3");
info << QStringLiteral("5=4");
info << QStringLiteral("6=5");
info << QStringLiteral("7=6");
info << QStringLiteral("8=7");
info << QStringLiteral("9=8");
info << QStringLiteral("10=9");
info << QStringLiteral("11=10");
info << QStringLiteral("12=11");
info << QStringLiteral("13=12");
info << QStringLiteral("14=13");
info << QStringLiteral("15=14");
} else if (oldName == QLatin1String("equalizer")) {
info << QStringLiteral("ladspa.1901");
info << QStringLiteral("logain=0");
info << QStringLiteral("midgain=1");
info << QStringLiteral("higain=2");
} else if (oldName == QLatin1String("declipper")) {
info << QStringLiteral("ladspa.1195");
}
return info;
}
QString DocumentValidator::colorToString(const QColor &c)
{
QString ret = QStringLiteral("%1,%2,%3,%4");
ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
return ret;
}
bool DocumentValidator::isProject() const
{
return m_doc.documentElement().tagName() == QLatin1String("mlt");
}
bool DocumentValidator::isModified() const
{
return m_modified;
}
bool DocumentValidator::checkMovit()
{
QString playlist = m_doc.toString();
if (!playlist.contains(QStringLiteral("movit."))) {
// Project does not use Movit GLSL effects, we can load it
return true;
}
if (KMessageBox::questionYesNo(QApplication::activeWindow(),
i18n("The project file uses some GPU effects. GPU acceleration is not currently enabled.\nDo you want to convert the "
"project to a non-GPU version?\nThis might result in data loss.")) != KMessageBox::Yes) {
return false;
}
// Try to convert Movit filters to their non GPU equivalent
QStringList convertedFilters;
QStringList discardedFilters;
bool hasWB = EffectsRepository::get()->exists(QStringLiteral("frei0r.colgate"));
bool hasBlur = EffectsRepository::get()->exists(QStringLiteral("frei0r.IIRblur"));
QString compositeTrans;
if (TransitionsRepository::get()->exists(QStringLiteral("qtblend"))) {
compositeTrans = QStringLiteral("qtblend");
} else if (TransitionsRepository::get()->exists(QStringLiteral("frei0r.cairoblend"))) {
compositeTrans = QStringLiteral("frei0r.cairoblend");
}
// Parse all effects in document
QDomNodeList filters = m_doc.elementsByTagName(QStringLiteral("filter"));
int max = filters.count();
for (int i = 0; i < max; ++i) {
QDomElement filt = filters.at(i).toElement();
QString filterId = filt.attribute(QStringLiteral("id"));
if (!filterId.startsWith(QLatin1String("movit."))) {
continue;
}
if (filterId == QLatin1String("movit.white_balance") && hasWB) {
// Convert to frei0r.colgate
filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.colgate"));
Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.colgate"));
Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.colgate"));
Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.colgate"));
Xml::renameXmlProperty(filt, QStringLiteral("neutral_color"), QStringLiteral("Neutral Color"));
QString value = Xml::getXmlProperty(filt, QStringLiteral("color_temperature"));
value = factorizeGeomValue(value, 15000.0);
Xml::setXmlProperty(filt, QStringLiteral("color_temperature"), value);
Xml::renameXmlProperty(filt, QStringLiteral("color_temperature"), QStringLiteral("Color Temperature"));
convertedFilters << filterId;
continue;
}
if (filterId == QLatin1String("movit.blur") && hasBlur) {
// Convert to frei0r.IIRblur
filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.IIRblur"));
Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.IIRblur"));
Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.IIRblur"));
Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.IIRblur"));
Xml::renameXmlProperty(filt, QStringLiteral("radius"), QStringLiteral("Amount"));
QString value = Xml::getXmlProperty(filt, QStringLiteral("Amount"));
value = factorizeGeomValue(value, 14.0);
Xml::setXmlProperty(filt, QStringLiteral("Amount"), value);
convertedFilters << filterId;
continue;
}
if (filterId == QLatin1String("movit.mirror")) {
// Convert to MLT's mirror
filt.setAttribute(QStringLiteral("id"), QStringLiteral("mirror"));
Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("mirror"));
Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("mirror"));
Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("mirror"));
Xml::setXmlProperty(filt, QStringLiteral("mirror"), QStringLiteral("flip"));
convertedFilters << filterId;
continue;
}
if (filterId.startsWith(QLatin1String("movit."))) {
// TODO: implement conversion for more filters
discardedFilters << filterId;
}
}
// Parse all transitions in document
QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
max = transitions.count();
for (int i = 0; i < max; ++i) {
QDomElement t = transitions.at(i).toElement();
QString transId = Xml::getXmlProperty(t, QStringLiteral("mlt_service"));
if (!transId.startsWith(QLatin1String("movit."))) {
continue;
}
if (transId == QLatin1String("movit.overlay") && !compositeTrans.isEmpty()) {
// Convert to frei0r.cairoblend
Xml::setXmlProperty(t, QStringLiteral("mlt_service"), compositeTrans);
convertedFilters << transId;
continue;
}
if (transId.startsWith(QLatin1String("movit."))) {
// TODO: implement conversion for more filters
discardedFilters << transId;
}
}
convertedFilters.removeDuplicates();
discardedFilters.removeDuplicates();
if (discardedFilters.isEmpty()) {
KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were converted to non GPU versions:"),
convertedFilters);
} else {
KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were deleted from the project:"), discardedFilters);
}
m_modified = true;
QString scene = m_doc.toString();
scene.replace(QLatin1String("movit."), QString());
m_doc.setContent(scene);
return true;
}
QString DocumentValidator::factorizeGeomValue(const QString &value, double factor)
{
const QStringList vals = value.split(QLatin1Char(';'));
QString result;
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (int i = 0; i < vals.count(); i++) {
const QString &s = vals.at(i);
QString key = s.section(QLatin1Char('='), 0, 0);
QString val = s.section(QLatin1Char('='), 1, 1);
double v = locale.toDouble(val) / factor;
result.append(key + QLatin1Char('=') + locale.toString(v));
if (i + 1 < vals.count()) {
result.append(QLatin1Char(';'));
}
}
return result;
}
void DocumentValidator::checkOrphanedProducers()
{
QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
QDomElement main = mlt.firstChildElement(QStringLiteral("playlist"));
QDomNodeList bin_producers = main.childNodes();
QStringList binProducers;
for (int k = 0; k < bin_producers.count(); k++) {
QDomElement mltprod = bin_producers.at(k).toElement();
if (mltprod.tagName() != QLatin1String("entry")) {
continue;
}
binProducers << mltprod.attribute(QStringLiteral("producer"));
}
QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
int max = producers.count();
QStringList allProducers;
for (int i = 0; i < max; ++i) {
QDomElement prod = producers.item(i).toElement();
if (prod.isNull()) {
continue;
}
allProducers << prod.attribute(QStringLiteral("id"));
}
QDomDocumentFragment frag = m_doc.createDocumentFragment();
QDomDocumentFragment trackProds = m_doc.createDocumentFragment();
for (int i = 0; i < producers.count(); ++i) {
QDomElement prod = producers.item(i).toElement();
if (prod.isNull()) {
continue;
}
QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
if (id.startsWith(QLatin1String("slowmotion")) || id == QLatin1String("black")) {
continue;
}
if (!binProducers.contains(id)) {
QString binId = Xml::getXmlProperty(prod, QStringLiteral("kdenlive:binid"));
Xml::setXmlProperty(prod, QStringLiteral("kdenlive:id"), binId);
if (!binId.isEmpty() && binProducers.contains(binId)) {
continue;
}
qCWarning(KDENLIVE_LOG) << " ///////// WARNING, FOUND UNKNOWN PRODUDER: " << id << " ----------------";
// This producer is unknown to Bin
QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
QString distinctiveTag(QStringLiteral("resource"));
if (service == QLatin1String("kdenlivetitle")) {
distinctiveTag = QStringLiteral("xmldata");
}
QString orphanValue = Xml::getXmlProperty(prod, distinctiveTag);
for (int j = 0; j < producers.count(); j++) {
// Search for a similar producer
QDomElement binProd = producers.item(j).toElement();
binId = binProd.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
if (service != QLatin1String("timewarp") && (binId.startsWith(QLatin1String("slowmotion")) || !binProducers.contains(binId))) {
continue;
}
QString binService = Xml::getXmlProperty(binProd, QStringLiteral("mlt_service"));
qCDebug(KDENLIVE_LOG) << " / /LKNG FOR: " << service << " / " << orphanValue << ", checking: " << binProd.attribute(QStringLiteral("id"));
if (service != binService) {
continue;
}
QString binValue = Xml::getXmlProperty(binProd, distinctiveTag);
if (binValue == orphanValue) {
// Found probable source producer, replace
frag.appendChild(prod);
if (i > 0) {
i--;
}
QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
for (int k = 0; k < entries.count(); k++) {
QDomElement entry = entries.at(k).toElement();
if (entry.attribute(QStringLiteral("producer")) == id) {
QString entryId = binId;
if (service.contains(QStringLiteral("avformat")) || service == QLatin1String("xml") || service == QLatin1String("consumer")) {
// We must use track producer, find track for this entry
QString trackPlaylist = entry.parentNode().toElement().attribute(QStringLiteral("id"));
entryId.append(QLatin1Char('_') + trackPlaylist);
}
if (!allProducers.contains(entryId)) {
// The track producer does not exist, create a clone for it
QDomElement cloned = binProd.cloneNode(true).toElement();
cloned.setAttribute(QStringLiteral("id"), entryId);
trackProds.appendChild(cloned);
allProducers << entryId;
}
entry.setAttribute(QStringLiteral("producer"), entryId);
m_modified = true;
}
}
continue;
}
}
}
}
if (!trackProds.isNull()) {
QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
mlt.insertBefore(trackProds, firstProd);
}
}
void DocumentValidator::fixTitleProducerLocale(QDomElement &producer)
{
QString data = Xml::getXmlProperty(producer, QStringLiteral("xmldata"));
QDomDocument doc;
doc.setContent(data);
QDomNodeList nodes = doc.elementsByTagName(QStringLiteral("position"));
bool fixed = false;
for (int i = 0; i < nodes.count(); i++) {
QDomElement pos = nodes.at(i).toElement();
QString x = pos.attribute(QStringLiteral("x"));
QString y = pos.attribute(QStringLiteral("y"));
if (x.contains(QLatin1Char(','))) {
// x pos was saved in locale format, fix
x = x.section(QLatin1Char(','), 0, 0);
pos.setAttribute(QStringLiteral("x"), x);
fixed = true;
}
if (y.contains(QLatin1Char(','))) {
// x pos was saved in locale format, fix
y = y.section(QLatin1Char(','), 0, 0);
pos.setAttribute(QStringLiteral("y"), y);
fixed = true;
}
}
nodes = doc.elementsByTagName(QStringLiteral("content"));
for (int i = 0; i < nodes.count(); i++) {
QDomElement pos = nodes.at(i).toElement();
QString x = pos.attribute(QStringLiteral("font-outline"));
QString y = pos.attribute(QStringLiteral("textwidth"));
if (x.contains(QLatin1Char(','))) {
// x pos was saved in locale format, fix
x = x.section(QLatin1Char(','), 0, 0);
pos.setAttribute(QStringLiteral("font-outline"), x);
fixed = true;
}
if (y.contains(QLatin1Char(','))) {
// x pos was saved in locale format, fix
y = y.section(QLatin1Char(','), 0, 0);
pos.setAttribute(QStringLiteral("textwidth"), y);
fixed = true;
}
}
if (fixed) {
Xml::setXmlProperty(producer, QStringLiteral("xmldata"), doc.toString());
}
}
diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp
index 458bbfddc..e78de958e 100644
--- a/src/effects/effectstack/model/effectstackmodel.cpp
+++ b/src/effects/effectstack/model/effectstackmodel.cpp
@@ -1,1260 +1,1262 @@
/***************************************************************************
* 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 "effectstackmodel.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "effectgroupmodel.hpp"
#include "effectitemmodel.hpp"
#include "effects/effectsrepository.hpp"
#include "macros.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include
#include
#include
#include
EffectStackModel::EffectStackModel(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack)
: AbstractTreeModel()
, m_effectStackEnabled(true)
, m_ownerId(std::move(ownerId))
, m_undoStack(std::move(undo_stack))
, m_lock(QReadWriteLock::Recursive)
, m_loadingExisting(false)
{
m_masterService = std::move(service);
}
std::shared_ptr EffectStackModel::construct(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack)
{
std::shared_ptr self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack)));
self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true);
return self;
}
void EffectStackModel::resetService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_masterService = std::move(service);
m_childServices.clear();
// replant all effects in new service
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->plant(m_masterService);
}
}
void EffectStackModel::addService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_childServices.emplace_back(std::move(service));
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->plantClone(m_childServices.back());
}
}
void EffectStackModel::loadService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_childServices.emplace_back(std::move(service));
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->loadClone(m_childServices.back());
}
}
void EffectStackModel::removeService(const std::shared_ptr &service)
{
QWriteLocker locker(&m_lock);
std::vector to_delete;
for (int i = int(m_childServices.size()) - 1; i >= 0; --i) {
auto ptr = m_childServices[uint(i)].lock();
if (service->get_int("_childid") == ptr->get_int("_childid")) {
for (int j = 0; j < rootItem->childCount(); ++j) {
std::static_pointer_cast(rootItem->child(j))->unplantClone(ptr);
}
to_delete.push_back(i);
}
}
for (int i : to_delete) {
m_childServices.erase(m_childServices.begin() + i);
}
}
void EffectStackModel::removeCurrentEffect()
{
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0) {
return;
}
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(ix));
if (effect) {
removeEffect(effect);
}
}
void EffectStackModel::removeEffect(const std::shared_ptr &effect)
{
qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!";
QWriteLocker locker(&m_lock);
Q_ASSERT(m_allItems.count(effect->getId()) > 0);
int parentId = -1;
if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
int current = 0;
if (auto srv = m_masterService.lock()) {
current = srv->get_int("kdenlive:activeeffect");
if (current >= rootItem->childCount() - 1) {
srv->set("kdenlive:activeeffect", --current);
}
}
int currentRow = effect->row();
Fun undo = addItem_lambda(effect, parentId);
if (currentRow != rowCount() - 1) {
Fun move = moveItem_lambda(effect->getId(), currentRow, true);
PUSH_LAMBDA(move, undo);
}
Fun redo = removeItem_lambda(effect->getId());
bool res = redo();
if (res) {
int inFades = int(m_fadeIns.size());
int outFades = int(m_fadeOuts.size());
m_fadeIns.erase(effect->getId());
m_fadeOuts.erase(effect->getId());
inFades = int(m_fadeIns.size()) - inFades;
outFades = int(m_fadeOuts.size()) - outFades;
QString effectName = EffectsRepository::get()->getName(effect->getAssetId());
Fun update = [this, current, inFades, outFades]() {
// Required to build the effect view
if (current < 0 || rowCount() == 0) {
// Stack is now empty
emit dataChanged(QModelIndex(), QModelIndex(), {});
} else {
QVector roles = {TimelineModel::EffectNamesRole};
if (inFades < 0) {
roles << TimelineModel::FadeInRole;
}
if (outFades < 0) {
roles << TimelineModel::FadeOutRole;
}
qDebug() << "// EMITTING UNDO DATA CHANGE: " << roles;
emit dataChanged(QModelIndex(), QModelIndex(), roles);
}
// TODO: only update if effect is fade or keyframe
/*if (inFades < 0) {
pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
} else if (outFades < 0) {
pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
}*/
pCore->updateItemKeyframes(m_ownerId);
return true;
};
Fun update2 = [this, inFades, outFades]() {
// Required to build the effect view
QVector roles = {TimelineModel::EffectNamesRole};
// TODO: only update if effect is fade or keyframe
if (inFades < 0) {
roles << TimelineModel::FadeInRole;
} else if (outFades < 0) {
roles << TimelineModel::FadeOutRole;
}
qDebug() << "// EMITTING REDO DATA CHANGE: " << roles;
emit dataChanged(QModelIndex(), QModelIndex(), roles);
pCore->updateItemKeyframes(m_ownerId);
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update2, undo);
PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName));
} else {
qDebug() << "..........FAILED EFFECT DELETION";
}
}
bool EffectStackModel::copyXmlEffect(QDomElement effect)
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
bool result = fromXml(effect, undo, redo);
if (result) {
PUSH_UNDO(undo, redo, i18n("Copy effect"));
}
return result;
}
QDomElement EffectStackModel::toXml(QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("effects"));
int currentIn = pCore->getItemIn(m_ownerId);
container.setAttribute(QStringLiteral("parentIn"), currentIn);
for (int i = 0; i < rootItem->childCount(); ++i) {
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i));
QDomElement sub = document.createElement(QStringLiteral("effect"));
sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
int filterIn = sourceEffect->filter().get_int("in");
int filterOut = sourceEffect->filter().get_int("out");
if (filterOut > filterIn) {
sub.setAttribute(QStringLiteral("in"), filterIn);
sub.setAttribute(QStringLiteral("out"), filterOut);
}
QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
for (const QString ¶m : passProps) {
int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
if (paramVal > 0) {
Xml::setXmlProperty(sub, param, QString::number(paramVal));
}
}
QVector> params = sourceEffect->getAllParameters();
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble()));
} else {
Xml::setXmlProperty(sub, param.first, param.second.toString());
}
}
container.appendChild(sub);
}
return container;
}
QDomElement EffectStackModel::rowToXml(int row, QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("effects"));
if (row < 0 || row >= rootItem->childCount()) {
return container;
}
int currentIn = pCore->getItemIn(m_ownerId);
container.setAttribute(QStringLiteral("parentIn"), currentIn);
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(row));
QDomElement sub = document.createElement(QStringLiteral("effect"));
sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
int filterIn = sourceEffect->filter().get_int("in");
int filterOut = sourceEffect->filter().get_int("out");
if (filterOut > filterIn) {
sub.setAttribute(QStringLiteral("in"), filterIn);
sub.setAttribute(QStringLiteral("out"), filterOut);
}
QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
for (const QString ¶m : passProps) {
int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
if (paramVal > 0) {
Xml::setXmlProperty(sub, param, QString::number(paramVal));
}
}
QVector> params = sourceEffect->getAllParameters();
QLocale locale;
+ locale.setNumberOptions(QLocale::OmitGroupSeparator);
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble()));
} else {
Xml::setXmlProperty(sub, param.first, param.second.toString());
}
}
container.appendChild(sub);
return container;
}
bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo)
{
QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect"));
int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt();
qDebug()<<"// GOT PREVIOUS PARENTIN: "<getItemIn(m_ownerId);
PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement node = nodeList.item(i).toElement();
const QString effectId = node.attribute(QStringLiteral("id"));
EffectType type = EffectsRepository::get()->getType(effectId);
bool isAudioEffect = type == EffectType::Audio || type == EffectType::CustomAudio;
if (isAudioEffect) {
if (state != PlaylistState::AudioOnly) {
continue;
}
} else if (state != PlaylistState::VideoOnly) {
continue;
}
bool effectEnabled = true;
if (Xml::hasXmlProperty(node, QLatin1String("disable"))) {
effectEnabled = Xml::getXmlProperty(node, QLatin1String("disable")).toInt() != 1;
}
auto effect = EffectItemModel::construct(effectId, shared_from_this(), effectEnabled);
const QString in = node.attribute(QStringLiteral("in"));
const QString out = node.attribute(QStringLiteral("out"));
if (!out.isEmpty()) {
effect->filter().set("in", in.toUtf8().constData());
effect->filter().set("out", out.toUtf8().constData());
}
QStringList keyframeParams = effect->getKeyframableParameters();
QVector> parameters;
QDomNodeList params = node.elementsByTagName(QStringLiteral("property"));
for (int j = 0; j < params.count(); j++) {
QDomElement pnode = params.item(j).toElement();
const QString pName = pnode.attribute(QStringLiteral("name"));
if (pName == QLatin1String("in") || pName == QLatin1String("out")) {
continue;
}
if (keyframeParams.contains(pName)) {
// This is a keyframable parameter, fix offset
QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn);
parameters.append(QPair(pName, QVariant(pValue)));
} else {
parameters.append(QPair(pName, QVariant(pnode.text())));
}
}
effect->setParameters(parameters);
Fun local_undo = removeItem_lambda(effect->getId());
// TODO the parent should probably not always be the root
Fun local_redo = addItem_lambda(effect, rootItem->getId());
effect->prepareKeyframes();
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
m_fadeIns.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
effect->filter().set("in", currentIn);
effect->filter().set("out", currentIn + duration);
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
int filterOut = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
effect->filter().set("in", filterOut - duration);
effect->filter().set("out", filterOut);
}
local_redo();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
if (true) {
Fun update = [this]() {
emit dataChanged(QModelIndex(), QModelIndex(), {});
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
}
return true;
}
bool EffectStackModel::copyEffect(const std::shared_ptr &sourceItem, PlaylistState::ClipState state)
{
QWriteLocker locker(&m_lock);
if (sourceItem->childCount() > 0) {
// TODO: group
return false;
}
bool audioEffect = sourceItem->isAudio();
if (audioEffect) {
if (state == PlaylistState::VideoOnly) {
// This effect cannot be used
return false;
}
} else if (state == PlaylistState::AudioOnly) {
return false;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(sourceItem);
const QString effectId = sourceEffect->getAssetId();
bool enabled = sourceEffect->isEnabled();
auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled);
effect->setParameters(sourceEffect->getAllParameters());
if (!enabled) {
effect->filter().set("disable", 1);
}
effect->filter().set("in", sourceEffect->filter().get_int("in"));
effect->filter().set("out", sourceEffect->filter().get_int("out"));
Fun local_undo = removeItem_lambda(effect->getId());
// TODO the parent should probably not always be the root
Fun local_redo = addItem_lambda(effect, rootItem->getId());
effect->prepareKeyframes();
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
QVector roles = {TimelineModel::EffectNamesRole};
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
m_fadeIns.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
int in = pCore->getItemIn(m_ownerId);
effect->filter().set("in", in);
effect->filter().set("out", in + duration);
roles << TimelineModel::FadeInRole;
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
effect->filter().set("in", out - duration);
effect->filter().set("out", out);
roles << TimelineModel::FadeOutRole;
}
bool res = local_redo();
if (res) {
Fun update = [this, roles]() {
emit dataChanged(QModelIndex(), QModelIndex(), roles);
return true;
};
}
return res;
}
bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent)
{
QWriteLocker locker(&m_lock);
auto effect = EffectItemModel::construct(effectId, shared_from_this());
PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
if (effect->isAudio()) {
if (state == PlaylistState::VideoOnly) {
// Cannot add effect to this clip
return false;
}
} else if (state == PlaylistState::AudioOnly) {
// Cannot add effect to this clip
return false;
}
Fun undo = removeItem_lambda(effect->getId());
// TODO the parent should probably not always be the root
Fun redo = addItem_lambda(effect, rootItem->getId());
effect->prepareKeyframes();
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
int currentActive = getActiveEffect();
if (makeCurrent) {
if (auto srvPtr = m_masterService.lock()) {
srvPtr->set("kdenlive:activeeffect", rowCount());
}
}
bool res = redo();
if (res) {
int inFades = 0;
int outFades = 0;
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
int duration = effect->filter().get_length() - 1;
int in = pCore->getItemIn(m_ownerId);
effect->filter().set("in", in);
effect->filter().set("out", in + duration);
inFades++;
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
/*int duration = effect->filter().get_length() - 1;
int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
effect->filter().set("in", out - duration);
effect->filter().set("out", out);*/
outFades++;
} else if (m_ownerId.first == ObjectType::TimelineTrack) {
effect->filter().set("out", pCore->getItemDuration(m_ownerId));
}
Fun update = [this, inFades, outFades]() {
// TODO: only update if effect is fade or keyframe
QVector roles = {TimelineModel::EffectNamesRole};
if (inFades > 0) {
roles << TimelineModel::FadeInRole;
} else if (outFades > 0) {
roles << TimelineModel::FadeOutRole;
}
pCore->updateItemKeyframes(m_ownerId);
emit dataChanged(QModelIndex(), QModelIndex(), roles);
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
PUSH_UNDO(undo, redo, i18n("Add effect %1", EffectsRepository::get()->getName(effectId)));
} else if (makeCurrent) {
if (auto srvPtr = m_masterService.lock()) {
srvPtr->set("kdenlive:activeeffect", currentActive);
}
}
return res;
}
bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo,
bool logUndo)
{
QWriteLocker locker(&m_lock);
const int fadeInDuration = getFadePosition(true);
const int fadeOutDuration = getFadePosition(false);
int out = newIn + duration;
for (const auto &leaf : rootItem->getLeaves()) {
std::shared_ptr