File indexing completed on 2024-05-05 04:52:42

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "keyframemodel.hpp"
0007 #include "../../bpoint.h"
0008 #include "core.h"
0009 #include "doc/docundostack.hpp"
0010 #include "macros.hpp"
0011 #include "profiles/profilemodel.hpp"
0012 #include "rotoscoping/rotohelper.hpp"
0013 #include "utils/qcolorutils.h"
0014 
0015 #include <QDebug>
0016 #include <QJsonDocument>
0017 #include <QLineF>
0018 #include <QSize>
0019 #include <mlt++/Mlt.h>
0020 #include <utility>
0021 
0022 // std::unordered_map and QHash could not be used here
0023 #ifdef USE_MLT_NEW_KEYFRAMES
0024 extern const QMap<KeyframeType, QString> KeyframeTypeName = {
0025     {KeyframeType::Linear, i18n("Linear")},
0026     {KeyframeType::Discrete, i18n("Discrete")},
0027     {KeyframeType::CurveSmooth, i18n("Smooth")},
0028     {KeyframeType::BounceIn, i18n("Bounce In")},
0029     {KeyframeType::BounceOut, i18n("Bounce Out")},
0030     {KeyframeType::CubicIn, i18n("Cubic In")},
0031     {KeyframeType::CubicOut, i18n("Cubic Out")},
0032     {KeyframeType::ExponentialIn, i18n("Exponential In")},
0033     {KeyframeType::ExponentialOut, i18n("Exponential Out")},
0034     {KeyframeType::CircularIn, i18n("Circular In")},
0035     {KeyframeType::CircularOut, i18n("Circular Out")},
0036     {KeyframeType::ElasticIn, i18n("Elastic In")},
0037     {KeyframeType::ElasticOut, i18n("Elastic Out")},
0038     {KeyframeType::Curve, i18n("Smooth (deprecated)")},
0039 };
0040 #else
0041 extern const QMap<KeyframeType, QString> KeyframeTypeName = {
0042     {KeyframeType::Linear, i18n("Linear")}, {KeyframeType::Discrete, i18n("Discrete")}, {KeyframeType::Curve, i18n("Smooth")}};
0043 #endif
0044 
0045 KeyframeModel::KeyframeModel(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack, int in, int out,
0046                              QObject *parent)
0047     : QAbstractListModel(parent)
0048     , m_model(std::move(model))
0049     , m_undoStack(std::move(undo_stack))
0050     , m_index(index)
0051     , m_lastData()
0052     , m_lock(QReadWriteLock::Recursive)
0053 {
0054     qDebug() << "Construct keyframemodel. Checking model:" << m_model.expired();
0055     if (auto ptr = m_model.lock()) {
0056         m_paramType = ptr->data(m_index, AssetParameterModel::TypeRole).value<ParamType>();
0057     }
0058     setup();
0059     refresh(in, out);
0060 }
0061 
0062 void KeyframeModel::setup()
0063 {
0064     // We connect the signals of the abstractitemmodel to a more generic one.
0065     connect(this, &KeyframeModel::columnsMoved, this, &KeyframeModel::modelChanged);
0066     connect(this, &KeyframeModel::columnsRemoved, this, &KeyframeModel::modelChanged);
0067     connect(this, &KeyframeModel::columnsInserted, this, &KeyframeModel::modelChanged);
0068     connect(this, &KeyframeModel::rowsMoved, this, &KeyframeModel::modelChanged);
0069     connect(this, &KeyframeModel::rowsRemoved, this, &KeyframeModel::modelChanged);
0070     connect(this, &KeyframeModel::rowsInserted, this, &KeyframeModel::modelChanged);
0071     connect(this, &KeyframeModel::modelReset, this, &KeyframeModel::modelChanged);
0072     connect(this, &KeyframeModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, const QVector<int> &roles) {
0073         QVector<int> notParamRoles = {SelectedRole, ActiveRole};
0074         if (roles.size() == 1 && notParamRoles.contains(roles.first())) {
0075             // Selection role changed, no need to update the keyframe parameters
0076             return;
0077         }
0078         Q_EMIT modelChanged();
0079     });
0080     connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
0081 }
0082 
0083 bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value, bool notify, Fun &undo, Fun &redo)
0084 {
0085     qDebug() << "ADD keyframe" << pos.frames(pCore->getCurrentFps()) << value << notify;
0086     QWriteLocker locker(&m_lock);
0087     Fun local_undo = []() { return true; };
0088     Fun local_redo = []() { return true; };
0089     if (m_keyframeList.count(pos) > 0) {
0090         qDebug() << "already there";
0091         if (std::pair<KeyframeType, QVariant>({type, value}) == m_keyframeList.at(pos)) {
0092             qDebug() << "nothing to do";
0093             return true; // nothing to do
0094         }
0095         // In this case we simply change the type and value
0096         KeyframeType oldType = m_keyframeList[pos].first;
0097         QVariant oldValue = m_keyframeList[pos].second;
0098         local_undo = updateKeyframe_lambda(pos, oldType, oldValue, notify);
0099         local_redo = updateKeyframe_lambda(pos, type, value, notify);
0100         if (local_redo()) {
0101             UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0102             return true;
0103         }
0104     } else {
0105         Fun redo_first = addKeyframe_lambda(pos, type, value, notify);
0106         if (redo_first()) {
0107             local_redo = addKeyframe_lambda(pos, type, value, true);
0108             local_undo = deleteKeyframe_lambda(pos, true);
0109             UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0110             return true;
0111         }
0112     }
0113     return false;
0114 }
0115 
0116 bool KeyframeModel::addKeyframe(int frame, double normalizedValue)
0117 {
0118     QVariant result = getNormalizedValue(normalizedValue);
0119     if (result.isValid()) {
0120         // TODO: Use default configurable kf type
0121         return addKeyframe(GenTime(frame, pCore->getCurrentFps()), KeyframeType::Linear, result);
0122     }
0123     return false;
0124 }
0125 
0126 bool KeyframeModel::addKeyframe(GenTime pos, KeyframeType type, QVariant value)
0127 {
0128     QWriteLocker locker(&m_lock);
0129     Fun undo = []() { return true; };
0130     Fun redo = []() { return true; };
0131 
0132     bool update = (m_keyframeList.count(pos) > 0);
0133     bool res = addKeyframe(pos, type, std::move(value), true, undo, redo);
0134     if (res) {
0135         PUSH_UNDO(undo, redo, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
0136     }
0137     return res;
0138 }
0139 
0140 bool KeyframeModel::removeKeyframe(GenTime pos, Fun &undo, Fun &redo, bool notify, bool updateSelection)
0141 {
0142     qDebug() << "Going to remove keyframe at " << pos.frames(pCore->getCurrentFps()) << " NOTIFY: " << notify;
0143     qDebug() << "before" << getAnimProperty();
0144     QWriteLocker locker(&m_lock);
0145     Q_ASSERT(m_keyframeList.count(pos) > 0);
0146     KeyframeType oldType = m_keyframeList[pos].first;
0147     QVariant oldValue = m_keyframeList[pos].second;
0148     Fun select_undo = []() { return true; };
0149     Fun select_redo = []() { return true; };
0150     if (updateSelection) {
0151         if (auto ptr = m_model.lock()) {
0152             if (!ptr->m_selectedKeyframes.isEmpty()) {
0153                 int ix = getIndexForPos(pos);
0154                 QVector<int> selection;
0155                 QVector<int> prevSelection = ptr->m_selectedKeyframes;
0156                 for (auto &kf : prevSelection) {
0157                     if (kf == ix) {
0158                         continue;
0159                     }
0160                     if (kf < ix) {
0161                         selection << kf;
0162                     } else {
0163                         selection << (kf - 1);
0164                     }
0165                 }
0166                 setActiveKeyframe(-1);
0167                 std::sort(selection.begin(), selection.end());
0168                 select_redo = [this, selection]() {
0169                     setSelectedKeyframes(selection);
0170                     return true;
0171                 };
0172                 select_undo = [this, prevSelection]() {
0173                     setSelectedKeyframes(prevSelection);
0174                     return true;
0175                 };
0176             }
0177         }
0178     }
0179     Fun redo_first = deleteKeyframe_lambda(pos, notify);
0180     if (redo_first()) {
0181         Fun local_undo = addKeyframe_lambda(pos, oldType, oldValue, notify);
0182         select_redo();
0183         qDebug() << "after" << getAnimProperty();
0184         UPDATE_UNDO_REDO(redo_first, local_undo, undo, redo);
0185         UPDATE_UNDO_REDO(select_redo, select_undo, undo, redo);
0186         return true;
0187     }
0188     return false;
0189 }
0190 
0191 bool KeyframeModel::duplicateKeyframe(GenTime srcPos, GenTime dstPos, Fun &undo, Fun &redo)
0192 {
0193     QWriteLocker locker(&m_lock);
0194     Q_ASSERT(m_keyframeList.count(srcPos) > 0);
0195     KeyframeType oldType = m_keyframeList[srcPos].first;
0196     QVariant oldValue = m_keyframeList[srcPos].second;
0197     Fun local_redo = addKeyframe_lambda(dstPos, oldType, oldValue, true);
0198     Fun local_undo = deleteKeyframe_lambda(dstPos, true);
0199     if (local_redo()) {
0200         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0201         return true;
0202     }
0203     return false;
0204 }
0205 
0206 bool KeyframeModel::removeKeyframe(int frame)
0207 {
0208     GenTime pos(frame, pCore->getCurrentFps());
0209     return removeKeyframe(pos);
0210 }
0211 
0212 bool KeyframeModel::removeKeyframe(GenTime pos)
0213 {
0214     QWriteLocker locker(&m_lock);
0215     Fun undo = []() { return true; };
0216     Fun redo = []() { return true; };
0217 
0218     if (m_keyframeList.count(pos) > 0 && m_keyframeList.find(pos) == m_keyframeList.begin()) {
0219         return false; // initial point must stay
0220     }
0221 
0222     bool res = removeKeyframe(pos, undo, redo);
0223     if (res) {
0224         PUSH_UNDO(undo, redo, i18n("Delete keyframe"));
0225     }
0226     return res;
0227 }
0228 
0229 GenTime KeyframeModel::getPosAtIndex(int ix) const
0230 {
0231     QList<GenTime> positions = getKeyframePos();
0232     std::sort(positions.begin(), positions.end());
0233     if (ix < 0 || ix >= positions.count()) {
0234         return GenTime();
0235     }
0236     return positions.at(ix);
0237 }
0238 
0239 bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, const QVariant &newVal, Fun &undo, Fun &redo, bool updateView)
0240 {
0241     qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
0242     QWriteLocker locker(&m_lock);
0243     // Check if we have several selected keyframes
0244     if (oldPos == pos) {
0245         if (!newVal.isValid()) {
0246             // no change
0247             return true;
0248         }
0249     }
0250     if (auto ptr = m_model.lock()) {
0251         if (ptr->m_selectedKeyframes.size() > 1) {
0252             // We have several selected keyframes, move them all
0253             double offset = 0.;
0254 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0255             if (newVal.isValid() && newVal.type() == QVariant::Double) {
0256 #else
0257             if (newVal.isValid() && newVal.typeId() == QMetaType::Double) {
0258 #endif
0259                 int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(oldPos)));
0260                 double oldVal = data(index(row), NormalizedValueRole).toDouble();
0261                 offset = newVal.toDouble() - oldVal;
0262             }
0263             QVector<GenTime> positions;
0264             for (auto &kf : ptr->m_selectedKeyframes) {
0265                 if (kf > 0) {
0266                     positions << getPosAtIndex(kf);
0267                 }
0268             }
0269             GenTime delta = pos - oldPos;
0270             if (pos > oldPos) {
0271                 // Moving right, reverse sort
0272                 std::sort(positions.rbegin(), positions.rend());
0273                 // Check max pos
0274                 bool ok = false;
0275                 GenTime test = positions.first();
0276                 auto next = getNextKeyframe(test, &ok);
0277                 if (ok) {
0278                     delta = qMin(delta, next.first - GenTime(1, pCore->getCurrentFps()) - test);
0279                 }
0280             } else {
0281                 // Moving left
0282                 std::sort(positions.begin(), positions.end());
0283                 // Check min pos
0284                 bool ok = false;
0285                 GenTime test = positions.first();
0286                 auto next = getPrevKeyframe(test, &ok);
0287                 if (ok) {
0288                     delta = qMax(delta, (next.first + GenTime(1, pCore->getCurrentFps())) - test);
0289                 }
0290             }
0291             if (delta == GenTime()) {
0292                 if (!newVal.isValid()) {
0293                     // no change
0294                     return true;
0295                 }
0296             }
0297             bool res = true;
0298             for (auto &p : positions) {
0299                 if (p == oldPos) {
0300                     res = res && moveOneKeyframe(oldPos, oldPos + delta, newVal, undo, redo, updateView);
0301                 } else {
0302                     if (!qFuzzyIsNull(offset)) {
0303                         // Calculate new value
0304                         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(p)));
0305                         double newVal2 = qBound(0., data(index(row), NormalizedValueRole).toDouble() + offset, 1.);
0306                         res = res && moveOneKeyframe(p, p + delta, newVal2, undo, redo, updateView);
0307                     } else {
0308                         res = res && moveOneKeyframe(p, p + delta, QVariant(), undo, redo, updateView);
0309                     }
0310                 }
0311             }
0312             return res;
0313         } else {
0314             // We have only one selected keyframe
0315             if (pos > oldPos) {
0316                 // Moving right
0317                 bool ok = false;
0318                 auto next = getNextKeyframe(oldPos, &ok);
0319                 if (ok) {
0320                     pos = qMin(pos, next.first - GenTime(1, pCore->getCurrentFps()));
0321                 }
0322             } else {
0323                 // Moving left
0324                 bool ok = false;
0325                 auto next = getPrevKeyframe(oldPos, &ok);
0326                 if (ok) {
0327                     pos = qMax(pos, next.first + GenTime(1, pCore->getCurrentFps()));
0328                 }
0329             }
0330             return moveOneKeyframe(oldPos, pos, newVal, undo, redo, updateView);
0331         }
0332     }
0333     return false;
0334 }
0335 
0336 bool KeyframeModel::moveOneKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, Fun &undo, Fun &redo, bool updateView)
0337 {
0338     qDebug() << "starting to move keyframe" << oldPos.frames(pCore->getCurrentFps()) << pos.frames(pCore->getCurrentFps());
0339     QWriteLocker locker(&m_lock);
0340     Q_ASSERT(m_keyframeList.count(oldPos) > 0);
0341     if (oldPos == pos) {
0342         if (!newVal.isValid()) {
0343             // no change
0344             return true;
0345         }
0346         if (m_paramType == ParamType::AnimatedRect) {
0347             return updateKeyframe(pos, newVal);
0348         }
0349         // Calculate real value from normalized
0350         QVariant result = getNormalizedValue(newVal.toDouble());
0351         return updateKeyframe(pos, result);
0352     }
0353     if (oldPos != pos && hasKeyframe(pos)) {
0354         // Move rejected, another keyframe is here
0355         qDebug() << "==== MOVE REJECTED!!";
0356         return false;
0357     }
0358     KeyframeType oldType = m_keyframeList[oldPos].first;
0359     QVariant oldValue = m_keyframeList[oldPos].second;
0360     Fun local_undo = []() { return true; };
0361     Fun local_redo = []() { return true; };
0362     qDebug() << getAnimProperty();
0363     // TODO: use the new Animation::key_set_frame to move a keyframe
0364     bool res = removeKeyframe(oldPos, local_undo, local_redo, true, false);
0365     qDebug() << "Move keyframe finished deletion:" << res;
0366     qDebug() << getAnimProperty();
0367     if (res) {
0368         if (m_paramType == ParamType::AnimatedRect) {
0369             if (!newVal.isValid()) {
0370                 newVal = oldValue;
0371             }
0372             res = addKeyframe(pos, oldType, newVal, updateView, local_undo, local_redo);
0373         } else if (newVal.isValid()) {
0374             QVariant result = getNormalizedValue(newVal.toDouble());
0375             if (result.isValid()) {
0376                 res = addKeyframe(pos, oldType, result, updateView, local_undo, local_redo);
0377             }
0378         } else {
0379             res = addKeyframe(pos, oldType, oldValue, updateView, local_undo, local_redo);
0380         }
0381         qDebug() << "Move keyframe finished insertion:" << res;
0382         qDebug() << getAnimProperty();
0383     }
0384     if (res) {
0385         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0386     } else {
0387         bool undone = local_undo();
0388         Q_ASSERT(undone);
0389     }
0390     return res;
0391 }
0392 
0393 bool KeyframeModel::moveKeyframe(int oldPos, int pos, bool logUndo)
0394 {
0395     GenTime oPos(oldPos, pCore->getCurrentFps());
0396     GenTime nPos(pos, pCore->getCurrentFps());
0397     return moveKeyframe(oPos, nPos, QVariant(), logUndo);
0398 }
0399 
0400 bool KeyframeModel::offsetKeyframes(int oldPos, int pos, bool logUndo)
0401 {
0402     if (oldPos == pos) return true;
0403     GenTime oldFrame(oldPos, pCore->getCurrentFps());
0404     Q_ASSERT(m_keyframeList.count(oldFrame) > 0);
0405     GenTime diff(pos - oldPos, pCore->getCurrentFps());
0406     QWriteLocker locker(&m_lock);
0407     Fun undo = []() { return true; };
0408     Fun redo = []() { return true; };
0409     QList<GenTime> times;
0410     for (const auto &m : m_keyframeList) {
0411         if (m.first < oldFrame) continue;
0412         times << m.first;
0413     }
0414     bool res = true;
0415     for (const auto &t : qAsConst(times)) {
0416         res &= moveKeyframe(t, t + diff, QVariant(), undo, redo);
0417     }
0418     if (res && logUndo) {
0419         PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframes"));
0420     }
0421     return res;
0422 }
0423 
0424 bool KeyframeModel::moveKeyframe(int oldPos, int pos, QVariant newVal)
0425 {
0426     GenTime oPos(oldPos, pCore->getCurrentFps());
0427     GenTime nPos(pos, pCore->getCurrentFps());
0428     return moveKeyframe(oPos, nPos, std::move(newVal), true);
0429 }
0430 
0431 bool KeyframeModel::moveKeyframe(GenTime oldPos, GenTime pos, QVariant newVal, bool logUndo)
0432 {
0433     QWriteLocker locker(&m_lock);
0434     Q_ASSERT(m_keyframeList.count(oldPos) > 0);
0435     if (oldPos == pos) return true;
0436     Fun undo = []() { return true; };
0437     Fun redo = []() { return true; };
0438     bool res = moveKeyframe(oldPos, pos, std::move(newVal), undo, redo);
0439     if (res && logUndo) {
0440         PUSH_UNDO(undo, redo, i18nc("@action", "Move keyframe"));
0441     }
0442     return res;
0443 }
0444 
0445 bool KeyframeModel::directUpdateKeyframe(GenTime pos, QVariant value, bool notify)
0446 {
0447     QWriteLocker locker(&m_lock);
0448     Q_ASSERT(m_keyframeList.count(pos) > 0);
0449     KeyframeType type = m_keyframeList[pos].first;
0450     auto operation = updateKeyframe_lambda(pos, type, std::move(value), notify);
0451     return operation();
0452 }
0453 
0454 bool KeyframeModel::updateKeyframe(GenTime pos, const QVariant &value, Fun &undo, Fun &redo, bool update)
0455 {
0456     QWriteLocker locker(&m_lock);
0457     Q_ASSERT(m_keyframeList.count(pos) > 0);
0458     KeyframeType type = m_keyframeList[pos].first;
0459     QVariant oldValue = m_keyframeList[pos].second;
0460     // Check if keyframe is different
0461     if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel) {
0462         if (qFuzzyCompare(oldValue.toDouble(), value.toDouble())) return true;
0463     }
0464     auto operation = updateKeyframe_lambda(pos, type, value, update);
0465     auto reverse = updateKeyframe_lambda(pos, type, oldValue, update);
0466     bool res = operation();
0467     if (res) {
0468         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
0469     }
0470     return res;
0471 }
0472 
0473 bool KeyframeModel::updateKeyframe(int pos, double newVal)
0474 {
0475     GenTime Pos(pos, pCore->getCurrentFps());
0476     if (auto ptr = m_model.lock()) {
0477         double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
0478         double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
0479         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
0480             min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
0481             max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
0482         }
0483         double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
0484         double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
0485         int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
0486         double realValue;
0487         if (logRole == -1) {
0488             // Logarythmic scale
0489             if (newVal >= 0.5) {
0490                 realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
0491             } else {
0492                 realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
0493             }
0494         } else {
0495             realValue = (newVal * (max - min) + min) / factor;
0496         }
0497         return updateKeyframe(Pos, realValue);
0498     }
0499     return false;
0500 }
0501 
0502 bool KeyframeModel::updateKeyframe(GenTime pos, QVariant value)
0503 {
0504     QWriteLocker locker(&m_lock);
0505     Q_ASSERT(m_keyframeList.count(pos) > 0);
0506 
0507     Fun undo = []() { return true; };
0508     Fun redo = []() { return true; };
0509     bool res = updateKeyframe(pos, value, undo, redo);
0510     if (res) {
0511         PUSH_UNDO(undo, redo, i18n("Update keyframe"));
0512     }
0513     return res;
0514 }
0515 
0516 KeyframeType convertFromMltType(mlt_keyframe_type type)
0517 {
0518     switch (type) {
0519     case mlt_keyframe_linear:
0520         return KeyframeType::Linear;
0521     case mlt_keyframe_discrete:
0522         return KeyframeType::Discrete;
0523 #ifdef USE_MLT_NEW_KEYFRAMES
0524     case mlt_keyframe_smooth_natural:
0525         return KeyframeType::CurveSmooth;
0526     case mlt_keyframe_bounce_in:
0527         return KeyframeType::BounceIn;
0528     case mlt_keyframe_bounce_out:
0529         return KeyframeType::BounceOut;
0530     case mlt_keyframe_cubic_in:
0531         return KeyframeType::CubicIn;
0532     case mlt_keyframe_cubic_out:
0533         return KeyframeType::CubicOut;
0534     case mlt_keyframe_exponential_in:
0535         return KeyframeType::ExponentialIn;
0536     case mlt_keyframe_exponential_out:
0537         return KeyframeType::ExponentialOut;
0538     case mlt_keyframe_circular_in:
0539         return KeyframeType::CircularIn;
0540     case mlt_keyframe_circular_out:
0541         return KeyframeType::CircularOut;
0542     case mlt_keyframe_elastic_in:
0543         return KeyframeType::ElasticIn;
0544     case mlt_keyframe_elastic_out:
0545         return KeyframeType::ElasticOut;
0546 #endif
0547     case mlt_keyframe_smooth:
0548         return KeyframeType::Curve;
0549     default:
0550         return KeyframeType::Linear;
0551     }
0552 }
0553 
0554 bool KeyframeModel::updateKeyframeType(GenTime pos, int type, Fun &undo, Fun &redo)
0555 {
0556     QWriteLocker locker(&m_lock);
0557     Q_ASSERT(m_keyframeList.count(pos) > 0);
0558     KeyframeType oldType = m_keyframeList[pos].first;
0559     KeyframeType newType = convertFromMltType(mlt_keyframe_type(type));
0560     QVariant value = m_keyframeList[pos].second;
0561     // Check if keyframe is different
0562     if (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel) {
0563         if (oldType == newType) return true;
0564     }
0565     auto operation = updateKeyframe_lambda(pos, newType, value, true);
0566     auto reverse = updateKeyframe_lambda(pos, oldType, value, true);
0567     bool res = operation();
0568     if (res) {
0569         UPDATE_UNDO_REDO(operation, reverse, undo, redo);
0570     }
0571     return res;
0572 }
0573 
0574 Fun KeyframeModel::updateKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
0575 {
0576     QWriteLocker locker(&m_lock);
0577     return [this, pos, type, value, notify]() {
0578         // qDebug() << "update lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
0579         Q_ASSERT(m_keyframeList.count(pos) > 0);
0580         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
0581         m_keyframeList[pos].first = type;
0582         m_keyframeList[pos].second = value;
0583         if (notify) Q_EMIT dataChanged(index(row), index(row), {ValueRole, NormalizedValueRole, TypeRole});
0584         return true;
0585     };
0586 }
0587 
0588 Fun KeyframeModel::addKeyframe_lambda(GenTime pos, KeyframeType type, const QVariant &value, bool notify)
0589 {
0590     QWriteLocker locker(&m_lock);
0591     return [this, notify, pos, type, value]() {
0592         qDebug() << "add lambda" << pos.frames(pCore->getCurrentFps()) << value << notify;
0593         Q_ASSERT(m_keyframeList.count(pos) == 0);
0594         // We determine the row of the newly added marker
0595         auto insertionIt = m_keyframeList.lower_bound(pos);
0596         int insertionRow = static_cast<int>(m_keyframeList.size());
0597         if (insertionIt != m_keyframeList.end()) {
0598             insertionRow = static_cast<int>(std::distance(m_keyframeList.begin(), insertionIt));
0599         }
0600         if (notify) beginInsertRows(QModelIndex(), insertionRow, insertionRow);
0601         m_keyframeList[pos].first = type;
0602         m_keyframeList[pos].second = value;
0603         if (notify) endInsertRows();
0604         return true;
0605     };
0606 }
0607 
0608 Fun KeyframeModel::deleteKeyframe_lambda(GenTime pos, bool notify)
0609 {
0610     QWriteLocker locker(&m_lock);
0611     return [this, pos, notify]() {
0612         qDebug() << "delete lambda" << pos.frames(pCore->getCurrentFps()) << notify;
0613         qDebug() << "before" << getAnimProperty();
0614         Q_ASSERT(m_keyframeList.count(pos) > 0);
0615         // Q_ASSERT(pos != GenTime()); // cannot delete initial point
0616         int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
0617         if (notify) beginRemoveRows(QModelIndex(), row, row);
0618         m_keyframeList.erase(pos);
0619         if (notify) endRemoveRows();
0620         qDebug() << "after" << getAnimProperty();
0621         return true;
0622     };
0623 }
0624 
0625 QHash<int, QByteArray> KeyframeModel::roleNames() const
0626 {
0627     QHash<int, QByteArray> roles;
0628     roles[PosRole] = "position";
0629     roles[FrameRole] = "frame";
0630     roles[TypeRole] = "type";
0631     roles[ValueRole] = "value";
0632     roles[SelectedRole] = "selected";
0633     roles[ActiveRole] = "active";
0634     roles[NormalizedValueRole] = "normalizedValue";
0635     roles[MoveOnlyRole] = "moveOnly";
0636     return roles;
0637 }
0638 
0639 QVariant KeyframeModel::data(const QModelIndex &index, int role) const
0640 {
0641     READ_LOCK();
0642     if (index.row() < 0 || index.row() >= static_cast<int>(m_keyframeList.size()) || !index.isValid()) {
0643         return QVariant();
0644     }
0645     auto it = m_keyframeList.begin();
0646     std::advance(it, index.row());
0647     switch (role) {
0648     case Qt::DisplayRole:
0649     case Qt::EditRole:
0650     case ValueRole:
0651         if (m_paramType == ParamType::Roto_spline) {
0652             return 0;
0653         }
0654         return it->second.second;
0655     case MoveOnlyRole: {
0656         if (m_paramType == ParamType::Roto_spline) {
0657             return true;
0658         }
0659         return false;
0660     }
0661     case NormalizedValueRole: {
0662         if (m_paramType == ParamType::AnimatedRect) {
0663             const QString &data = it->second.second.toString();
0664             bool ok;
0665             double converted = data.section(QLatin1Char(' '), -1).toDouble(&ok);
0666             if (!ok) {
0667                 qDebug() << "QLocale: Could not convert animated rect opacity" << data;
0668             }
0669             return converted;
0670         }
0671         if (m_paramType == ParamType::Roto_spline) {
0672             return 0.5;
0673         }
0674         double val = it->second.second.toDouble();
0675         if (auto ptr = m_model.lock()) {
0676             Q_ASSERT(m_index.isValid());
0677             double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
0678             double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
0679             if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
0680                 min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
0681                 max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
0682             }
0683             double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
0684             double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
0685             int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
0686             double linear = val * factor;
0687             if (logRole == -1) {
0688                 // Logarythmic scale
0689                 // transform current value to 0..1 scale
0690                 if (linear >= norm) {
0691                     double scaled = (linear - norm) / (max * factor - norm);
0692                     return 0.5 + pow(scaled, 0.6) * 0.5;
0693                 }
0694                 double scaled = (linear - norm) / (min * factor - norm);
0695                 // Log scale
0696                 return 0.5 - pow(scaled, 0.6) * 0.5;
0697             }
0698             return (linear - min) / (max - min);
0699         } else {
0700             qDebug() << "// CANNOT LOCK effect MODEL";
0701         }
0702         return 1;
0703     }
0704     case PosRole:
0705         return it->first.seconds();
0706     case FrameRole:
0707     case Qt::UserRole:
0708         return it->first.frames(pCore->getCurrentFps());
0709     case TypeRole:
0710         return QVariant::fromValue<KeyframeType>(it->second.first);
0711     case SelectedRole:
0712         if (auto ptr = m_model.lock()) {
0713             return ptr->m_selectedKeyframes.contains(index.row());
0714         }
0715         break;
0716     case ActiveRole:
0717         if (auto ptr = m_model.lock()) {
0718             return ptr->m_activeKeyframe == index.row();
0719         }
0720         break;
0721     }
0722     return QVariant();
0723 }
0724 
0725 int KeyframeModel::rowCount(const QModelIndex &parent) const
0726 {
0727     READ_LOCK();
0728     if (parent.isValid()) return 0;
0729     return static_cast<int>(m_keyframeList.size());
0730 }
0731 
0732 bool KeyframeModel::singleKeyframe() const
0733 {
0734     READ_LOCK();
0735     return m_keyframeList.size() <= 1;
0736 }
0737 
0738 Keyframe KeyframeModel::getKeyframe(const GenTime &pos, bool *ok) const
0739 {
0740     READ_LOCK();
0741     if (m_keyframeList.count(pos) == 0) {
0742         // return empty marker
0743         *ok = false;
0744         return {GenTime(), KeyframeType::Linear};
0745     }
0746     *ok = true;
0747     return {pos, m_keyframeList.at(pos).first};
0748 }
0749 
0750 Keyframe KeyframeModel::getNextKeyframe(const GenTime &pos, bool *ok) const
0751 {
0752     auto it = m_keyframeList.upper_bound(pos);
0753     if (it == m_keyframeList.end()) {
0754         // return empty marker
0755         *ok = false;
0756         return {GenTime(), KeyframeType::Linear};
0757     }
0758     *ok = true;
0759     return {(*it).first, (*it).second.first};
0760 }
0761 
0762 Keyframe KeyframeModel::getPrevKeyframe(const GenTime &pos, bool *ok) const
0763 {
0764     auto it = m_keyframeList.lower_bound(pos);
0765     if (it == m_keyframeList.begin()) {
0766         // return empty marker
0767         *ok = false;
0768         return {GenTime(), KeyframeType::Linear};
0769     }
0770     --it;
0771     *ok = true;
0772     return {(*it).first, (*it).second.first};
0773 }
0774 
0775 Keyframe KeyframeModel::getClosestKeyframe(const GenTime &pos, bool *ok) const
0776 {
0777     if (m_keyframeList.count(pos) > 0) {
0778         return getKeyframe(pos, ok);
0779     }
0780     bool ok1, ok2;
0781     auto next = getNextKeyframe(pos, &ok1);
0782     auto prev = getPrevKeyframe(pos, &ok2);
0783     *ok = ok1 || ok2;
0784     if (ok1 && ok2) {
0785         double fps = pCore->getCurrentFps();
0786         if (qAbs(next.first.frames(fps) - pos.frames(fps)) < qAbs(prev.first.frames(fps) - pos.frames(fps))) {
0787             return next;
0788         }
0789         return prev;
0790     } else if (ok1) {
0791         return next;
0792     } else if (ok2) {
0793         return prev;
0794     }
0795     // return empty marker
0796     return {GenTime(), KeyframeType::Linear};
0797 }
0798 
0799 bool KeyframeModel::hasKeyframe(int frame) const
0800 {
0801     return hasKeyframe(GenTime(frame, pCore->getCurrentFps()));
0802 }
0803 bool KeyframeModel::hasKeyframe(const GenTime &pos) const
0804 {
0805     READ_LOCK();
0806     return m_keyframeList.count(pos) > 0;
0807 }
0808 
0809 bool KeyframeModel::removeAllKeyframes(Fun &undo, Fun &redo)
0810 {
0811     QWriteLocker locker(&m_lock);
0812     Fun local_undo = []() { return true; };
0813     Fun local_redo = []() { return true; };
0814     int kfrCount = int(m_keyframeList.size()) - 1;
0815     // Clear selection
0816     if (auto ptr = m_model.lock()) {
0817         ptr->m_selectedKeyframes = {};
0818     }
0819     if (kfrCount <= 0) {
0820         // Nothing to do
0821         UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0822         return true;
0823     }
0824     // we trigger only one global remove/insertrow event
0825     Fun update_redo_start = [this, kfrCount]() {
0826         beginRemoveRows(QModelIndex(), 1, kfrCount);
0827         return true;
0828     };
0829     Fun update_redo_end = [this]() {
0830         endRemoveRows();
0831         return true;
0832     };
0833     Fun update_undo_start = [this, kfrCount]() {
0834         beginInsertRows(QModelIndex(), 1, kfrCount);
0835         return true;
0836     };
0837     Fun update_undo_end = [this]() {
0838         endInsertRows();
0839         return true;
0840     };
0841     PUSH_LAMBDA(update_redo_start, local_redo);
0842     PUSH_LAMBDA(update_undo_start, local_undo);
0843     QList<GenTime> all_pos = getKeyframePos();
0844     update_redo_start();
0845     bool res = true;
0846     bool first = true;
0847     for (const auto &p : qAsConst(all_pos)) {
0848         if (first) { // skip first point
0849             first = false;
0850             continue;
0851         }
0852         res = removeKeyframe(p, local_undo, local_redo, false);
0853         if (!res) {
0854             bool undone = local_undo();
0855             Q_ASSERT(undone);
0856             return false;
0857         }
0858     }
0859     update_redo_end();
0860     PUSH_LAMBDA(update_redo_end, local_redo);
0861     PUSH_LAMBDA(update_undo_end, local_undo);
0862     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
0863     return true;
0864 }
0865 
0866 bool KeyframeModel::removeAllKeyframes()
0867 {
0868     QWriteLocker locker(&m_lock);
0869     Fun undo = []() { return true; };
0870     Fun redo = []() { return true; };
0871     bool res = removeAllKeyframes(undo, redo);
0872     if (res) {
0873         PUSH_UNDO(undo, redo, i18n("Delete all keyframes"));
0874     }
0875     return res;
0876 }
0877 
0878 mlt_keyframe_type convertToMltType(KeyframeType type)
0879 {
0880     return static_cast<mlt_keyframe_type>(static_cast<int>(type));
0881 }
0882 
0883 QString KeyframeModel::getAnimProperty() const
0884 {
0885     if (m_paramType == ParamType::Roto_spline) {
0886         return getRotoProperty();
0887     }
0888     Mlt::Properties mlt_prop;
0889     if (auto ptr = m_model.lock()) {
0890         ptr->passProperties(mlt_prop);
0891     }
0892     int ix = 0;
0893     bool first = true;
0894     std::shared_ptr<Mlt::Animation> anim(nullptr);
0895     for (const auto &keyframe : m_keyframeList) {
0896         switch (m_paramType) {
0897         case ParamType::AnimatedRect:
0898         case ParamType::Color:
0899             mlt_prop.anim_set("key", keyframe.second.second.toString().toUtf8().constData(), keyframe.first.frames(pCore->getCurrentFps()));
0900             break;
0901         default:
0902             mlt_prop.anim_set("key", keyframe.second.second.toDouble(), keyframe.first.frames(pCore->getCurrentFps()));
0903             break;
0904         }
0905         if (first) {
0906             anim.reset(mlt_prop.get_anim("key"));
0907             first = false;
0908         }
0909         anim->key_set_type(ix, convertToMltType(keyframe.second.first));
0910         ix++;
0911     }
0912     QString ret;
0913     if (anim) {
0914         char *cut = anim->serialize_cut();
0915         ret = QString(cut);
0916         free(cut);
0917     }
0918     return ret;
0919 }
0920 
0921 QString KeyframeModel::getRotoProperty() const
0922 {
0923     QJsonDocument doc;
0924     if (auto ptr = m_model.lock()) {
0925         int in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
0926         int out = in + ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
0927         QVariantMap map;
0928         for (const auto &keyframe : m_keyframeList) {
0929             map.insert(QString::number(keyframe.first.frames(pCore->getCurrentFps())).rightJustified(int(log10(double(out))) + 1, '0'), keyframe.second.second);
0930         }
0931         doc = QJsonDocument::fromVariant(map);
0932     }
0933     return doc.toJson();
0934 }
0935 
0936 void KeyframeModel::parseAnimProperty(const QString &prop, int in, int out)
0937 {
0938     Fun undo = []() { return true; };
0939     Fun redo = []() { return true; };
0940     disconnect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
0941     removeAllKeyframes(undo, redo);
0942     bool useOpacity = true;
0943     Mlt::Properties mlt_prop;
0944     if (auto ptr = m_model.lock()) {
0945         if (out <= in) {
0946             in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
0947             out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
0948         }
0949         ptr->passProperties(mlt_prop);
0950         useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
0951     } else {
0952         qDebug() << "###################\n\n/// ERROR LOCKING MODEL!!! ";
0953     }
0954     mlt_prop.set("key", prop.toUtf8().constData());
0955     // This is a fake query to force the animation to be parsed
0956     (void)mlt_prop.anim_get_double("key", 0, out);
0957 
0958     Mlt::Animation anim = mlt_prop.get_animation("key");
0959 
0960     qDebug() << "Found" << anim.key_count() << ", OUT: " << out << ", animation properties: " << prop;
0961     bool useDefaultType = !prop.contains(QLatin1Char('='));
0962     for (int i = 0; i < anim.key_count(); ++i) {
0963         int frame;
0964         mlt_keyframe_type type;
0965         anim.key_get(i, frame, type);
0966         if (useDefaultType) {
0967             // TODO: use a default user defined type
0968             type = mlt_keyframe_linear;
0969         }
0970         QVariant value;
0971         switch (m_paramType) {
0972         case ParamType::AnimatedRect: {
0973             mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
0974             if (useOpacity) {
0975                 value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(rect.o, 0, 'f'));
0976             } else {
0977                 value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
0978             }
0979             break;
0980         }
0981         case ParamType::Color: {
0982             mlt_color mltColor = mlt_prop.anim_get_color("key", frame);
0983             QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
0984             value = QVariant(QColorUtils::colorToString(color, true));
0985             break;
0986         }
0987 
0988         default:
0989             value = QVariant(mlt_prop.anim_get_double("key", frame));
0990             break;
0991         }
0992         if (i == 0 && frame > in) {
0993             // Always add a keyframe at start pos
0994             addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
0995         } else if (frame == in && hasKeyframe(GenTime(in))) {
0996             // First keyframe already exists, adjust its value
0997             updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, true);
0998             continue;
0999         }
1000         addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, true, undo, redo);
1001     }
1002     connect(this, &KeyframeModel::modelChanged, this, &KeyframeModel::sendModification);
1003 }
1004 
1005 void KeyframeModel::resetAnimProperty(const QString &prop)
1006 {
1007     Fun undo = []() { return true; };
1008     Fun redo = []() { return true; };
1009 
1010     // Delete all existing keyframes
1011     QSignalBlocker bk(this);
1012     removeAllKeyframes(undo, redo);
1013 
1014     Mlt::Properties mlt_prop;
1015     int in = 0;
1016     bool useOpacity = true;
1017     if (auto ptr = m_model.lock()) {
1018         in = ptr->data(m_index, AssetParameterModel::ParentInRole).toInt();
1019         ptr->passProperties(mlt_prop);
1020         if (m_paramType == ParamType::AnimatedRect) {
1021             useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
1022         }
1023     }
1024     mlt_prop.set("key", prop.toUtf8().constData());
1025     // This is a fake query to force the animation to be parsed
1026     (void)mlt_prop.anim_get_int("key", 0, 0);
1027 
1028     Mlt::Animation anim = mlt_prop.get_animation("key");
1029 
1030     qDebug() << "Found" << anim.key_count() << "animation properties";
1031     for (int i = 0; i < anim.key_count(); ++i) {
1032         int frame;
1033         mlt_keyframe_type type;
1034         anim.key_get(i, frame, type);
1035         if (!prop.contains(QLatin1Char('='))) {
1036             // TODO: use a default user defined type
1037             type = mlt_keyframe_linear;
1038         }
1039         QVariant value;
1040         switch (m_paramType) {
1041         case ParamType::AnimatedRect: {
1042             mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
1043             if (useOpacity) {
1044                 value = QVariant(QStringLiteral("%1 %2 %3 %4 %5").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h).arg(QString::number(rect.o, 'f')));
1045             } else {
1046                 value = QVariant(QStringLiteral("%1 %2 %3 %4").arg(rect.x).arg(rect.y).arg(rect.w).arg(rect.h));
1047             }
1048             break;
1049         }
1050         default:
1051             value = QVariant(mlt_prop.anim_get_double("key", frame));
1052             break;
1053         }
1054         if (i == 0 && frame > in) {
1055             // Always add a keyframe at start pos
1056             addKeyframe(GenTime(in, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
1057         } else if (frame == in && hasKeyframe(GenTime(in))) {
1058             // First keyframe already exists, adjust its value
1059             updateKeyframe(GenTime(frame, pCore->getCurrentFps()), value, undo, redo, false);
1060             continue;
1061         }
1062         addKeyframe(GenTime(frame, pCore->getCurrentFps()), convertFromMltType(type), value, false, undo, redo);
1063     }
1064     QString effectName;
1065     if (auto ptr = m_model.lock()) {
1066         effectName = ptr->data(m_index, Qt::DisplayRole).toString();
1067     } else {
1068         effectName = i18n("effect");
1069     }
1070     Fun update_local = [this]() {
1071         Q_EMIT dataChanged(index(0), index(int(m_keyframeList.size())), {});
1072         return true;
1073     };
1074     update_local();
1075     PUSH_LAMBDA(update_local, undo);
1076     PUSH_LAMBDA(update_local, redo);
1077     PUSH_UNDO(undo, redo, i18n("Reset %1", effectName));
1078 }
1079 
1080 void KeyframeModel::parseRotoProperty(const QString &prop)
1081 {
1082     Fun undo = []() { return true; };
1083     Fun redo = []() { return true; };
1084 
1085     QJsonParseError jsonError;
1086     QJsonDocument doc = QJsonDocument::fromJson(prop.toUtf8(), &jsonError);
1087     QVariant data = doc.toVariant();
1088     if (data.canConvert<QVariantMap>()) {
1089         QMap<QString, QVariant> map = data.toMap();
1090         QMap<QString, QVariant>::const_iterator i = map.constBegin();
1091         while (i != map.constEnd()) {
1092             addKeyframe(GenTime(i.key().toInt(), pCore->getCurrentFps()), KeyframeType::Linear, i.value(), false, undo, redo);
1093             ++i;
1094         }
1095     }
1096 }
1097 
1098 QVariant KeyframeModel::getInterpolatedValue(int p) const
1099 {
1100     auto pos = GenTime(p, pCore->getCurrentFps());
1101     return getInterpolatedValue(pos);
1102 }
1103 
1104 QVariant KeyframeModel::updateInterpolated(const QVariant &interpValue, double val)
1105 {
1106     QStringList vals = interpValue.toString().split(QLatin1Char(' '));
1107     if (!vals.isEmpty()) {
1108         vals[vals.size() - 1] = QString::number(val, 'f');
1109     }
1110     return vals.join(QLatin1Char(' '));
1111 }
1112 
1113 QVariant KeyframeModel::getNormalizedValue(double newVal) const
1114 {
1115     if (auto ptr = m_model.lock()) {
1116         double min = ptr->data(m_index, AssetParameterModel::VisualMinRole).toDouble();
1117         double max = ptr->data(m_index, AssetParameterModel::VisualMaxRole).toDouble();
1118         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
1119             min = ptr->data(m_index, AssetParameterModel::MinRole).toDouble();
1120             max = ptr->data(m_index, AssetParameterModel::MaxRole).toDouble();
1121         }
1122         if (qFuzzyIsNull(min) && qFuzzyIsNull(max)) {
1123             min = 0.;
1124             max = 1.;
1125         }
1126         double factor = ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
1127         double norm = ptr->data(m_index, AssetParameterModel::DefaultRole).toDouble();
1128         int logRole = ptr->data(m_index, AssetParameterModel::ScaleRole).toInt();
1129         double realValue;
1130         if (logRole == -1) {
1131             // Logarythmic scale
1132             if (newVal >= 0.5) {
1133                 realValue = norm + pow(2 * (newVal - 0.5), 10.0 / 6) * (max / factor - norm);
1134             } else {
1135                 realValue = norm - pow(2 * (0.5 - newVal), 10.0 / 6) * (norm - min / factor);
1136             }
1137         } else {
1138             realValue = (newVal * (max - min) + min) / factor;
1139         }
1140         return QVariant(realValue);
1141     }
1142     return QVariant();
1143 }
1144 
1145 QVariant KeyframeModel::getInterpolatedValue(const GenTime &pos) const
1146 {
1147     if (m_keyframeList.count(pos) > 0) {
1148         return m_keyframeList.at(pos).second;
1149     }
1150     if (m_keyframeList.size() == 0) {
1151         return QVariant();
1152     }
1153     Mlt::Properties mlt_prop;
1154     QString animData;
1155     int out = 0;
1156     bool useOpacity = false;
1157     if (auto ptr = m_model.lock()) {
1158         ptr->passProperties(mlt_prop);
1159         out = ptr->data(m_index, AssetParameterModel::ParentDurationRole).toInt();
1160         useOpacity = ptr->data(m_index, AssetParameterModel::OpacityRole).toBool();
1161         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1162     }
1163 
1164     if (!animData.isEmpty() && (m_paramType == ParamType::KeyframeParam || m_paramType == ParamType::ColorWheel)) {
1165         mlt_prop.set("key", animData.toUtf8().constData());
1166         // This is a fake query to force the animation to be parsed
1167         (void)mlt_prop.anim_get_double("key", 0, out);
1168         return QVariant(mlt_prop.anim_get_double("key", pos.frames(pCore->getCurrentFps())));
1169     }
1170     if (!animData.isEmpty() && m_paramType == ParamType::AnimatedRect) {
1171         mlt_prop.set("key", animData.toUtf8().constData());
1172         // This is a fake query to force the animation to be parsed
1173         (void)mlt_prop.anim_get_double("key", 0, out);
1174         mlt_rect rect = mlt_prop.anim_get_rect("key", pos.frames(pCore->getCurrentFps()));
1175         QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
1176         if (useOpacity) {
1177             res.append(QStringLiteral(" %1").arg(QString::number(rect.o, 'f')));
1178         }
1179         return QVariant(res);
1180     }
1181     if (!animData.isEmpty() && m_paramType == ParamType::Color) {
1182         mlt_prop.set("key", animData.toUtf8().constData());
1183         // This is a fake query to force the animation to be parsed
1184         (void)mlt_prop.anim_get_double("key", 0, out);
1185         mlt_color mltColor = mlt_prop.anim_get_color("key", pos.frames(pCore->getCurrentFps()));
1186         QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
1187         return QVariant(QColorUtils::colorToString(color, true));
1188     }
1189     if (m_paramType == ParamType::Roto_spline) {
1190         // interpolate
1191         auto next = m_keyframeList.upper_bound(pos);
1192         if (next == m_keyframeList.cbegin()) {
1193             return (m_keyframeList.cbegin())->second.second;
1194         } else if (next == m_keyframeList.cend()) {
1195             auto it = m_keyframeList.cend();
1196             --it;
1197             return it->second.second;
1198         }
1199         auto prev = next;
1200         --prev;
1201 
1202         QSize frame = pCore->getCurrentFrameSize();
1203         QList<BPoint> p1 = RotoHelper::getPoints(prev->second.second, frame);
1204         QList<BPoint> p2 = RotoHelper::getPoints(next->second.second, frame);
1205         // relPos should be in [0,1]:
1206         // - equal to 0 on prev keyframe
1207         // - equal to 1 on next keyframe
1208         qreal relPos = 0;
1209         if (next->first != prev->first) {
1210             relPos = (pos.frames(pCore->getCurrentFps()) - prev->first.frames(pCore->getCurrentFps())) /
1211                      qreal(((next->first - prev->first).frames(pCore->getCurrentFps())));
1212         }
1213         int count = qMin(p1.count(), p2.count());
1214         QList<QVariant> vlist;
1215         for (int i = 0; i < count; ++i) {
1216             BPoint bp;
1217             QList<QVariant> pl;
1218             for (int j = 0; j < 3; ++j) {
1219                 if (p1.at(i)[j] != p2.at(i)[j]) {
1220                     bp[j] = QLineF(p1.at(i)[j], p2.at(i)[j]).pointAt(relPos);
1221                 } else {
1222                     bp[j] = p1.at(i)[j];
1223                 }
1224                 pl << QVariant(QList<QVariant>() << QVariant(bp[j].x() / frame.width()) << QVariant(bp[j].y() / frame.height()));
1225             }
1226             vlist << QVariant(pl);
1227         }
1228         return vlist;
1229     }
1230     return QVariant();
1231 }
1232 
1233 void KeyframeModel::sendModification()
1234 {
1235     if (auto ptr = m_model.lock()) {
1236         Q_ASSERT(m_index.isValid());
1237         QString name = ptr->data(m_index, AssetParameterModel::NameRole).toString();
1238         if (AssetParameterModel::isAnimated(m_paramType)) {
1239             m_lastData = getAnimProperty();
1240             ptr->setParameter(name, m_lastData, false, m_index);
1241         } else {
1242             Q_ASSERT(false); // Not implemented, TODO
1243         }
1244     }
1245 }
1246 
1247 QString KeyframeModel::realValue(double normalizedValue) const
1248 {
1249     double value = getNormalizedValue(normalizedValue).toDouble();
1250     if (auto ptr = m_model.lock()) {
1251         int decimals = ptr->data(m_index, AssetParameterModel::DecimalsRole).toInt();
1252         value *= ptr->data(m_index, AssetParameterModel::FactorRole).toDouble();
1253         QString result;
1254         if (decimals == 0) {
1255             if (m_paramType == ParamType::AnimatedRect) {
1256                 value = qRound(value * 100.);
1257             }
1258             // Fix rounding erros in double > int conversion
1259             if (value > 0.) {
1260                 value += 0.001;
1261             } else {
1262                 value -= 0.001;
1263             }
1264             result = QString::number(int(value));
1265         } else {
1266             result = QString::number(value, 'f', decimals);
1267         }
1268         result.append(ptr->data(m_index, AssetParameterModel::SuffixRole).toString());
1269         return result;
1270     }
1271     return QString::number(value);
1272 }
1273 
1274 void KeyframeModel::refresh(int in, int out)
1275 {
1276     Q_ASSERT(m_index.isValid());
1277     QString animData;
1278     if (auto ptr = m_model.lock()) {
1279         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1280     } else {
1281         qDebug() << "WARNING : unable to access keyframe's model";
1282         return;
1283     }
1284     if (animData == m_lastData) {
1285         // nothing to do
1286         // qDebug() << "// DATA WAS ALREADY PARSED, ABORTING REFRESH\n";
1287         return;
1288     }
1289     if (m_paramType == ParamType::Roto_spline) {
1290         parseRotoProperty(animData);
1291     } else if (AssetParameterModel::isAnimated(m_paramType)) {
1292         parseAnimProperty(animData, in, out);
1293     } else {
1294         // first, try to convert to double
1295         bool ok = false;
1296         double value = animData.toDouble(&ok);
1297         if (ok) {
1298             Fun undo = []() { return true; };
1299             Fun redo = []() { return true; };
1300             addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
1301         } else {
1302             Q_ASSERT(false); // Not implemented, TODO
1303         }
1304     }
1305     m_lastData = animData;
1306 }
1307 
1308 void KeyframeModel::reset()
1309 {
1310     Q_ASSERT(m_index.isValid());
1311     QString animData;
1312     if (auto ptr = m_model.lock()) {
1313         animData = ptr->data(m_index, AssetParameterModel::ValueRole).toString();
1314     } else {
1315         qDebug() << "WARNING : unable to access keyframe's model";
1316         return;
1317     }
1318     if (animData == m_lastData) {
1319         // nothing to do
1320         qDebug() << "// DATA WAS ALREADY PARSED, ABORTING\n_________________";
1321         return;
1322     }
1323     if (m_paramType == ParamType::Roto_spline) {
1324         // TODO: resetRotoProperty(animData);
1325     } else if (AssetParameterModel::isAnimated(m_paramType)) {
1326         qDebug() << "parsing keyframe" << animData;
1327         resetAnimProperty(animData);
1328     } else {
1329         // first, try to convert to double
1330         bool ok = false;
1331         double value = animData.toDouble(&ok);
1332         if (ok) {
1333             Fun undo = []() { return true; };
1334             Fun redo = []() { return true; };
1335             addKeyframe(GenTime(), KeyframeType::Linear, QVariant(value), false, undo, redo);
1336             PUSH_UNDO(undo, redo, i18n("Reset effect"));
1337             qDebug() << "KEYFRAME ADDED" << value;
1338         } else {
1339             Q_ASSERT(false); // Not implemented, TODO
1340         }
1341     }
1342     m_lastData = animData;
1343 }
1344 
1345 QList<QPoint> KeyframeModel::getRanges(const QString &animData, const std::shared_ptr<AssetParameterModel> &model)
1346 {
1347     Mlt::Properties mlt_prop;
1348     model->passProperties(mlt_prop);
1349     mlt_prop.set("key", animData.toUtf8().constData());
1350     // This is a fake query to force the animation to be parsed
1351     (void)mlt_prop.anim_get_int("key", 0, 0);
1352 
1353     Mlt::Animation anim = mlt_prop.get_animation("key");
1354     int frame;
1355     mlt_keyframe_type type;
1356     anim.key_get(0, frame, type);
1357     mlt_rect rect = mlt_prop.anim_get_rect("key", frame);
1358     QPoint pX(int(rect.x), int(rect.x));
1359     QPoint pY(int(rect.y), int(rect.y));
1360     QPoint pW(int(rect.w), int(rect.w));
1361     QPoint pH(int(rect.h), int(rect.h));
1362     QPoint pO(int(rect.o), int(rect.o));
1363     for (int i = 1; i < anim.key_count(); ++i) {
1364         anim.key_get(i, frame, type);
1365         if (!animData.contains(QLatin1Char('='))) {
1366             // TODO: use a default user defined type
1367             type = mlt_keyframe_linear;
1368         }
1369         rect = mlt_prop.anim_get_rect("key", frame);
1370         pX.setX(qMin(int(rect.x), pX.x()));
1371         pX.setY(qMax(int(rect.x), pX.y()));
1372         pY.setX(qMin(int(rect.y), pY.x()));
1373         pY.setY(qMax(int(rect.y), pY.y()));
1374         pW.setX(qMin(int(rect.w), pW.x()));
1375         pW.setY(qMax(int(rect.w), pW.y()));
1376         pH.setX(qMin(int(rect.h), pH.x()));
1377         pH.setY(qMax(int(rect.h), pH.y()));
1378         pO.setX(qMin(int(rect.o), pO.x()));
1379         pO.setY(qMax(int(rect.o), pO.y()));
1380         // 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)));
1381     }
1382     QList<QPoint> result{pX, pY, pW, pH, pO};
1383     return result;
1384 }
1385 
1386 std::shared_ptr<Mlt::Properties> KeyframeModel::getAnimation(std::shared_ptr<AssetParameterModel> model, const QString &animData, int duration)
1387 {
1388     std::shared_ptr<Mlt::Properties> mlt_prop(new Mlt::Properties());
1389     model->passProperties(*mlt_prop.get());
1390     mlt_prop->set("key", animData.toUtf8().constData());
1391     // This is a fake query to force the animation to be parsed
1392     (void)mlt_prop->anim_get_rect("key", 0, duration);
1393     return mlt_prop;
1394 }
1395 
1396 const QString KeyframeModel::getAnimationStringWithOffset(std::shared_ptr<AssetParameterModel> model, const QString &animData, int offset, int duration,
1397                                                           ParamType paramType, bool useOpacity)
1398 {
1399     Mlt::Properties mlt_prop;
1400     model->passProperties(mlt_prop);
1401     mlt_prop.set("key", animData.toUtf8().constData());
1402     // This is a fake query to force the animation to be parsed
1403     (void)mlt_prop.anim_get_rect("key", 0);
1404     Mlt::Animation anim = mlt_prop.get_animation("key");
1405     if (offset > 0) {
1406         for (int i = anim.key_count() - 1; i >= 0; --i) {
1407             int pos = anim.key_get_frame(i) + offset;
1408             anim.key_set_frame(i, pos);
1409         }
1410     } else if (offset < 0) {
1411         for (int i = 0; i < anim.key_count(); ++i) {
1412             int pos = anim.key_get_frame(i) + offset;
1413             if (pos >= 0) {
1414                 anim.key_set_frame(i, pos);
1415             }
1416         }
1417     }
1418     // If last key is beyond duration, add new keyframe at end
1419     int lastPos = anim.key_get_frame(anim.key_count() - 1);
1420     if (lastPos > duration) {
1421         QVariant value;
1422         switch (paramType) {
1423         case ParamType::AnimatedRect: {
1424             mlt_rect rect = mlt_prop.anim_get_rect("key", duration);
1425             QString res = QStringLiteral("%1 %2 %3 %4").arg(int(rect.x)).arg(int(rect.y)).arg(int(rect.w)).arg(int(rect.h));
1426             if (useOpacity) {
1427                 res.append(QStringLiteral(" %1").arg(QString::number(rect.o, 'f')));
1428             }
1429             value = QVariant(res);
1430             break;
1431         }
1432         case ParamType::Color: {
1433             mlt_color mltColor = mlt_prop.anim_get_color("key", duration);
1434             QColor color(mltColor.r, mltColor.g, mltColor.b, mltColor.a);
1435             value = QVariant(QColorUtils::colorToString(color, true));
1436             break;
1437         }
1438         default:
1439             value = QVariant(mlt_prop.anim_get_double("key", duration));
1440             break;
1441         }
1442         mlt_prop.anim_set("key", value.toString().toUtf8().constData(), duration);
1443         // Ensure the added keyframe uses the same type as last one
1444         mlt_keyframe_type lastType = anim.key_get_type(anim.key_count() - 1);
1445         for (int i = 0; i < anim.key_count(); i++) {
1446             if (anim.key_get_frame(i) == duration) {
1447                 anim.key_set_type(i, lastType);
1448                 break;
1449             }
1450         }
1451     }
1452     return qstrdup(anim.serialize_cut(0, duration));
1453 }
1454 
1455 const QString KeyframeModel::getIconByKeyframeType(KeyframeType type){
1456     switch (type) {
1457     case KeyframeType::Linear:
1458         return QStringLiteral("linear");
1459     case KeyframeType::Discrete:
1460         return QStringLiteral("discrete");
1461     case KeyframeType::Curve:
1462         return QStringLiteral("smooth");
1463 #ifdef USE_MLT_NEW_KEYFRAMES
1464     case KeyframeType::CurveSmooth:
1465         return QStringLiteral("smooth");
1466 #endif
1467     default:
1468         return QStringLiteral("favorite");
1469     }
1470 }
1471 QList<GenTime> KeyframeModel::getKeyframePos() const
1472 {
1473     QList<GenTime> all_pos;
1474     for (const auto &m : m_keyframeList) {
1475         all_pos.push_back(m.first);
1476     }
1477     return all_pos;
1478 }
1479 
1480 bool KeyframeModel::removeNextKeyframes(GenTime pos, Fun &undo, Fun &redo)
1481 {
1482     QWriteLocker locker(&m_lock);
1483     std::vector<GenTime> all_pos;
1484     Fun local_undo = []() { return true; };
1485     Fun local_redo = []() { return true; };
1486     for (const auto &m : m_keyframeList) {
1487         if (m.first >= pos && m.first != m_keyframeList.begin()->first) {
1488             all_pos.push_back(m.first);
1489         }
1490     }
1491     std::sort(all_pos.begin(), all_pos.end());
1492     int kfrCount = int(m_keyframeList.size());
1493     // Remove deleted keyframes from selection
1494     if (auto ptr = m_model.lock()) {
1495         QVector<int> selection;
1496         for (auto &ix : ptr->m_selectedKeyframes) {
1497             if (ix < kfrCount) {
1498                 selection << ix;
1499             }
1500         }
1501         ptr->m_selectedKeyframes = selection;
1502     }
1503     // we trigger only one global remove/insertrow event
1504     int row = static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(all_pos.front())));
1505     Fun update_redo_start = [this, row, kfrCount]() {
1506         beginRemoveRows(QModelIndex(), row, kfrCount - 1);
1507         return true;
1508     };
1509     Fun update_redo_end = [this]() {
1510         endRemoveRows();
1511         return true;
1512     };
1513     Fun update_undo_start = [this, row, kfrCount]() {
1514         beginInsertRows(QModelIndex(), row, kfrCount - 1);
1515         return true;
1516     };
1517     Fun update_undo_end = [this]() {
1518         endInsertRows();
1519         return true;
1520     };
1521     PUSH_LAMBDA(update_redo_start, local_redo);
1522     PUSH_LAMBDA(update_undo_start, local_undo);
1523     update_redo_start();
1524     for (const auto &p : all_pos) {
1525         bool res = removeKeyframe(p, local_undo, local_redo, false);
1526         if (!res) {
1527             bool undone = local_undo();
1528             Q_ASSERT(undone);
1529             return false;
1530         }
1531     }
1532     update_redo_end();
1533     PUSH_LAMBDA(update_redo_end, local_redo);
1534     PUSH_LAMBDA(update_undo_end, local_undo);
1535     UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
1536     return true;
1537 }
1538 
1539 void KeyframeModel::setSelectedKeyframe(int ix, bool add)
1540 {
1541     QVector<int> previous;
1542     if (auto ptr = m_model.lock()) {
1543         if (add) {
1544             if (ptr->m_selectedKeyframes.contains(ix)) {
1545                 // remove from selection
1546                 ptr->m_selectedKeyframes.removeAll(ix);
1547             } else {
1548                 ptr->m_selectedKeyframes << ix;
1549             }
1550         } else {
1551             previous = ptr->m_selectedKeyframes;
1552             ptr->m_selectedKeyframes = {ix};
1553         }
1554     }
1555     if (!add) {
1556         for (auto &ix2 : previous) {
1557             if (ix2 > -1) {
1558                 Q_EMIT requestModelUpdate(index(ix2), index(ix2), {SelectedRole});
1559             }
1560         }
1561     }
1562     if (ix > -1) {
1563         Q_EMIT requestModelUpdate(index(ix), index(ix), {SelectedRole});
1564     }
1565 }
1566 
1567 void KeyframeModel::setSelectedKeyframes(QVector<int> selection)
1568 {
1569     QVector<int> previous;
1570     selection.removeAll(-1);
1571     std::sort(selection.begin(), selection.end());
1572     if (auto ptr = m_model.lock()) {
1573         previous = ptr->m_selectedKeyframes;
1574         ptr->m_selectedKeyframes = selection;
1575     }
1576     if (!selection.isEmpty()) {
1577         Q_EMIT requestModelUpdate(index(selection.first()), index(selection.last()), {SelectedRole});
1578     }
1579     for (auto &ix : previous) {
1580         if (ix > -1 && !selection.contains(ix)) {
1581             Q_EMIT requestModelUpdate(index(ix), index(ix), {SelectedRole});
1582         }
1583     }
1584 }
1585 
1586 int KeyframeModel::activeKeyframe() const
1587 {
1588     if (auto ptr = m_model.lock()) {
1589         return ptr->m_activeKeyframe;
1590     }
1591     return -1;
1592 }
1593 
1594 void KeyframeModel::setActiveKeyframe(int ix)
1595 {
1596     int oldActive = -1;
1597     if (auto ptr = m_model.lock()) {
1598         oldActive = ptr->m_activeKeyframe;
1599         if (oldActive == ix) {
1600             // Keyframe already active
1601             return;
1602         }
1603         ptr->m_activeKeyframe = ix;
1604     }
1605     Q_EMIT requestModelUpdate(index(ix), index(ix), {ActiveRole});
1606     if (oldActive > -1) {
1607         Q_EMIT requestModelUpdate(index(oldActive), index(oldActive), {ActiveRole});
1608     }
1609 }
1610 
1611 int KeyframeModel::getIndexForPos(const GenTime pos) const
1612 {
1613     if (m_keyframeList.count(pos) == 0) {
1614         return -1;
1615     }
1616     return static_cast<int>(std::distance(m_keyframeList.begin(), m_keyframeList.find(pos)));
1617 }