File indexing completed on 2024-05-12 04:52:53

0001 /*
0002     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "klocalizedstring.h"
0007 #include <QCheckBox>
0008 #include <QComboBox>
0009 #include <QDialogButtonBox>
0010 #include <QHBoxLayout>
0011 #include <QJsonArray>
0012 #include <QJsonDocument>
0013 #include <QJsonObject>
0014 #include <QPainter>
0015 #include <QSpinBox>
0016 #include <QVBoxLayout>
0017 #include <utility>
0018 
0019 #include "assets/keyframes/view/keyframeview.hpp"
0020 #include "core.h"
0021 #include "doc/kdenlivedoc.h"
0022 #include "kdenlivesettings.h"
0023 #include "keyframeimport.h"
0024 #include "monitor/monitor.h"
0025 #include "profiles/profilemodel.hpp"
0026 #include "widgets/positionwidget.h"
0027 #include <macros.hpp>
0028 
0029 #include "mlt++/MltAnimation.h"
0030 #include "mlt++/MltProperties.h"
0031 
0032 KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr<AssetParameterModel> model, const QList<QPersistentModelIndex> &indexes, int parentIn,
0033                                int parentDuration, QWidget *parent)
0034     : QDialog(parent)
0035     , m_model(std::move(model))
0036     , m_indexes(indexes)
0037     , m_supportsAnim(false)
0038     , m_previewLabel(nullptr)
0039     , m_sourceCombo(nullptr)
0040     , m_targetCombo(nullptr)
0041     , m_isReady(false)
0042 {
0043     setAttribute(Qt::WA_DeleteOnClose);
0044     auto *lay = new QVBoxLayout(this);
0045     auto *l1 = new QHBoxLayout;
0046     QLabel *lab = new QLabel(i18n("Data to import:"), this);
0047     l1->addWidget(lab);
0048 
0049     m_dataCombo = new QComboBox(this);
0050     l1->addWidget(m_dataCombo);
0051     l1->addStretch(10);
0052     lay->addLayout(l1);
0053     int in = -1;
0054     int out = -1;
0055     std::pair<int, int> sourceInOut = {-1, -1};
0056     // Set  up data
0057     auto json = QJsonDocument::fromJson(animData.toUtf8());
0058     if (!json.isArray()) {
0059         qDebug() << "Error : Json file should be an array";
0060         // Try to build data from a single value
0061         QJsonArray list;
0062         QJsonObject currentParam;
0063         currentParam.insert(QLatin1String("name"), QStringLiteral("data"));
0064         currentParam.insert(QLatin1String("value"), animData);
0065         bool ok;
0066         QString firstFrame = animData.section(QLatin1Char('='), 0, 0);
0067         in = firstFrame.toInt(&ok);
0068         if (!ok) {
0069             firstFrame.chop(1);
0070             in = firstFrame.toInt(&ok);
0071         }
0072         if (ok && sourceInOut.first == -1) {
0073             sourceInOut.first = in;
0074         }
0075         QString lastFrame = animData.section(QLatin1Char(';'), -1);
0076         lastFrame = lastFrame.section(QLatin1Char('='), 0, 0);
0077         int lastPos = lastFrame.toInt(&ok);
0078         if (!ok) {
0079             lastFrame.chop(1);
0080             lastPos = lastFrame.toInt(&ok);
0081         }
0082         if (ok && sourceInOut.second == -1) {
0083             sourceInOut.second = lastPos;
0084         }
0085         QString first = animData.section(QLatin1Char('='), 1, 1);
0086         if (!first.isEmpty()) {
0087             int spaces = first.count(QLatin1Char(' '));
0088             switch (spaces) {
0089             case 0:
0090                 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::Hidden)));
0091                 break;
0092             default:
0093                 currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
0094                 break;
0095             }
0096             // currentParam.insert(QLatin1String("min"), QJsonValue(min));
0097             // currentParam.insert(QLatin1String("max"), QJsonValue(max));
0098             list.push_back(currentParam);
0099             json = QJsonDocument(list);
0100         }
0101     }
0102     if (!json.isArray()) {
0103         qDebug() << "Error : Json file should be an array";
0104         return;
0105     }
0106     auto list = json.array();
0107     int ix = 0;
0108     for (const auto &entry : qAsConst(list)) {
0109         if (!entry.isObject()) {
0110             qDebug() << "Warning : Skipping invalid marker data";
0111             continue;
0112         }
0113         auto entryObj = entry.toObject();
0114         if (!entryObj.contains(QLatin1String("name"))) {
0115             qDebug() << "Warning : Skipping invalid marker data (does not contain name)";
0116             continue;
0117         }
0118         QString name = entryObj[QLatin1String("name")].toString();
0119         QString displayName = entryObj[QLatin1String("DisplayName")].toString();
0120         if (displayName.isEmpty()) {
0121             displayName = name;
0122         }
0123         QString value = entryObj[QLatin1String("value")].toString();
0124         int type = entryObj[QLatin1String("type")].toInt(0);
0125         double min = entryObj[QLatin1String("min")].toDouble(0);
0126         double max = entryObj[QLatin1String("max")].toDouble(0);
0127         if (sourceInOut.first == -1) {
0128             sourceInOut.first = entryObj[QLatin1String("in")].toInt(0);
0129         }
0130         if (sourceInOut.second == -1) {
0131             sourceInOut.second = entryObj[QLatin1String("out")].toInt(0);
0132         }
0133         if (in == -1) {
0134             in = entryObj[QLatin1String("in")].toInt(0);
0135         }
0136         if (out == -1) {
0137             out = entryObj[QLatin1String("out")].toInt(0);
0138         }
0139         bool opacity = entryObj[QLatin1String("opacity")].toBool(true);
0140         m_dataCombo->insertItem(ix, displayName);
0141         m_dataCombo->setItemData(ix, value, ValueRole);
0142         m_dataCombo->setItemData(ix, type, TypeRole);
0143         m_dataCombo->setItemData(ix, min, MinRole);
0144         m_dataCombo->setItemData(ix, max, MaxRole);
0145         m_dataCombo->setItemData(ix, opacity, OpacityRole);
0146         ix++;
0147     }
0148     // If we have several available parameters, put the geometry first
0149     if (m_dataCombo->count() > 1) {
0150         for (int indx = 0; indx < m_dataCombo->count(); indx++) {
0151             auto type = m_dataCombo->itemData(indx, TypeRole).value<ParamType>();
0152             if (type == ParamType::AnimatedRect) {
0153                 m_dataCombo->setCurrentIndex(indx);
0154                 break;
0155             }
0156         }
0157     }
0158     m_previewLabel = new QLabel(this);
0159     m_previewLabel->setMinimumSize(100, 150);
0160     m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0161     m_previewLabel->setScaledContents(true);
0162 
0163     lay->addWidget(m_previewLabel);
0164     // Zone in / out
0165     in = qMax(0, in);
0166     out = qMin(sourceInOut.first + parentDuration - 1, sourceInOut.second);
0167     m_inPoint = new PositionWidget(i18n("In"), in, 0, out, QString(), this);
0168     connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
0169     lay->addWidget(m_inPoint);
0170     m_outPoint = new PositionWidget(i18n("Duration"), out, in, out, QString(), this);
0171     connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
0172     lay->addWidget(m_outPoint);
0173 
0174     // Output offset
0175     int clipIn = parentIn;
0176     m_offsetPoint = new PositionWidget(i18n("Time offset:"), clipIn, 0, clipIn + parentDuration, "", this);
0177     lay->addWidget(m_offsetPoint);
0178 
0179     int count = 0;
0180     // Check what kind of parameters are in our target
0181     for (const QPersistentModelIndex &idx : indexes) {
0182         auto type = m_model->data(idx, AssetParameterModel::TypeRole).value<ParamType>();
0183         if (type == ParamType::KeyframeParam) {
0184             m_simpleTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
0185             QString name(m_model->data(idx, AssetParameterModel::NameRole).toString());
0186             if (name.contains("Position X") || name.contains("Position Y") || name.contains("Size X") || name.contains("Size Y")) {
0187                 count++;
0188             }
0189         } else if (type == ParamType::Roto_spline) {
0190             m_simpleTargets.insert(i18n("Rotoscoping shape"), idx);
0191         } else if (type == ParamType::AnimatedRect) {
0192             m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
0193         }
0194     }
0195 
0196     l1 = new QHBoxLayout;
0197     m_targetCombo = new QComboBox(this);
0198     m_sourceCombo = new QComboBox(this);
0199     /*ix = 0;
0200     if (!m_geometryTargets.isEmpty()) {
0201         m_sourceCombo->insertItem(ix, i18n("Geometry"));
0202         m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole);
0203         ix++;
0204         m_sourceCombo->insertItem(ix, i18n("Position"));
0205         m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole);
0206         ix++;
0207     }
0208     if (!m_simpleTargets.isEmpty()) {
0209         m_sourceCombo->insertItem(ix, i18n("X"));
0210         m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole);
0211         ix++;
0212         m_sourceCombo->insertItem(ix, i18n("Y"));
0213         m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole);
0214         ix++;
0215         m_sourceCombo->insertItem(ix, i18n("Width"));
0216         m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole);
0217         ix++;
0218         m_sourceCombo->insertItem(ix, i18n("Height"));
0219         m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole);
0220         ix++;
0221     }*/
0222     connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateRange);
0223     connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
0224     m_alignSourceCombo = new QComboBox(this);
0225     m_alignSourceCombo->addItem(i18n("Top left"), 0);
0226     m_alignSourceCombo->addItem(i18n("Top center"), 1);
0227     m_alignSourceCombo->addItem(i18n("Top right"), 2);
0228     m_alignSourceCombo->addItem(i18n("Left center"), 3);
0229     m_alignSourceCombo->addItem(i18n("Center"), 4);
0230     m_alignSourceCombo->addItem(i18n("Right center"), 5);
0231     m_alignSourceCombo->addItem(i18n("Bottom left"), 6);
0232     m_alignSourceCombo->addItem(i18n("Bottom center"), 7);
0233     m_alignSourceCombo->addItem(i18n("Bottom right"), 8);
0234     m_alignTargetCombo = new QComboBox(this);
0235     m_alignTargetCombo->addItem(i18n("Top left"), 0);
0236     m_alignTargetCombo->addItem(i18n("Top center"), 1);
0237     m_alignTargetCombo->addItem(i18n("Top right"), 2);
0238     m_alignTargetCombo->addItem(i18n("Left center"), 3);
0239     m_alignTargetCombo->addItem(i18n("Center"), 4);
0240     m_alignTargetCombo->addItem(i18n("Right center"), 5);
0241     m_alignTargetCombo->addItem(i18n("Bottom left"), 6);
0242     m_alignTargetCombo->addItem(i18n("Bottom center"), 7);
0243     m_alignTargetCombo->addItem(i18n("Bottom right"), 8);
0244     connect(m_alignSourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
0245     connect(m_alignTargetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
0246     lab = new QLabel(i18n("Map "), this);
0247     QLabel *lab2 = new QLabel(i18n(" to "), this);
0248     l1->addWidget(lab);
0249     l1->addWidget(m_sourceCombo);
0250     l1->addWidget(m_alignSourceCombo);
0251     l1->addWidget(lab2);
0252     l1->addWidget(m_targetCombo);
0253     l1->addWidget(m_alignTargetCombo);
0254     ix = 0;
0255     QMap<QString, QModelIndex>::const_iterator j = m_geometryTargets.constBegin();
0256     while (j != m_geometryTargets.constEnd()) {
0257         m_targetCombo->insertItem(ix, j.key());
0258         m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
0259         m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
0260         ++j;
0261         ix++;
0262     }
0263     if (count == 4) {
0264         // add an option to map to a a fake rectangle (pretend position params are a mlt rect)
0265         m_targetCombo->insertItem(ix, i18n("Rectangle"));
0266     }
0267     ix = 0;
0268     j = m_simpleTargets.constBegin();
0269     while (j != m_simpleTargets.constEnd()) {
0270         m_targetCombo->insertItem(ix, j.key());
0271         m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
0272         m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
0273         ++j;
0274         ix++;
0275     }
0276     if (m_simpleTargets.count() + m_geometryTargets.count() > 1) {
0277         // Target contains several animatable parameters, propose choice
0278     }
0279     lay->addLayout(l1);
0280 
0281     m_offsetX.setRange(-pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->width());
0282     m_offsetY.setRange(-pCore->getCurrentProfile()->height(), pCore->getCurrentProfile()->height());
0283     connect(&m_offsetX, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
0284     connect(&m_offsetY, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
0285 
0286     // Destination range
0287     l1 = new QHBoxLayout;
0288     lab = new QLabel(i18n("Position offset:"), this);
0289     l1->addWidget(lab);
0290     l1->addWidget(&m_offsetX);
0291     l1->addWidget(&m_offsetY);
0292     lay->addLayout(l1);
0293 
0294     // Source range
0295     m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this);
0296     lay->addWidget(m_sourceRangeLabel);
0297 
0298     // update range info
0299 
0300     connect(m_targetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateDestinationRange);
0301 
0302     // Destination range
0303     l1 = new QHBoxLayout;
0304     lab = new QLabel(i18n("Destination range:"), this);
0305 
0306     l1->addWidget(lab);
0307     l1->addWidget(&m_destMin);
0308     l1->addWidget(&m_destMax);
0309     lay->addLayout(l1);
0310 
0311     l1 = new QHBoxLayout;
0312     m_limitRange = new QCheckBox(i18n("Actual range only"), this);
0313     connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange);
0314     connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
0315     l1->addWidget(m_limitRange);
0316     l1->addStretch(10);
0317     lay->addLayout(l1);
0318     l1 = new QHBoxLayout;
0319     m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this);
0320     m_limitKeyframes->setChecked(true);
0321     m_limitNumber = new QSpinBox(this);
0322     m_limitNumber->setMinimum(1);
0323     m_limitNumber->setValue(20);
0324     l1->addWidget(m_limitKeyframes);
0325     l1->addWidget(m_limitNumber);
0326     l1->addStretch(10);
0327     lay->addLayout(l1);
0328     connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled);
0329     connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
0330     connect(m_limitNumber, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateDisplay);
0331     connect(m_dataCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateDataDisplay);
0332     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0333     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0334     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0335     lay->addWidget(buttonBox);
0336     m_isReady = true;
0337     updateDestinationRange();
0338     updateDataDisplay();
0339     updateView();
0340 }
0341 
0342 KeyframeImport::~KeyframeImport() = default;
0343 
0344 void KeyframeImport::resizeEvent(QResizeEvent *ev)
0345 {
0346     QWidget::resizeEvent(ev);
0347     updateDisplay();
0348 }
0349 
0350 void KeyframeImport::updateDataDisplay()
0351 {
0352     QString comboData = m_dataCombo->currentData().toString();
0353     auto type = m_dataCombo->currentData(TypeRole).value<ParamType>();
0354     auto values = m_dataCombo->currentData(ValueRole).toString().split(QLatin1Char(';'));
0355 
0356     // we do not need all the options if there is only one keyframe
0357     bool onlyOne = values.length() == 1;
0358     m_previewLabel->setVisible(!onlyOne);
0359     m_limitKeyframes->setVisible(!onlyOne);
0360     m_limitNumber->setVisible(!onlyOne);
0361     m_inPoint->setVisible(!onlyOne);
0362     m_outPoint->setVisible(!onlyOne);
0363 
0364     m_maximas = KeyframeModel::getRanges(comboData, m_model);
0365     m_sourceCombo->clear();
0366     if (type == ParamType::KeyframeParam) {
0367         // 1 dimensional param.
0368         m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue);
0369 
0370         // map rotation to rotation by default if possible
0371         if (m_dataCombo->currentText() == QStringLiteral("rotation")) {
0372             int idx = m_targetCombo->findText(i18n("Rotation"));
0373             if (idx > -1) {
0374                 m_targetCombo->setCurrentIndex(idx);
0375             }
0376         }
0377         updateRange();
0378         return;
0379     }
0380     if (type == ParamType::Roto_spline) {
0381         m_sourceCombo->addItem(i18n("Rotoscoping shape"), ImportRoles::RotoData);
0382         return;
0383     }
0384     double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
0385     double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
0386     m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry);
0387     m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position);
0388     m_sourceCombo->addItem(i18n("Inverted Position"), ImportRoles::InvertedPosition);
0389     m_sourceCombo->addItem(i18n("Offset Position"), ImportRoles::OffsetPosition);
0390     m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly);
0391     m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly);
0392     if (wDist > 0) {
0393         m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly);
0394     }
0395     if (hDist > 0) {
0396         m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly);
0397     }
0398 
0399     // if available map to Rectangle by default
0400     int idx = m_targetCombo->findText(i18n("Rectangle"));
0401     if (idx > -1) {
0402         m_targetCombo->setCurrentIndex(idx);
0403     }
0404     updateRange();
0405     /*if (!m_inPoint->isValid()) {
0406         m_inPoint->blockSignals(true);
0407         m_outPoint->blockSignals(true);
0408         // m_inPoint->setRange(0, m_keyframeView->duration);
0409         m_inPoint->setPosition(0);
0410         m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt());
0411         m_inPoint->blockSignals(false);
0412         m_outPoint->blockSignals(false);
0413     }*/
0414 }
0415 
0416 void KeyframeImport::updateRange()
0417 {
0418     int pos = m_sourceCombo->currentData().toInt();
0419     m_alignSourceCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
0420     m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
0421     m_offsetX.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
0422     m_offsetY.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
0423     m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
0424     m_limitRange->setEnabled(pos != ImportRoles::RotoData);
0425     QString rangeText;
0426     if (m_limitRange->isChecked()) {
0427         switch (pos) {
0428         case ImportRoles::SimpleValue:
0429         case ImportRoles::XOnly:
0430             rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y());
0431             break;
0432         case ImportRoles::YOnly:
0433             rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y());
0434             break;
0435         case ImportRoles::WidthOnly:
0436             rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y());
0437             break;
0438         case ImportRoles::HeightOnly:
0439             rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y());
0440             break;
0441         default:
0442             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());
0443             break;
0444         }
0445     } else {
0446         int profileWidth = pCore->getCurrentProfile()->width();
0447         int profileHeight = pCore->getCurrentProfile()->height();
0448         switch (pos) {
0449         case ImportRoles::SimpleValue:
0450             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
0451             break;
0452         case ImportRoles::XOnly:
0453             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
0454             break;
0455         case ImportRoles::YOnly:
0456             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
0457             break;
0458         case ImportRoles::WidthOnly:
0459             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
0460             break;
0461         case ImportRoles::HeightOnly:
0462             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
0463             break;
0464         default:
0465             rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()),
0466                              qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
0467             break;
0468         }
0469     }
0470     m_sourceRangeLabel->setText(rangeText);
0471     updateDisplay();
0472 }
0473 
0474 void KeyframeImport::updateDestinationRange()
0475 {
0476     if (m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
0477         m_destMin.setEnabled(false);
0478         m_destMax.setEnabled(false);
0479         m_limitRange->setEnabled(false);
0480         return;
0481     }
0482 
0483     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
0484         // 1 dimension target
0485         m_destMin.setEnabled(true);
0486         m_destMax.setEnabled(true);
0487         m_limitRange->setEnabled(true);
0488         double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble();
0489         double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble();
0490         m_destMin.setRange(min, max);
0491         m_destMax.setRange(min, max);
0492         m_destMin.setValue(min);
0493         m_destMax.setValue(max);
0494     } else {
0495         int profileWidth = pCore->getCurrentProfile()->width();
0496         m_destMin.setRange(-2 * profileWidth, 2 * profileWidth);
0497         m_destMax.setRange(-2 * profileWidth, 2 * profileWidth);
0498         m_destMin.setEnabled(false);
0499         m_destMax.setEnabled(false);
0500         m_limitRange->setEnabled(false);
0501         updateView();
0502     }
0503 }
0504 
0505 void KeyframeImport::updateDisplay()
0506 {
0507     if (!m_isReady) {
0508         // Not ready
0509         return;
0510     }
0511     QPixmap pix(m_previewLabel->width(), m_previewLabel->height());
0512     pix.fill(Qt::transparent);
0513     QList<QPoint> maximas;
0514     int selectedtarget = m_sourceCombo->currentData().toInt();
0515     int profileWidth = pCore->getCurrentProfile()->width();
0516     int profileHeight = pCore->getCurrentProfile()->height();
0517     if (!m_maximas.isEmpty()) {
0518         if (m_maximas.at(0).x() == m_maximas.at(0).y() ||
0519             (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
0520             maximas << QPoint();
0521         } else {
0522             if (m_limitRange->isChecked()) {
0523                 maximas << m_maximas.at(0);
0524             } else {
0525                 QPoint p1;
0526                 if (selectedtarget == ImportRoles::SimpleValue) {
0527                     p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
0528                 } else {
0529                     p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
0530                 }
0531                 maximas << p1;
0532             }
0533         }
0534     }
0535     if (m_maximas.count() > 1) {
0536         if (m_maximas.at(1).x() == m_maximas.at(1).y() ||
0537             (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
0538             maximas << QPoint();
0539         } else {
0540             if (m_limitRange->isChecked()) {
0541                 maximas << m_maximas.at(1);
0542             } else {
0543                 QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
0544                 maximas << p2;
0545             }
0546         }
0547     }
0548     if (m_maximas.count() > 2) {
0549         if (m_maximas.at(2).x() == m_maximas.at(2).y() ||
0550             (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) {
0551             maximas << QPoint();
0552         } else {
0553             if (m_limitRange->isChecked()) {
0554                 maximas << m_maximas.at(2);
0555             } else {
0556                 QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
0557                 maximas << p3;
0558             }
0559         }
0560     }
0561     if (m_maximas.count() > 3) {
0562         if (m_maximas.at(3).x() == m_maximas.at(3).y() ||
0563             (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) {
0564             maximas << QPoint();
0565         } else {
0566             if (m_limitRange->isChecked()) {
0567                 maximas << m_maximas.at(3);
0568             } else {
0569                 QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
0570                 maximas << p4;
0571             }
0572         }
0573     }
0574     drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0,
0575                          palette().text().color());
0576     m_previewLabel->setPixmap(pix);
0577 }
0578 
0579 QString KeyframeImport::selectedData() const
0580 {
0581     // return serialized keyframes
0582     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
0583         // Exporting a 1 dimension animation
0584         int ix = m_sourceCombo->currentData().toInt();
0585         QPoint maximas;
0586         if (m_limitRange->isChecked()) {
0587             maximas = m_maximas.at(ix);
0588         } else if (ix == ImportRoles::WidthOnly) {
0589             // Width maximas
0590             maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width()));
0591         } else {
0592             // Height maximas
0593             maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height()));
0594         }
0595         if (m_dataCombo->currentData(TypeRole).value<ParamType>() == ParamType::Roto_spline) {
0596             QJsonDocument doc = QJsonDocument::fromJson(m_dataCombo->currentData().toString().toUtf8());
0597             return QString(doc.toJson(QJsonDocument::Compact));
0598         }
0599         std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
0600         std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
0601         animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition());
0602         int existingKeys = anim->key_count();
0603         int out = m_outPoint->getPosition();
0604         if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
0605             // We need to limit keyframes, create new animation
0606             int in = m_inPoint->getPosition();
0607             std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
0608             std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
0609             anim2->interpolate();
0610 
0611             // Remove existing kfrs
0612             int firstKeyframe = -1;
0613             int lastKeyframe = -1;
0614             if (anim2->is_key(0)) {
0615                 if (in == 0) {
0616                     firstKeyframe = 0;
0617                 }
0618                 anim2->remove(0);
0619             }
0620             int keyPos = anim2->next_key(0);
0621             while (anim2->is_key(keyPos)) {
0622                 if (firstKeyframe == -1) {
0623                     firstKeyframe = keyPos;
0624                 }
0625                 if (keyPos < out) {
0626                     lastKeyframe = keyPos;
0627                 } else {
0628                     lastKeyframe = out;
0629                 }
0630                 anim2->remove(keyPos);
0631                 keyPos = anim2->next_key(keyPos);
0632             }
0633             anim2->interpolate();
0634             int length = lastKeyframe;
0635             double interval = double(length) / (m_limitNumber->value() - 1);
0636             for (int i = 0; i < m_limitNumber->value(); i++) {
0637                 int pos = firstKeyframe + in + i * interval;
0638                 if (pos > out) {
0639                     break;
0640                 }
0641                 pos = qMin(pos, length - 1);
0642                 double dval = animData->anim_get_double("key", pos);
0643                 animData2->anim_set("key", dval, pos);
0644             }
0645             anim2->interpolate();
0646             return anim2->serialize_cut();
0647         } else {
0648             // Ensure we have a keyframe at destination end
0649             if (out < anim->length() && !anim->is_key(out)) {
0650                 double dval = animData->anim_get_double("key", out);
0651                 animData->anim_set("key", dval, out);
0652             }
0653         }
0654         return anim->serialize_cut();
0655     }
0656     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
0657     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
0658     animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition());
0659     // Ensure we have a keyframe at destination end
0660     int out = m_outPoint->getPosition();
0661     int existingKeys = anim->key_count();
0662     if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
0663         // We need to limit keyframes, create new animation
0664         int in = m_inPoint->getPosition();
0665         std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
0666         std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
0667         anim2->interpolate();
0668 
0669         // Remove existing kfrs
0670         int firstKeyframe = -1;
0671         int lastKeyframe = -1;
0672         if (anim2->is_key(0)) {
0673             if (in == 0) {
0674                 firstKeyframe = 0;
0675             }
0676             anim2->remove(0);
0677         }
0678         int keyPos = anim2->next_key(0);
0679         while (anim2->is_key(keyPos)) {
0680             if (firstKeyframe == -1) {
0681                 firstKeyframe = keyPos;
0682             }
0683             if (keyPos < out) {
0684                 lastKeyframe = keyPos;
0685             } else {
0686                 lastKeyframe = out;
0687             }
0688             anim2->remove(keyPos);
0689             keyPos = anim2->next_key(keyPos);
0690         }
0691         anim2->interpolate();
0692         int length = lastKeyframe - firstKeyframe;
0693         double interval = double(length) / (m_limitNumber->value() - 1);
0694         for (int i = 0; i < m_limitNumber->value(); i++) {
0695             int pos = firstKeyframe + i * interval;
0696             pos = qMin(pos, lastKeyframe);
0697             if (pos > out) {
0698                 break;
0699             }
0700             mlt_rect rect = animData->anim_get_rect("key", pos);
0701             animData2->anim_set("key", rect, pos);
0702         }
0703         anim2->interpolate();
0704         return anim2->serialize_cut();
0705     } else {
0706         if (out < anim->length() && !anim->is_key(out)) {
0707             mlt_rect rect = animData->anim_get_rect("key", out);
0708             animData->anim_set("key", rect, out);
0709         }
0710     }
0711     return anim->serialize_cut();
0712 }
0713 
0714 QString KeyframeImport::selectedTarget() const
0715 {
0716     return m_targetCombo->currentData().toString();
0717 }
0718 
0719 void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor)
0720 {
0721     // qDebug() << "============= DRAWING KFR CHANNS: " << m_dataCombo->currentData().toString();
0722     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
0723     QRect br(0, 0, pix.width(), pix.height());
0724     double frameFactor = double(out - in) / br.width();
0725     int offset = 1;
0726     if (limitKeyframes > 0) {
0727         offset = int((out - in) / limitKeyframes / frameFactor);
0728     }
0729     double min = m_dataCombo->currentData(MinRole).toDouble();
0730     double max = m_dataCombo->currentData(MaxRole).toDouble();
0731     double xDist;
0732     if (max > min) {
0733         xDist = max - min;
0734     } else {
0735         xDist = m_maximas.at(0).y() - m_maximas.at(0).x();
0736     }
0737     double yDist = m_maximas.at(1).y() - m_maximas.at(1).x();
0738     double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
0739     double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
0740     double xOffset = m_maximas.at(0).x();
0741     double yOffset = m_maximas.at(1).x();
0742     double wOffset = m_maximas.at(2).x();
0743     double hOffset = m_maximas.at(3).x();
0744     QColor cX(255, 0, 0, 100);
0745     QColor cY(0, 255, 0, 100);
0746     QColor cW(0, 0, 255, 100);
0747     QColor cH(255, 255, 0, 100);
0748     // Draw curves labels
0749     QPainter painter;
0750     painter.begin(&pix);
0751     QRectF txtRect = painter.boundingRect(br, QStringLiteral("t"));
0752     txtRect.setX(2);
0753     txtRect.setWidth(br.width() - 4);
0754     txtRect.moveTop(br.height() - txtRect.height());
0755     QRectF drawnText;
0756     int maxHeight = int(br.height() - txtRect.height() - 2);
0757     painter.setPen(textColor);
0758     int rectSize = int(txtRect.height() / 2);
0759     if (xDist > 0) {
0760         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cX);
0761         txtRect.setX(txtRect.x() + rectSize * 2);
0762         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()),
0763                          &drawnText);
0764     }
0765     if (yDist > 0) {
0766         if (drawnText.isValid()) {
0767             txtRect.setX(drawnText.right() + rectSize);
0768         }
0769         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cY);
0770         txtRect.setX(txtRect.x() + rectSize * 2);
0771         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()),
0772                          &drawnText);
0773     }
0774     if (wDist > 0) {
0775         if (drawnText.isValid()) {
0776             txtRect.setX(drawnText.right() + rectSize);
0777         }
0778         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cW);
0779         txtRect.setX(txtRect.x() + rectSize * 2);
0780         painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText);
0781     }
0782     if (hDist > 0) {
0783         if (drawnText.isValid()) {
0784             txtRect.setX(drawnText.right() + rectSize);
0785         }
0786         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cH);
0787         txtRect.setX(txtRect.x() + rectSize * 2);
0788         painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText);
0789     }
0790 
0791     // Draw curves
0792     for (int i = 0; i < br.width(); i++) {
0793         mlt_rect rect = animData->anim_get_rect("key", int(i * frameFactor) + in);
0794         if (xDist > 0) {
0795             painter.setPen(cX);
0796             int val = int((rect.x - xOffset) * maxHeight / xDist);
0797             // qDebug() << "// DRAWINC CURVE : " << rect.x << ", POS: " << (int(i * frameFactor) + in) << ", RESULT: " << val;
0798             painter.drawLine(i, maxHeight - val, i, maxHeight);
0799         }
0800         if (yDist > 0) {
0801             painter.setPen(cY);
0802             int val = int((rect.y - yOffset) * maxHeight / yDist);
0803             painter.drawLine(i, maxHeight - val, i, maxHeight);
0804         }
0805         if (wDist > 0) {
0806             painter.setPen(cW);
0807             int val = int((rect.w - wOffset) * maxHeight / wDist);
0808             qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val;
0809             painter.drawLine(i, maxHeight - val, i, maxHeight);
0810         }
0811         if (hDist > 0) {
0812             painter.setPen(cH);
0813             int val = int((rect.h - hOffset) * maxHeight / hDist);
0814             painter.drawLine(i, maxHeight - val, i, maxHeight);
0815         }
0816     }
0817     if (offset > 1) {
0818         // Overlay limited keyframes curve
0819         cX.setAlpha(255);
0820         cY.setAlpha(255);
0821         cW.setAlpha(255);
0822         cH.setAlpha(255);
0823         mlt_rect rect1 = animData->anim_get_rect("key", in);
0824         int prevPos = 0;
0825         for (int i = offset; i < br.width(); i += offset) {
0826             mlt_rect rect2 = animData->anim_get_rect("key", int(i * frameFactor) + in);
0827             if (xDist > 0) {
0828                 painter.setPen(cX);
0829                 int val1 = int((rect1.x - xOffset) * maxHeight / xDist);
0830                 int val2 = int((rect2.x - xOffset) * maxHeight / xDist);
0831                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
0832             }
0833             if (yDist > 0) {
0834                 painter.setPen(cY);
0835                 int val1 = int((rect1.y - yOffset) * maxHeight / yDist);
0836                 int val2 = int((rect2.y - yOffset) * maxHeight / yDist);
0837                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
0838             }
0839             if (wDist > 0) {
0840                 painter.setPen(cW);
0841                 int val1 = int((rect1.w - wOffset) * maxHeight / wDist);
0842                 int val2 = int((rect2.w - wOffset) * maxHeight / wDist);
0843                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
0844             }
0845             if (hDist > 0) {
0846                 painter.setPen(cH);
0847                 int val1 = int((rect1.h - hOffset) * maxHeight / hDist);
0848                 int val2 = int((rect2.h - hOffset) * maxHeight / hDist);
0849                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
0850             }
0851             rect1 = rect2;
0852             prevPos = i;
0853         }
0854     }
0855 }
0856 
0857 void KeyframeImport::importSelectedData()
0858 {
0859     // Simple double value
0860     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
0861     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
0862     std::shared_ptr<KeyframeModelList> kfrModel = m_model->getKeyframeModel();
0863     Fun undo = []() { return true; };
0864     Fun redo = []() { return true; };
0865     // Geometry target
0866     int sourceAlign = m_alignSourceCombo->currentData().toInt();
0867     int targetAlign = m_alignTargetCombo->currentData().toInt();
0868     QLocale locale; // Import from clipboard – OK to use locale here?
0869     locale.setNumberOptions(QLocale::OmitGroupSeparator);
0870     // wether we are mapping to a fake rectangle
0871     bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
0872     bool useOpacity = m_dataCombo->currentData(OpacityRole).toBool();
0873     for (const auto &ix : qAsConst(m_indexes)) {
0874         // update keyframes in other indexes
0875         KeyframeModel *km = kfrModel->getKeyModel(ix);
0876         qDebug() << "== " << ix << " = " << m_targetCombo->currentData().toModelIndex();
0877         if (ix == m_targetCombo->currentData().toModelIndex() || fakeRect) {
0878             // Import our keyframes
0879             KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles>(m_sourceCombo->currentData().toInt());
0880             if (convertMode == ImportRoles::RotoData && m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
0881                 QJsonObject json = QJsonDocument::fromJson(selectedData().toUtf8()).object();
0882                 for (int i = 0; i < json.count(); i++) {
0883                     int frame = json.keys().at(i).toInt();
0884                     if (frame > m_outPoint->getPosition()) {
0885                         break;
0886                     }
0887                     km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType::Linear,
0888                                     json.value(json.keys().at(i)), true, undo, redo);
0889                 }
0890                 continue;
0891             }
0892             mlt_keyframe_type type;
0893             mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
0894             for (int i = 0; i < anim->key_count(); i++) {
0895                 int frame = 0;
0896                 int error = anim->key_get(i, frame, type);
0897                 if (frame > m_outPoint->getPosition()) {
0898                     break;
0899                 }
0900                 if (error) {
0901                     continue;
0902                 }
0903                 QVariant current = km->getInterpolatedValue(frame);
0904                 if (convertMode == ImportRoles::SimpleValue) {
0905                     double dval = animData->anim_get_double("key", frame);
0906                     km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), dval,
0907                                     true, undo, redo);
0908                     continue;
0909                 }
0910                 QStringList kfrData = current.toString().split(QLatin1Char(' '));
0911                 // Safety check
0912                 if (fakeRect) {
0913                     while (kfrData.size() < 4) {
0914                         kfrData.append("0");
0915                     }
0916                 }
0917                 int size = kfrData.size();
0918                 switch (convertMode) {
0919                 case ImportRoles::FullGeometry:
0920                 case ImportRoles::HeightOnly:
0921                 case ImportRoles::WidthOnly:
0922                     if (size < 4) {
0923                         continue;
0924                     }
0925                     break;
0926                 case ImportRoles::Position:
0927                 case ImportRoles::InvertedPosition:
0928                 case ImportRoles::OffsetPosition:
0929                 case ImportRoles::YOnly:
0930                     if (size < 2) {
0931                         continue;
0932                     }
0933                     break;
0934                 default:
0935                     if (size == 0) {
0936                         continue;
0937                     }
0938                     break;
0939                 }
0940                 mlt_rect rect = animData->anim_get_rect("key", frame);
0941                 if (!useOpacity) {
0942                     rect.o = 1;
0943                 }
0944                 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
0945                     switch (sourceAlign) {
0946                     case 1:
0947                         // Align top center
0948                         rect.x += rect.w / 2;
0949                         break;
0950                     case 2:
0951                         // Align top right
0952                         rect.x += rect.w;
0953                         break;
0954                     case 3:
0955                         // Align left center
0956                         rect.y += rect.h / 2;
0957                         break;
0958                     case 4:
0959                         // Align center
0960                         rect.x += rect.w / 2;
0961                         rect.y += rect.h / 2;
0962                         break;
0963                     case 5:
0964                         // Align right center
0965                         rect.x += rect.w;
0966                         rect.y += rect.h / 2;
0967                         break;
0968                     case 6:
0969                         // Align bottom left
0970                         rect.y += rect.h;
0971                         break;
0972                     case 7:
0973                         // Align bottom center
0974                         rect.x += rect.w / 2;
0975                         rect.y += rect.h;
0976                         break;
0977                     case 8:
0978                         // Align bottom right
0979                         rect.x += rect.w;
0980                         rect.y += rect.h;
0981                         break;
0982                     default:
0983                         break;
0984                     }
0985                     switch (targetAlign) {
0986                     case 1:
0987                         // Align top center
0988                         rect.x -= kfrData[2].toInt() / 2;
0989                         break;
0990                     case 2:
0991                         // Align top right
0992                         rect.x -= kfrData[2].toInt();
0993                         break;
0994                     case 3:
0995                         // Align left center
0996                         rect.y -= kfrData[3].toInt() / 2;
0997                         break;
0998                     case 4:
0999                         // Align center
1000                         rect.x -= kfrData[2].toInt() / 2;
1001                         rect.y -= kfrData[3].toInt() / 2;
1002                         break;
1003                     case 5:
1004                         // Align right center
1005                         rect.x -= kfrData[2].toInt();
1006                         rect.y -= kfrData[3].toInt() / 2;
1007                         break;
1008                     case 6:
1009                         // Align bottom left
1010                         rect.y -= kfrData[3].toInt();
1011                         break;
1012                     case 7:
1013                         // Align bottom center
1014                         rect.x -= kfrData[2].toInt() / 2;
1015                         rect.y -= kfrData[3].toInt();
1016                         break;
1017                     case 8:
1018                         // Align bottom right
1019                         rect.x -= kfrData[2].toInt();
1020                         rect.y -= kfrData[3].toInt();
1021                         break;
1022                     default:
1023                         break;
1024                     }
1025                 }
1026                 rect.x += m_offsetX.value();
1027                 rect.y += m_offsetY.value();
1028                 switch (convertMode) {
1029                 case ImportRoles::RotoData:
1030                     break;
1031                 case ImportRoles::FullGeometry:
1032                     kfrData[0] = locale.toString(int(rect.x));
1033                     kfrData[1] = locale.toString(int(rect.y));
1034                     kfrData[2] = locale.toString(int(rect.w));
1035                     kfrData[3] = locale.toString(int(rect.h));
1036                     if (size > 4) {
1037                         kfrData[4] = QString::number(rect.o);
1038                     }
1039                     break;
1040                 case ImportRoles::Position:
1041                     kfrData[0] = locale.toString(int(rect.x));
1042                     kfrData[1] = locale.toString(int(rect.y));
1043                     break;
1044                 case ImportRoles::InvertedPosition:
1045                     kfrData[0] = locale.toString(int(-rect.x));
1046                     kfrData[1] = locale.toString(int(-rect.y));
1047                     break;
1048                 case ImportRoles::OffsetPosition:
1049                     kfrData[0] = locale.toString(int(firstRect.x - rect.x));
1050                     kfrData[1] = locale.toString(int(firstRect.y - rect.y));
1051                     break;
1052                 case ImportRoles::SimpleValue:
1053                 case ImportRoles::XOnly:
1054                     kfrData[0] = locale.toString(int(rect.x));
1055                     break;
1056                 case ImportRoles::YOnly:
1057                     kfrData[1] = locale.toString(int(rect.y));
1058                     break;
1059                 case ImportRoles::WidthOnly:
1060                     kfrData[2] = locale.toString(int(rect.w));
1061                     break;
1062                 case ImportRoles::HeightOnly:
1063                     kfrData[3] = locale.toString(int(rect.h));
1064                     break;
1065                 }
1066                 // map the fake rectangle internaly to the right params
1067                 QString name = ix.data(AssetParameterModel::NameRole).toString();
1068                 QSize frameSize = pCore->getCurrentFrameSize();
1069                 if (name.contains("Position X") &&
1070                     !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly)) {
1071                     current = kfrData[0].toDouble() / frameSize.width();
1072                     if (convertMode == ImportRoles::FullGeometry) {
1073                         current = current.toDouble() + rect.w / frameSize.width() / 2;
1074                     }
1075                 } else if (name.contains("Position Y") &&
1076                            !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1077                     current = kfrData[1].toDouble() / frameSize.height();
1078                     if (convertMode == ImportRoles::FullGeometry) {
1079                         current = current.toDouble() + rect.h / frameSize.height() / 2;
1080                     }
1081                 } else if (name.contains("Size X") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition ||
1082                                                        convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1083                     current = kfrData[2].toDouble() / frameSize.width() / 2;
1084                 } else if (name.contains("Size Y") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition ||
1085                                                        convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1086                     current = kfrData[3].toDouble() / frameSize.height() / 2;
1087                 } else if (fakeRect) {
1088                     current = km->getInterpolatedValue(frame).toDouble();
1089                 } else {
1090                     current = kfrData.join(QLatin1Char(' '));
1091                 }
1092                 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current,
1093                                 true, undo, redo);
1094             }
1095         } else {
1096             int frame = 0;
1097             mlt_keyframe_type type;
1098             for (int i = 0; i < anim->key_count(); i++) {
1099                 int error = anim->key_get(i, frame, type);
1100                 if (frame > m_outPoint->getPosition()) {
1101                     break;
1102                 }
1103                 if (error) {
1104                     continue;
1105                 }
1106                 // frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition());
1107                 QVariant current = km->getInterpolatedValue(frame);
1108                 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current,
1109                                 true, undo, redo);
1110             }
1111         }
1112     }
1113     pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard"));
1114 }
1115 
1116 int KeyframeImport::getImportType() const
1117 {
1118     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
1119         return -1;
1120     }
1121     return m_sourceCombo->currentData().toInt();
1122 }
1123 
1124 void KeyframeImport::updateView()
1125 {
1126     QPersistentModelIndex ix = m_targetCombo->currentData().toModelIndex();
1127     QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1128 
1129     // Calculate updated keyframes
1130     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
1131     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
1132     // Geometry target
1133     int sourceAlign = m_alignSourceCombo->currentData().toInt();
1134     int targetAlign = m_alignTargetCombo->currentData().toInt();
1135     QLocale locale; // Import from clipboard – OK to use locale here?
1136     locale.setNumberOptions(QLocale::OmitGroupSeparator);
1137     // update keyframes in other indexes
1138     if (!m_originalParams.contains(ix)) {
1139         qDebug() << "=== Original parameter not found";
1140         return;
1141     }
1142     animData->set("original", m_originalParams.value(ix).toUtf8().constData());
1143     std::shared_ptr<Mlt::Animation> animo(new Mlt::Animation(animData->get_animation("original")));
1144     animo->interpolate();
1145     // wether we are mapping to a fake rectangle
1146     bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
1147     // Import our keyframes
1148     int frame = 0;
1149     KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles>(m_sourceCombo->currentData().toInt());
1150     mlt_keyframe_type type;
1151     mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
1152     bool useOpacity = m_dataCombo->currentData(OpacityRole).toBool();
1153     for (int i = 0; i < anim->key_count(); i++) {
1154         int error = anim->key_get(i, frame, type);
1155         if (error) {
1156             continue;
1157         }
1158         mlt_rect sourceRect = animData->anim_get_rect("original", frame);
1159         if (!useOpacity) {
1160             sourceRect.o = 1;
1161         }
1162         QStringList kfrData = {QString::number(sourceRect.x), QString::number(sourceRect.y), QString::number(sourceRect.w), QString::number(sourceRect.h),
1163                                QString::number(sourceRect.o)};
1164         // Safety check
1165         if (fakeRect) {
1166             while (kfrData.size() < 4) {
1167                 kfrData.append("0");
1168             }
1169         }
1170         int size = kfrData.size();
1171         switch (convertMode) {
1172         case ImportRoles::FullGeometry:
1173         case ImportRoles::HeightOnly:
1174         case ImportRoles::WidthOnly:
1175             if (size < 4) {
1176                 continue;
1177             }
1178             break;
1179         case ImportRoles::Position:
1180         case ImportRoles::InvertedPosition:
1181         case ImportRoles::OffsetPosition:
1182         case ImportRoles::YOnly:
1183             if (size < 2) {
1184                 continue;
1185             }
1186             break;
1187         default:
1188             if (size == 0) {
1189                 continue;
1190             }
1191             break;
1192         }
1193         mlt_rect rect = animData->anim_get_rect("key", frame);
1194         mlt_keyframe_type kfrType = anim->keyframe_type(frame);
1195         if (!useOpacity) {
1196             rect.o = 1;
1197         }
1198         if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
1199             switch (sourceAlign) {
1200             case 1:
1201                 // Align top center
1202                 rect.x += rect.w / 2;
1203                 break;
1204             case 2:
1205                 // Align top right
1206                 rect.x += rect.w;
1207                 break;
1208             case 3:
1209                 // Align left center
1210                 rect.y += rect.h / 2;
1211                 break;
1212             case 4:
1213                 // Align center
1214                 rect.x += rect.w / 2;
1215                 rect.y += rect.h / 2;
1216                 break;
1217             case 5:
1218                 // Align right center
1219                 rect.x += rect.w;
1220                 rect.y += rect.h / 2;
1221                 break;
1222             case 6:
1223                 // Align bottom left
1224                 rect.y += rect.h;
1225                 break;
1226             case 7:
1227                 // Align bottom center
1228                 rect.x += rect.w / 2;
1229                 rect.y += rect.h;
1230                 break;
1231             case 8:
1232                 // Align bottom right
1233                 rect.x += rect.w;
1234                 rect.y += rect.h;
1235                 break;
1236             default:
1237                 break;
1238             }
1239             switch (targetAlign) {
1240             case 1:
1241                 // Align top center
1242                 rect.x -= kfrData[2].toInt() / 2;
1243                 break;
1244             case 2:
1245                 // Align top right
1246                 rect.x -= kfrData[2].toInt();
1247                 break;
1248             case 3:
1249                 // Align left center
1250                 rect.y -= kfrData[3].toInt() / 2;
1251                 break;
1252             case 4:
1253                 // Align center
1254                 rect.x -= kfrData[2].toInt() / 2;
1255                 rect.y -= kfrData[3].toInt() / 2;
1256                 break;
1257             case 5:
1258                 // Align right center
1259                 rect.x -= kfrData[2].toInt();
1260                 rect.y -= kfrData[3].toInt() / 2;
1261                 break;
1262             case 6:
1263                 // Align bottom left
1264                 rect.y -= kfrData[3].toInt();
1265                 break;
1266             case 7:
1267                 // Align bottom center
1268                 rect.x -= kfrData[2].toInt() / 2;
1269                 rect.y -= kfrData[3].toInt();
1270                 break;
1271             case 8:
1272                 // Align bottom right
1273                 rect.x -= kfrData[2].toInt();
1274                 rect.y -= kfrData[3].toInt();
1275                 break;
1276             default:
1277                 break;
1278             }
1279         }
1280         rect.x += m_offsetX.value();
1281         rect.y += m_offsetY.value();
1282         switch (convertMode) {
1283         case ImportRoles::RotoData:
1284             break;
1285         case ImportRoles::FullGeometry:
1286             kfrData[0] = locale.toString(int(rect.x));
1287             kfrData[1] = locale.toString(int(rect.y));
1288             kfrData[2] = locale.toString(int(rect.w));
1289             kfrData[3] = locale.toString(int(rect.h));
1290             kfrData[4] = QString::number(rect.o);
1291             break;
1292         case ImportRoles::Position:
1293             kfrData[0] = locale.toString(int(rect.x));
1294             kfrData[1] = locale.toString(int(rect.y));
1295             break;
1296         case ImportRoles::InvertedPosition:
1297             kfrData[0] = locale.toString(int(-rect.x));
1298             kfrData[1] = locale.toString(int(-rect.y));
1299             break;
1300         case ImportRoles::OffsetPosition:
1301             kfrData[0] = locale.toString(int(firstRect.x - rect.x));
1302             kfrData[1] = locale.toString(int(firstRect.y - rect.y));
1303             break;
1304         case ImportRoles::SimpleValue:
1305         case ImportRoles::XOnly:
1306             kfrData[0] = locale.toString(int(rect.x));
1307             break;
1308         case ImportRoles::YOnly:
1309             kfrData[1] = locale.toString(int(rect.y));
1310             break;
1311         case ImportRoles::WidthOnly:
1312             kfrData[2] = locale.toString(int(rect.w));
1313             break;
1314         case ImportRoles::HeightOnly:
1315             kfrData[3] = locale.toString(int(rect.h));
1316             break;
1317         }
1318         // map the fake rectangle internaly to the right params
1319         QString name = ix.data(AssetParameterModel::NameRole).toString();
1320         QSize frameSize = pCore->getCurrentFrameSize();
1321         bool doubleParameter = false;
1322         double val;
1323         if (name.contains("Position X") &&
1324             !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly)) {
1325             val = kfrData[0].toDouble() / frameSize.width();
1326             if (convertMode == ImportRoles::FullGeometry) {
1327                 val += rect.w / frameSize.width() / 2;
1328             }
1329             doubleParameter = true;
1330         } else if (name.contains("Position Y") &&
1331                    !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1332             val = kfrData[1].toDouble() / frameSize.height();
1333             if (convertMode == ImportRoles::FullGeometry) {
1334                 val += rect.h / frameSize.height() / 2;
1335             }
1336             doubleParameter = true;
1337         } else if (name.contains("Size X") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition ||
1338                                                convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1339             val = kfrData[2].toDouble() / frameSize.width() / 2;
1340             doubleParameter = true;
1341         } else if (name.contains("Size Y") && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition ||
1342                                                convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1343             val = kfrData[3].toDouble() / frameSize.height() / 2;
1344             doubleParameter = true;
1345         } else if (fakeRect) {
1346             val = animData->anim_get_double("original", frame);
1347             doubleParameter = true;
1348         } else {
1349             mlt_rect resultRect;
1350             resultRect.x = kfrData.at(0).toInt();
1351             resultRect.y = kfrData.at(1).toInt();
1352             resultRect.w = kfrData.at(2).toInt();
1353             resultRect.h = kfrData.at(3).toInt();
1354             resultRect.o = kfrData.at(4).toDouble();
1355             animData->anim_set("key2", resultRect, frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(),
1356                                m_outPoint->getPosition() - m_inPoint->getPosition(), kfrType);
1357         }
1358         if (doubleParameter) {
1359             animData->anim_set("key2", val, frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(),
1360                                m_outPoint->getPosition() - m_inPoint->getPosition(), kfrType);
1361         }
1362     }
1363     std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData->get_animation("key2")));
1364     anim2->interpolate();
1365     m_model->getAsset()->set(paramName.toUtf8().constData(), anim2->serialize_cut());
1366     if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) {
1367         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1368     } else {
1369         pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1370     }
1371     Q_EMIT updateQmlView();
1372 }
1373 
1374 void KeyframeImport::reject()
1375 {
1376     if (m_targetCombo == nullptr) {
1377         // no data to import, close
1378         QDialog::reject();
1379         return;
1380     }
1381     for (int i = 0; i < m_targetCombo->count(); i++) {
1382         QPersistentModelIndex ix = m_targetCombo->itemData(i).toModelIndex();
1383         if (m_originalParams.contains(ix)) {
1384             QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1385             m_model->getAsset()->set(paramName.toUtf8().constData(), m_originalParams.value(ix).toUtf8().constData());
1386         }
1387     }
1388     if (m_model->getOwnerId().type == KdenliveObjectType::BinClip) {
1389         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1390     } else {
1391         pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1392     }
1393     Q_EMIT updateQmlView();
1394     QDialog::reject();
1395 }
1396 
1397 void KeyframeImport::accept()
1398 {
1399     importSelectedData();
1400     QDialog::accept();
1401 }