1 /*
2     SPDX-FileCopyrightText: 2016 Jean-Baptiste Mardelle <jb@kdenlive.org>
3     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
4 */
5 
6 #include "klocalizedstring.h"
7 #include <QCheckBox>
8 #include <QComboBox>
9 #include <QDialogButtonBox>
10 #include <QHBoxLayout>
11 #include <QJsonArray>
12 #include <QJsonDocument>
13 #include <QJsonObject>
14 #include <QLabel>
15 #include <QPainter>
16 #include <QSpinBox>
17 #include <QVBoxLayout>
18 #include <utility>
19 
20 #include "assets/keyframes/view/keyframeview.hpp"
21 #include "core.h"
22 #include "doc/kdenlivedoc.h"
23 #include "monitor/monitor.h"
24 #include "kdenlivesettings.h"
25 #include "keyframeimport.h"
26 #include "profiles/profilemodel.hpp"
27 #include "widgets/positionwidget.h"
28 #include <macros.hpp>
29 
30 #include "mlt++/MltAnimation.h"
31 #include "mlt++/MltProperties.h"
32 
33 
KeyframeImport(const QString & animData,std::shared_ptr<AssetParameterModel> model,QList<QPersistentModelIndex> indexes,int parentIn,int parentDuration,QWidget * parent)34 KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr<AssetParameterModel> model, QList<QPersistentModelIndex> indexes, int parentIn, int parentDuration, QWidget *parent)
35     : QDialog(parent)
36     , m_model(std::move(model))
37     , m_indexes(indexes)
38     , m_supportsAnim(false)
39     , m_previewLabel(nullptr)
40     , m_sourceCombo(nullptr)
41     , m_targetCombo(nullptr)
42     , m_isReady(false)
43 {
44     setAttribute(Qt::WA_DeleteOnClose);
45     auto *lay = new QVBoxLayout(this);
46     auto *l1 = new QHBoxLayout;
47     QLabel *lab = new QLabel(i18n("Data to import:"), this);
48     l1->addWidget(lab);
49 
50     m_dataCombo = new QComboBox(this);
51     l1->addWidget(m_dataCombo);
52     l1->addStretch(10);
53     lay->addLayout(l1);
54     int in = -1;
55     int out = -1;
56     // Set  up data
57     auto json = QJsonDocument::fromJson(animData.toUtf8());
58     if (!json.isArray()) {
59         qDebug() << "Error : Json file should be an array";
60         // Try to build data from a single value
61         QJsonArray list;
62         QJsonObject currentParam;
63         currentParam.insert(QLatin1String("name"), QStringLiteral("data"));
64         currentParam.insert(QLatin1String("value"), animData);
65         bool ok;
66         QString firstFrame = animData.section(QLatin1Char('='), 0, 0);
67         in = firstFrame.toInt(&ok);
68         if (!ok) {
69             firstFrame.chop(1);
70             in = firstFrame.toInt(&ok);
71         }
72         QString first = animData.section(QLatin1Char('='), 1, 1);
73         if (!first.isEmpty()) {
74             int spaces = first.count(QLatin1Char(' '));
75             switch (spaces) {
76                 case 0:
77                     currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::Animated)));
78                     break;
79                 default:
80                     currentParam.insert(QLatin1String("type"), QJsonValue(int(ParamType::AnimatedRect)));
81                     break;
82             }
83             //currentParam.insert(QLatin1String("min"), QJsonValue(min));
84             //currentParam.insert(QLatin1String("max"), QJsonValue(max));
85             list.push_back(currentParam);
86             json = QJsonDocument(list);
87         }
88     }
89     if (!json.isArray()) {
90         qDebug() << "Error : Json file should be an array";
91         return;
92     }
93     auto list = json.array();
94     int ix = 0;
95     for (const auto &entry : qAsConst(list)) {
96         if (!entry.isObject()) {
97             qDebug() << "Warning : Skipping invalid marker data";
98             continue;
99         }
100         auto entryObj = entry.toObject();
101         if (!entryObj.contains(QLatin1String("name"))) {
102             qDebug() << "Warning : Skipping invalid marker data (does not contain name)";
103             continue;
104         }
105         QString name = entryObj[QLatin1String("name")].toString();
106         QString displayName = entryObj[QLatin1String("DisplayName")].toString();
107         if (displayName.isEmpty()) {
108             displayName = name;
109         }
110         QString value = entryObj[QLatin1String("value")].toString();
111         int type = entryObj[QLatin1String("type")].toInt(0);
112         double min = entryObj[QLatin1String("min")].toDouble(0);
113         double max = entryObj[QLatin1String("max")].toDouble(0);
114         if (in == -1) {
115             in = entryObj[QLatin1String("in")].toInt(0);
116         }
117         if (out == -1) {
118             out = entryObj[QLatin1String("out")].toInt(0);
119         }
120         bool opacity = entryObj[QLatin1String("opacity")].toBool(true);
121         m_dataCombo->insertItem(ix, displayName);
122         m_dataCombo->setItemData(ix, value, Qt::UserRole);
123         m_dataCombo->setItemData(ix, type, Qt::UserRole + 1);
124         m_dataCombo->setItemData(ix, min, Qt::UserRole + 2);
125         m_dataCombo->setItemData(ix, max, Qt::UserRole + 3);
126         m_dataCombo->setItemData(ix, opacity, Qt::UserRole + 4);
127         ix++;
128     }
129     m_previewLabel = new QLabel(this);
130     m_previewLabel->setMinimumSize(100, 150);
131     m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
132     m_previewLabel->setScaledContents(true);
133 
134     lay->addWidget(m_previewLabel);
135     // Zone in / out
136     in = qMax(0, in);
137     if (out <= 0) {
138         out = in + parentDuration;
139     }
140     m_inPoint = new PositionWidget(i18n("In"), in, 0, out, pCore->currentDoc()->timecode(), QString(), this);
141     connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
142     lay->addWidget(m_inPoint);
143     m_outPoint = new PositionWidget(i18n("Out"), out, in, out, pCore->currentDoc()->timecode(), QString(), this);
144     connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
145     lay->addWidget(m_outPoint);
146 
147     // Output offset
148     int clipIn = parentIn;
149     m_offsetPoint = new PositionWidget(i18n("Time offset"), clipIn, 0, clipIn + parentDuration, pCore->currentDoc()->timecode(), "", this);
150     lay->addWidget(m_offsetPoint);
151 
152     int count = 0;
153     // Check what kind of parameters are in our target
154     for (const QPersistentModelIndex &idx : indexes) {
155         auto type = m_model->data(idx, AssetParameterModel::TypeRole).value<ParamType>();
156         if (type == ParamType::KeyframeParam) {
157             m_simpleTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
158             QString name(m_model->data(idx, AssetParameterModel::NameRole).toString());
159             if(name.contains("Position X") || name.contains("Position Y") || name.contains("Size X") || name.contains("Size Y")) {
160                 count++;
161             }
162         } else if (type == ParamType::Roto_spline) {
163             m_simpleTargets.insert(i18n("Rotoscoping shape"), idx);
164         } else if (type == ParamType::AnimatedRect) {
165             m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
166         }
167     }
168 
169     l1 = new QHBoxLayout;
170     m_targetCombo = new QComboBox(this);
171     m_sourceCombo = new QComboBox(this);
172     /*ix = 0;
173     if (!m_geometryTargets.isEmpty()) {
174         m_sourceCombo->insertItem(ix, i18n("Geometry"));
175         m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole);
176         ix++;
177         m_sourceCombo->insertItem(ix, i18n("Position"));
178         m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole);
179         ix++;
180     }
181     if (!m_simpleTargets.isEmpty()) {
182         m_sourceCombo->insertItem(ix, i18n("X"));
183         m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole);
184         ix++;
185         m_sourceCombo->insertItem(ix, i18n("Y"));
186         m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole);
187         ix++;
188         m_sourceCombo->insertItem(ix, i18n("Width"));
189         m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole);
190         ix++;
191         m_sourceCombo->insertItem(ix, i18n("Height"));
192         m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole);
193         ix++;
194     }*/
195     connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateRange);
196     connect(m_sourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
197     m_alignSourceCombo = new QComboBox(this);
198     m_alignSourceCombo->addItem(i18n("Top left"), 0);
199     m_alignSourceCombo->addItem(i18n("Top center"), 1);
200     m_alignSourceCombo->addItem(i18n("Top right"), 2);
201     m_alignSourceCombo->addItem(i18n("Left center"), 3);
202     m_alignSourceCombo->addItem(i18n("Center"), 4);
203     m_alignSourceCombo->addItem(i18n("Right center"), 5);
204     m_alignSourceCombo->addItem(i18n("Bottom left"), 6);
205     m_alignSourceCombo->addItem(i18n("Bottom center"), 7);
206     m_alignSourceCombo->addItem(i18n("Bottom right"), 8);
207     m_alignTargetCombo = new QComboBox(this);
208     m_alignTargetCombo->addItem(i18n("Top left"), 0);
209     m_alignTargetCombo->addItem(i18n("Top center"), 1);
210     m_alignTargetCombo->addItem(i18n("Top right"), 2);
211     m_alignTargetCombo->addItem(i18n("Left center"), 3);
212     m_alignTargetCombo->addItem(i18n("Center"), 4);
213     m_alignTargetCombo->addItem(i18n("Right center"), 5);
214     m_alignTargetCombo->addItem(i18n("Bottom left"), 6);
215     m_alignTargetCombo->addItem(i18n("Bottom center"), 7);
216     m_alignTargetCombo->addItem(i18n("Bottom right"), 8);
217     connect(m_alignSourceCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
218     connect(m_alignTargetCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &KeyframeImport::updateView);
219     lab = new QLabel(i18n("Map "), this);
220     QLabel *lab2 = new QLabel(i18n(" to "), this);
221     l1->addWidget(lab);
222     l1->addWidget(m_sourceCombo);
223     l1->addWidget(m_alignSourceCombo);
224     l1->addWidget(lab2);
225     l1->addWidget(m_targetCombo);
226     l1->addWidget(m_alignTargetCombo);
227     ix = 0;
228     QMap<QString, QModelIndex>::const_iterator j = m_geometryTargets.constBegin();
229     while (j != m_geometryTargets.constEnd()) {
230         m_targetCombo->insertItem(ix, j.key());
231         m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
232         m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
233         ++j;
234         ix++;
235     }
236     if(count == 4) {
237         // add an option to map to a a fake rectangle (pretend position params are a mlt rect)
238         m_targetCombo->insertItem(ix, i18n("Rectangle"));
239     }
240     ix = 0;
241     j = m_simpleTargets.constBegin();
242     while (j != m_simpleTargets.constEnd()) {
243         m_targetCombo->insertItem(ix, j.key());
244         m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
245         m_originalParams.insert(j.value(), m_model->data(j.value(), AssetParameterModel::ValueRole).toString());
246         ++j;
247         ix++;
248     }
249     if (m_simpleTargets.count() + m_geometryTargets.count() > 1) {
250         // Target contains several animatable parameters, propose choice
251     }
252     lay->addLayout(l1);
253 
254     m_offsetX.setRange(-pCore->getCurrentProfile()->width(), pCore->getCurrentProfile()->width());
255     m_offsetY.setRange(-pCore->getCurrentProfile()->height(), pCore->getCurrentProfile()->height());
256     connect(&m_offsetX, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
257     connect(&m_offsetY, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &KeyframeImport::updateView);
258 
259     // Destination range
260     l1 = new QHBoxLayout;
261     lab = new QLabel(i18n("Position offset"), this);
262     l1->addWidget(lab);
263     l1->addWidget(&m_offsetX);
264     l1->addWidget(&m_offsetY);
265     lay->addLayout(l1);
266 
267 
268     // Source range
269     m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this);
270     lay->addWidget(m_sourceRangeLabel);
271 
272     // update range info
273     connect(m_targetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDestinationRange()));
274 
275     // Destination range
276     l1 = new QHBoxLayout;
277     lab = new QLabel(i18n("Destination range"), this);
278 
279     l1->addWidget(lab);
280     l1->addWidget(&m_destMin);
281     l1->addWidget(&m_destMax);
282     lay->addLayout(l1);
283 
284     l1 = new QHBoxLayout;
285     m_limitRange = new QCheckBox(i18n("Actual range only"), this);
286     connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange);
287     connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
288     l1->addWidget(m_limitRange);
289     l1->addStretch(10);
290     lay->addLayout(l1);
291     l1 = new QHBoxLayout;
292     m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this);
293     m_limitKeyframes->setChecked(true);
294     m_limitNumber = new QSpinBox(this);
295     m_limitNumber->setMinimum(1);
296     m_limitNumber->setValue(20);
297     l1->addWidget(m_limitKeyframes);
298     l1->addWidget(m_limitNumber);
299     l1->addStretch(10);
300     lay->addLayout(l1);
301     connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled);
302     connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
303     connect(m_limitNumber, SIGNAL(valueChanged(int)), this, SLOT(updateDisplay()));
304     connect(m_dataCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDataDisplay()));
305     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
306     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
307     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
308     lay->addWidget(buttonBox);
309     m_isReady = true;
310     updateDestinationRange();
311     updateDataDisplay();
312     updateView();
313 }
314 
315 KeyframeImport::~KeyframeImport() = default;
316 
resizeEvent(QResizeEvent * ev)317 void KeyframeImport::resizeEvent(QResizeEvent *ev)
318 {
319     QWidget::resizeEvent(ev);
320     updateDisplay();
321 }
322 
updateDataDisplay()323 void KeyframeImport::updateDataDisplay()
324 {
325     QString comboData = m_dataCombo->currentData().toString();
326     auto type = m_dataCombo->currentData(Qt::UserRole + 1).value<ParamType>();
327     auto values = m_dataCombo->currentData(Qt::UserRole).toString().split(QLatin1Char(';'));
328 
329     // we do not need all the options if there is only one keyframe
330     bool onlyOne = values.length() == 1;
331     m_previewLabel->setVisible(!onlyOne);
332     m_limitKeyframes->setVisible(!onlyOne);
333     m_limitNumber->setVisible(!onlyOne);
334     m_inPoint->setVisible(!onlyOne);
335     m_outPoint->setVisible(!onlyOne);
336 
337     m_maximas = KeyframeModel::getRanges(comboData, m_model);
338     m_sourceCombo->clear();
339     if (type == ParamType::KeyframeParam) {
340         // 1 dimensional param.
341         m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue);
342 
343         // map rotation to rotation by default if possible
344         if (m_dataCombo->currentText() == QStringLiteral("rotation")) {
345             int idx = m_targetCombo->findText(i18n("Rotation"));
346             if (idx > -1) {
347                 m_targetCombo->setCurrentIndex(idx);
348             }
349         }
350         updateRange();
351         return;
352     }
353     if (type == ParamType::Roto_spline) {
354         m_sourceCombo->addItem(i18n("Rotoscoping shape"), ImportRoles::RotoData);
355         return;
356     }
357     double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
358     double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
359     m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry);
360     m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position);
361     m_sourceCombo->addItem(i18n("Inverted Position"), ImportRoles::InvertedPosition);
362     m_sourceCombo->addItem(i18n("Offset Position"), ImportRoles::OffsetPosition);
363     m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly);
364     m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly);
365     if (wDist > 0) {
366         m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly);
367     }
368     if (hDist > 0) {
369         m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly);
370     }
371 
372     // if available map to Rectangle by default
373     int idx = m_targetCombo->findText(i18n("Rectangle"));
374     if (idx > -1) {
375         m_targetCombo->setCurrentIndex(idx);
376     }
377     updateRange();
378     /*if (!m_inPoint->isValid()) {
379         m_inPoint->blockSignals(true);
380         m_outPoint->blockSignals(true);
381         // m_inPoint->setRange(0, m_keyframeView->duration);
382         m_inPoint->setPosition(0);
383         m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt());
384         m_inPoint->blockSignals(false);
385         m_outPoint->blockSignals(false);
386     }*/
387 }
388 
updateRange()389 void KeyframeImport::updateRange()
390 {
391     int pos = m_sourceCombo->currentData().toInt();
392     m_alignSourceCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
393     m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
394     m_offsetX.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
395     m_offsetY.setEnabled(pos != ImportRoles::SimpleValue && pos != ImportRoles::RotoData);
396     m_alignTargetCombo->setEnabled(pos == ImportRoles::Position || pos == ImportRoles::InvertedPosition);
397     m_limitRange->setEnabled(pos != ImportRoles::RotoData);
398     QString rangeText;
399     if (m_limitRange->isChecked()) {
400         switch (pos) {
401             case ImportRoles::SimpleValue:
402             case ImportRoles::XOnly:
403             rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y());
404             break;
405         case ImportRoles::YOnly:
406             rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y());
407             break;
408         case ImportRoles::WidthOnly:
409             rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y());
410             break;
411         case ImportRoles::HeightOnly:
412             rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y());
413             break;
414         default:
415             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());
416             break;
417         }
418     } else {
419         int profileWidth = pCore->getCurrentProfile()->width();
420         int profileHeight = pCore->getCurrentProfile()->height();
421         switch (pos) {
422         case ImportRoles::SimpleValue:
423             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
424             break;
425         case ImportRoles::XOnly:
426             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
427             break;
428         case ImportRoles::YOnly:
429             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
430             break;
431         case ImportRoles::WidthOnly:
432             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
433             break;
434         case ImportRoles::HeightOnly:
435             rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
436             break;
437         default:
438             rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()),
439                              qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
440             break;
441         }
442     }
443     m_sourceRangeLabel->setText(rangeText);
444     updateDisplay();
445 }
446 
updateDestinationRange()447 void KeyframeImport::updateDestinationRange()
448 {
449     if (m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
450         m_destMin.setEnabled(false);
451         m_destMax.setEnabled(false);
452         m_limitRange->setEnabled(false);
453         return;
454     }
455 
456     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
457         // 1 dimension target
458         m_destMin.setEnabled(true);
459         m_destMax.setEnabled(true);
460         m_limitRange->setEnabled(true);
461         double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble();
462         double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble();
463         m_destMin.setRange(min, max);
464         m_destMax.setRange(min, max);
465         m_destMin.setValue(min);
466         m_destMax.setValue(max);
467     } else {
468         int profileWidth = pCore->getCurrentProfile()->width();
469         m_destMin.setRange(-2 * profileWidth, 2 * profileWidth);
470         m_destMax.setRange(-2 * profileWidth, 2 * profileWidth);
471         m_destMin.setEnabled(false);
472         m_destMax.setEnabled(false);
473         m_limitRange->setEnabled(false);
474         updateView();
475     }
476 }
477 
updateDisplay()478 void KeyframeImport::updateDisplay()
479 {
480     if (!m_isReady) {
481         // Not ready
482         return;
483     }
484     QPixmap pix(m_previewLabel->width(), m_previewLabel->height());
485     pix.fill(Qt::transparent);
486     QList<QPoint> maximas;
487     int selectedtarget = m_sourceCombo->currentData().toInt();
488     int profileWidth = pCore->getCurrentProfile()->width();
489     int profileHeight = pCore->getCurrentProfile()->height();
490     if (!m_maximas.isEmpty()) {
491         if (m_maximas.at(0).x() == m_maximas.at(0).y() || (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
492             maximas << QPoint();
493         } else {
494             if (m_limitRange->isChecked()) {
495                 maximas << m_maximas.at(0);
496             } else {
497                 QPoint p1;
498                 if (selectedtarget == ImportRoles::SimpleValue) {
499                     p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
500                 } else {
501                     p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
502                 }
503                 maximas << p1;
504             }
505         }
506     }
507     if (m_maximas.count() > 1) {
508         if (m_maximas.at(1).x() == m_maximas.at(1).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
509             maximas << QPoint();
510         } else {
511             if (m_limitRange->isChecked()) {
512                 maximas << m_maximas.at(1);
513             } else {
514                 QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
515                 maximas << p2;
516             }
517         }
518     }
519     if (m_maximas.count() > 2) {
520         if (m_maximas.at(2).x() == m_maximas.at(2).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) {
521             maximas << QPoint();
522         } else {
523             if (m_limitRange->isChecked()) {
524                 maximas << m_maximas.at(2);
525             } else {
526                 QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
527                 maximas << p3;
528             }
529         }
530     }
531     if (m_maximas.count() > 3) {
532         if (m_maximas.at(3).x() == m_maximas.at(3).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) {
533             maximas << QPoint();
534         } else {
535             if (m_limitRange->isChecked()) {
536                 maximas << m_maximas.at(3);
537             } else {
538                 QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
539                 maximas << p4;
540             }
541         }
542     }
543     drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0,
544                          palette().text().color());
545     m_previewLabel->setPixmap(pix);
546 }
547 
selectedData() const548 QString KeyframeImport::selectedData() const
549 {
550     // return serialized keyframes
551     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
552         // Exporting a 1 dimension animation
553         int ix = m_sourceCombo->currentData().toInt();
554         QPoint maximas;
555         if (m_limitRange->isChecked()) {
556             maximas = m_maximas.at(ix);
557         } else if (ix == ImportRoles::WidthOnly) {
558             // Width maximas
559             maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width()));
560         } else {
561             // Height maximas
562             maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height()));
563         }
564         std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
565         if (m_dataCombo->currentText() == "spline") {
566             QJsonDocument doc = QJsonDocument::fromJson(m_dataCombo->currentData().toString().toLocal8Bit());
567             return QString(doc.toJson(QJsonDocument::Compact));
568         }
569         std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
570         animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition());
571         int existingKeys = anim->key_count();
572         if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
573             // We need to limit keyframes, create new animation
574             int in = m_inPoint->getPosition();
575             int out = m_outPoint->getPosition();
576             std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
577             std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
578             anim2->interpolate();
579 
580             // Remove existing kfrs
581             int firstKeyframe = -1;
582             int lastKeyframe = -1;
583             if (anim2->is_key(0)) {
584                 if (in == 0) {
585                     firstKeyframe = 0;
586                 }
587                 anim2->remove(0);
588             }
589             int keyPos = anim2->next_key(0);
590             while (anim2->is_key(keyPos)) {
591                 if (firstKeyframe == -1) {
592                     firstKeyframe = keyPos;
593                 }
594                 if (keyPos < out) {
595                     lastKeyframe = keyPos;
596                 } else {
597                     lastKeyframe = out;
598                 }
599                 anim2->remove(keyPos);
600                 keyPos = anim2->next_key(keyPos);
601             }
602             anim2->interpolate();
603             int length = lastKeyframe;
604             double interval = double(length) / (m_limitNumber->value() - 1);
605             int pos = 0;
606             for (int i = 0; i < m_limitNumber->value(); i++) {
607                 pos = firstKeyframe + in + i * interval;
608                 pos = qMin(pos, length - 1);
609                 double dval = animData->anim_get_double("key", pos);
610                 animData2->anim_set("key", dval, pos);
611 
612             }
613             anim2->interpolate();
614             return anim2->serialize_cut();
615         }
616         return anim->serialize_cut();
617     }
618     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
619     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
620     animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition());
621     int existingKeys = anim->key_count();
622     if (m_limitKeyframes->isChecked() && m_limitNumber->value() < existingKeys) {
623             // We need to limit keyframes, create new animation
624             int in = m_inPoint->getPosition();
625             int out = m_outPoint->getPosition();
626             std::shared_ptr<Mlt::Properties> animData2 = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
627             std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData2->get_animation("key")));
628             anim2->interpolate();
629 
630             // Remove existing kfrs
631             int firstKeyframe = -1;
632             int lastKeyframe = -1;
633             if (anim2->is_key(0)) {
634                 if (in == 0) {
635                     firstKeyframe = 0;
636                 }
637                 anim2->remove(0);
638             }
639             int keyPos = anim2->next_key(0);
640             while (anim2->is_key(keyPos)) {
641                 if (firstKeyframe == -1) {
642                     firstKeyframe = keyPos;
643                 }
644                 if (keyPos < out) {
645                     lastKeyframe = keyPos;
646                 } else {
647                     lastKeyframe = out;
648                 }
649                 anim2->remove(keyPos);
650                 keyPos = anim2->next_key(keyPos);
651             }
652             anim2->interpolate();
653             int length = lastKeyframe - firstKeyframe;
654             double interval = double(length) / (m_limitNumber->value() - 1);
655             int pos = 0;
656             for (int i = 0; i < m_limitNumber->value(); i++) {
657                 pos = firstKeyframe + i * interval;
658                 pos = qMin(pos, lastKeyframe);
659                 mlt_rect rect = animData->anim_get_rect("key", pos);
660                 animData2->anim_set("key", rect, pos);
661             }
662             anim2->interpolate();
663             return anim2->serialize_cut();
664         }
665 
666     return anim->serialize_cut();
667 }
668 
selectedTarget() const669 QString KeyframeImport::selectedTarget() const
670 {
671     return m_targetCombo->currentData().toString();
672 }
673 
drawKeyFrameChannels(QPixmap & pix,int in,int out,int limitKeyframes,const QColor & textColor)674 void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor)
675 {
676     qDebug()<<"============= DRAWING KFR CHANNS: "<<m_dataCombo->currentData().toString();
677     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
678     QRect br(0, 0, pix.width(), pix.height());
679     double frameFactor = double(out - in) / br.width();
680     int offset = 1;
681     if (limitKeyframes > 0) {
682         offset = int((out - in) / limitKeyframes / frameFactor);
683     }
684     double min = m_dataCombo->currentData(Qt::UserRole + 2).toDouble();
685     double max = m_dataCombo->currentData(Qt::UserRole + 3).toDouble();
686     double xDist;
687     if (max > min) {
688         xDist = max - min;
689     } else {
690         xDist = m_maximas.at(0).y() - m_maximas.at(0).x();
691     }
692     double yDist = m_maximas.at(1).y() - m_maximas.at(1).x();
693     double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
694     double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
695     double xOffset = m_maximas.at(0).x();
696     double yOffset = m_maximas.at(1).x();
697     double wOffset = m_maximas.at(2).x();
698     double hOffset = m_maximas.at(3).x();
699     QColor cX(255, 0, 0, 100);
700     QColor cY(0, 255, 0, 100);
701     QColor cW(0, 0, 255, 100);
702     QColor cH(255, 255, 0, 100);
703     // Draw curves labels
704     QPainter painter;
705     painter.begin(&pix);
706     QRectF txtRect = painter.boundingRect(br, QStringLiteral("t"));
707     txtRect.setX(2);
708     txtRect.setWidth(br.width() - 4);
709     txtRect.moveTop(br.height() - txtRect.height());
710     QRectF drawnText;
711     int maxHeight = int(br.height() - txtRect.height() - 2);
712     painter.setPen(textColor);
713     int rectSize = int(txtRect.height() / 2);
714     if (xDist > 0) {
715         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cX);
716         txtRect.setX(txtRect.x() + rectSize * 2);
717         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()),
718                          &drawnText);
719     }
720     if (yDist > 0) {
721         if (drawnText.isValid()) {
722             txtRect.setX(drawnText.right() + rectSize);
723         }
724         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cY);
725         txtRect.setX(txtRect.x() + rectSize * 2);
726         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()),
727                          &drawnText);
728     }
729     if (wDist > 0) {
730         if (drawnText.isValid()) {
731             txtRect.setX(drawnText.right() + rectSize);
732         }
733         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cW);
734         txtRect.setX(txtRect.x() + rectSize * 2);
735         painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText);
736     }
737     if (hDist > 0) {
738         if (drawnText.isValid()) {
739             txtRect.setX(drawnText.right() + rectSize);
740         }
741         painter.fillRect(int(txtRect.x()), int(txtRect.top() + rectSize / 2), rectSize, rectSize, cH);
742         txtRect.setX(txtRect.x() + rectSize * 2);
743         painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText);
744     }
745 
746     // Draw curves
747     for (int i = 0; i < br.width(); i++) {
748         mlt_rect rect = animData->anim_get_rect("key", int(i * frameFactor) + in);
749         if (xDist > 0) {
750             painter.setPen(cX);
751             int val = int((rect.x - xOffset) * maxHeight / xDist);
752             qDebug() << "// DRAWINC CURVE : " << rect.x <<", POS: "<<(int(i * frameFactor) + in)<< ", RESULT: " << val;
753             painter.drawLine(i, maxHeight - val, i, maxHeight);
754         }
755         if (yDist > 0) {
756             painter.setPen(cY);
757             int val = int((rect.y - yOffset) * maxHeight / yDist);
758             painter.drawLine(i, maxHeight - val, i, maxHeight);
759         }
760         if (wDist > 0) {
761             painter.setPen(cW);
762             int val = int((rect.w - wOffset) * maxHeight / wDist);
763             qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val;
764             painter.drawLine(i, maxHeight - val, i, maxHeight);
765         }
766         if (hDist > 0) {
767             painter.setPen(cH);
768             int val = int((rect.h - hOffset) * maxHeight / hDist);
769             painter.drawLine(i, maxHeight - val, i, maxHeight);
770         }
771     }
772     if (offset > 1) {
773         // Overlay limited keyframes curve
774         cX.setAlpha(255);
775         cY.setAlpha(255);
776         cW.setAlpha(255);
777         cH.setAlpha(255);
778         mlt_rect rect1 = animData->anim_get_rect("key", in);
779         int prevPos = 0;
780         for (int i = offset; i < br.width(); i += offset) {
781             mlt_rect rect2 = animData->anim_get_rect("key", int(i * frameFactor) + in);
782             if (xDist > 0) {
783                 painter.setPen(cX);
784                 int val1 = int((rect1.x - xOffset) * maxHeight / xDist);
785                 int val2 = int((rect2.x - xOffset) * maxHeight / xDist);
786                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
787             }
788             if (yDist > 0) {
789                 painter.setPen(cY);
790                 int val1 = int((rect1.y - yOffset) * maxHeight / yDist);
791                 int val2 = int((rect2.y - yOffset) * maxHeight / yDist);
792                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
793             }
794             if (wDist > 0) {
795                 painter.setPen(cW);
796                 int val1 = int((rect1.w - wOffset) * maxHeight / wDist);
797                 int val2 = int((rect2.w - wOffset) * maxHeight / wDist);
798                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
799             }
800             if (hDist > 0) {
801                 painter.setPen(cH);
802                 int val1 = int((rect1.h - hOffset) * maxHeight / hDist);
803                 int val2 = int((rect2.h - hOffset) * maxHeight / hDist);
804                 painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
805             }
806             rect1 = rect2;
807             prevPos = i;
808         }
809     }
810 }
811 
importSelectedData()812 void KeyframeImport::importSelectedData()
813 {
814     // Simple double value
815     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
816     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
817     std::shared_ptr<KeyframeModelList> kfrModel = m_model->getKeyframeModel();
818     Fun undo = []() { return true; };
819     Fun redo = []() { return true; };
820     // Geometry target
821     int sourceAlign = m_alignSourceCombo->currentData().toInt();
822     int targetAlign = m_alignTargetCombo->currentData().toInt();
823     QLocale locale; // Import from clipboard – OK to use locale here?
824     locale.setNumberOptions(QLocale::OmitGroupSeparator);
825     for (const auto &ix : qAsConst(m_indexes)) {
826         // update keyframes in other indexes
827         KeyframeModel *km = kfrModel->getKeyModel(ix);
828         qDebug()<<"== "<<ix<<" = "<<m_targetCombo->currentData().toModelIndex();
829 
830         // wether we are mapping to a fake rectangle
831         bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
832         bool useOpacity = m_dataCombo->currentData(Qt::UserRole + 4).toBool();
833         if (ix == m_targetCombo->currentData().toModelIndex() || fakeRect) {
834             // Import our keyframes
835             int frame = 0;
836             KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles> (m_sourceCombo->currentData().toInt());
837             if (convertMode == ImportRoles::RotoData && m_targetCombo->currentText() == i18n("Rotoscoping shape")) {
838                 QJsonObject json = QJsonDocument::fromJson(selectedData().toLocal8Bit()).object();
839                 for (int i = 0; i < json.count(); i++) {
840                     int frame = json.keys().at(i).toInt();
841                     km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType::Linear, json.value(json.keys().at(i)), true, undo, redo);
842                 }
843                 continue;
844             }
845             mlt_keyframe_type type;
846             mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
847             for (int i = 0; i < anim->key_count(); i++) {
848                 int error = anim->key_get(i, frame, type);
849                 if (error) {
850                     continue;
851                 }
852                 QVariant current = km->getInterpolatedValue(frame);
853                 if (convertMode == ImportRoles::SimpleValue) {
854                     double dval = animData->anim_get_double("key", frame);
855                     km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), dval, true, undo, redo);
856                     continue;
857                 }
858                 QStringList kfrData = current.toString().split(QLatin1Char(' '));
859                 // Safety check
860                 if (fakeRect) {
861                     while (kfrData.size() < 4) {
862                         kfrData.append("0");
863                     }
864                 }
865                 int size = kfrData.size();
866                 switch (convertMode) {
867                     case ImportRoles::FullGeometry:
868                     case ImportRoles::HeightOnly:
869                     case ImportRoles::WidthOnly:
870                         if (size < 4) {
871                             continue;
872                         }
873                         break;
874                     case ImportRoles::Position:
875                     case ImportRoles::InvertedPosition:
876                     case ImportRoles::OffsetPosition:
877                     case ImportRoles::YOnly:
878                         if (size < 2) {
879                             continue;
880                         }
881                         break;
882                     default:
883                         if (size == 0) {
884                             continue;
885                         }
886                         break;
887                 }
888                 mlt_rect rect = animData->anim_get_rect("key", frame);
889                 if (!useOpacity) {
890                     rect.o = 1;
891                 }
892                 if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
893                     switch (sourceAlign) {
894                     case 1:
895                         // Align top center
896                         rect.x += rect.w / 2;
897                         break;
898                     case 2:
899                         // Align top right
900                         rect.x += rect.w;
901                         break;
902                     case 3:
903                         // Align left center
904                         rect.y += rect.h / 2;
905                         break;
906                     case 4:
907                         // Align center
908                         rect.x += rect.w / 2;
909                         rect.y += rect.h / 2;
910                         break;
911                     case 5:
912                         // Align right center
913                         rect.x += rect.w;
914                         rect.y += rect.h / 2;
915                         break;
916                     case 6:
917                         // Align bottom left
918                         rect.y += rect.h;
919                         break;
920                     case 7:
921                         // Align bottom center
922                         rect.x += rect.w / 2;
923                         rect.y += rect.h;
924                         break;
925                     case 8:
926                         // Align bottom right
927                         rect.x += rect.w;
928                         rect.y += rect.h;
929                         break;
930                     default:
931                         break;
932                     }
933                     switch (targetAlign) {
934                         case 1:
935                         // Align top center
936                         rect.x -= kfrData[2].toInt() / 2;
937                         break;
938                     case 2:
939                         // Align top right
940                         rect.x -= kfrData[2].toInt();
941                         break;
942                     case 3:
943                         // Align left center
944                         rect.y -= kfrData[3].toInt() / 2;
945                         break;
946                     case 4:
947                         // Align center
948                         rect.x -= kfrData[2].toInt() / 2;
949                         rect.y -= kfrData[3].toInt() / 2;
950                         break;
951                     case 5:
952                         // Align right center
953                         rect.x -= kfrData[2].toInt();
954                         rect.y -= kfrData[3].toInt() / 2;
955                         break;
956                     case 6:
957                         // Align bottom left
958                         rect.y -= kfrData[3].toInt();
959                         break;
960                     case 7:
961                         // Align bottom center
962                         rect.x -= kfrData[2].toInt() / 2;
963                         rect.y -= kfrData[3].toInt();
964                         break;
965                     case 8:
966                         // Align bottom right
967                         rect.x -= kfrData[2].toInt();
968                         rect.y -= kfrData[3].toInt();
969                         break;
970                     default:
971                         break;
972                     }
973                 }
974                 rect.x += m_offsetX.value();
975                 rect.y += m_offsetY.value();
976                 switch (convertMode) {
977                     case ImportRoles::RotoData:
978                         break;
979                     case ImportRoles::FullGeometry:
980                         kfrData[0] = locale.toString(int(rect.x));
981                         kfrData[1] = locale.toString(int(rect.y));
982                         kfrData[2] = locale.toString(int(rect.w));
983                         kfrData[3] = locale.toString(int(rect.h));
984                         if (size > 4) {
985                             kfrData[4] = QString::number(rect.o);
986                         }
987                         break;
988                     case ImportRoles::Position:
989                         kfrData[0] = locale.toString(int(rect.x));
990                         kfrData[1] = locale.toString(int(rect.y));
991                         break;
992                     case ImportRoles::InvertedPosition:
993                         kfrData[0] = locale.toString(int(-rect.x));
994                         kfrData[1] = locale.toString(int(-rect.y));
995                         break;
996                     case ImportRoles::OffsetPosition:
997                         kfrData[0] = locale.toString(int(firstRect.x - rect.x));
998                         kfrData[1] = locale.toString(int(firstRect.y - rect.y));
999                         break;
1000                     case ImportRoles::SimpleValue:
1001                     case ImportRoles::XOnly:
1002                         kfrData[0] = locale.toString(int(rect.x));
1003                         break;
1004                     case ImportRoles::YOnly:
1005                         kfrData[1] = locale.toString(int(rect.y));
1006                         break;
1007                     case ImportRoles::WidthOnly:
1008                         kfrData[2] = locale.toString(int(rect.w));
1009                         break;
1010                     case ImportRoles::HeightOnly:
1011                         kfrData[3] = locale.toString(int(rect.h));
1012                         break;
1013                 }
1014                 // map the fake rectangle internaly to the right params
1015                 QString name = ix.data(AssetParameterModel::NameRole).toString();
1016                 QSize frameSize = pCore->getCurrentFrameSize();
1017                 if (name.contains("Position X")
1018                         && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly) ) {
1019                     current = kfrData[0].toDouble() / frameSize.width();
1020                     if (convertMode == ImportRoles::FullGeometry) {
1021                         current = current.toDouble() + rect.w / frameSize.width() / 2;
1022                     }
1023                 } else if (name.contains("Position Y")
1024                           && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1025                     current = kfrData[1].toDouble() / frameSize.height();
1026                     if (convertMode == ImportRoles::FullGeometry) {
1027                         current = current.toDouble() + rect.h / frameSize.height() / 2;
1028                     }
1029                 } else if (name.contains("Size X")
1030                           && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1031                     current = kfrData[2].toDouble() / frameSize.width() / 2;
1032                 } else if (name.contains("Size Y")
1033                           && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1034                     current = kfrData[3].toDouble() / frameSize.height() / 2;
1035                 } else if(fakeRect){
1036                     current = km->getInterpolatedValue(frame).toDouble();
1037                 } else {
1038                     current = kfrData.join(QLatin1Char(' '));
1039                 }
1040                 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, true, undo, redo);
1041             }
1042         } else {
1043             int frame = 0;
1044             mlt_keyframe_type type;
1045             for (int i = 0; i < anim->key_count(); i++) {
1046                 int error = anim->key_get(i, frame, type);
1047                 if (error) {
1048                     continue;
1049                 }
1050                 //frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition());
1051                 QVariant current = km->getInterpolatedValue(frame);
1052                 km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), KeyframeType(type), current, true, undo, redo);
1053             }
1054         }
1055     }
1056     pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard"));
1057 }
1058 
getImportType() const1059 int KeyframeImport::getImportType() const
1060 {
1061     if (m_simpleTargets.contains(m_targetCombo->currentText())) {
1062         return -1;
1063     }
1064     return m_sourceCombo->currentData().toInt();
1065 }
1066 
updateView()1067 void KeyframeImport::updateView()
1068 {
1069     QPersistentModelIndex ix = m_targetCombo->currentData().toModelIndex();
1070     QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1071 
1072     // Calculate updated keyframes
1073     std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
1074     std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
1075     // Geometry target
1076     int sourceAlign = m_alignSourceCombo->currentData().toInt();
1077     int targetAlign = m_alignTargetCombo->currentData().toInt();
1078     QLocale locale; // Import from clipboard – OK to use locale here?
1079     locale.setNumberOptions(QLocale::OmitGroupSeparator);
1080     // update keyframes in other indexes
1081     if (!m_originalParams.contains(ix)) {
1082         qDebug()<<"=== Original parameter not found";
1083         return;
1084     }
1085     QString kfrData = m_originalParams.value(ix);
1086     animData->set("original", kfrData.toUtf8().constData());
1087     std::shared_ptr<Mlt::Animation> animo(new Mlt::Animation(animData->get_animation("original")));
1088     animo->interpolate();
1089     // wether we are mapping to a fake rectangle
1090     bool fakeRect = m_targetCombo->currentData().isNull() && m_targetCombo->currentText() == i18n("Rectangle");
1091     // Import our keyframes
1092     int frame = 0;
1093     KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles> (m_sourceCombo->currentData().toInt());
1094     mlt_keyframe_type type;
1095     mlt_rect firstRect = animData->anim_get_rect("key", anim->key_get_frame(0));
1096     for (int i = 0; i < anim->key_count(); i++) {
1097         int error = anim->key_get(i, frame, type);
1098         if (error) {
1099             continue;
1100         }
1101         //QVariant current = km->getInterpolatedValue(frame);
1102         mlt_rect sourceRect = animData->anim_get_rect("original", frame);
1103         QStringList kfrData = {QString::number(sourceRect.x), QString::number(sourceRect.y), QString::number(sourceRect.w), QString::number(sourceRect.h), QString::number(sourceRect.o)};
1104         // Safety check
1105         if (fakeRect) {
1106             while (kfrData.size() < 4) {
1107                 kfrData.append("0");
1108             }
1109         }
1110         int size = kfrData.size();
1111         switch (convertMode) {
1112             case ImportRoles::FullGeometry:
1113             case ImportRoles::HeightOnly:
1114             case ImportRoles::WidthOnly:
1115                 if (size < 4) {
1116                     continue;
1117                 }
1118                 break;
1119             case ImportRoles::Position:
1120             case ImportRoles::InvertedPosition:
1121             case ImportRoles::OffsetPosition:
1122             case ImportRoles::YOnly:
1123                 if (size < 2) {
1124                     continue;
1125                 }
1126                 break;
1127             default:
1128                 if (size == 0) {
1129                     continue;
1130                 }
1131                 break;
1132         }
1133         mlt_rect rect = animData->anim_get_rect("key", frame);
1134         if (convertMode == ImportRoles::Position || convertMode == ImportRoles::InvertedPosition) {
1135             switch (sourceAlign) {
1136             case 1:
1137                 // Align top center
1138                 rect.x += rect.w / 2;
1139                 break;
1140             case 2:
1141                 // Align top right
1142                 rect.x += rect.w;
1143             break;
1144             case 3:
1145                 // Align left center
1146                 rect.y += rect.h / 2;
1147                 break;
1148             case 4:
1149                 // Align center
1150                 rect.x += rect.w / 2;
1151                 rect.y += rect.h / 2;
1152                 break;
1153             case 5:
1154                 // Align right center
1155                 rect.x += rect.w;
1156                 rect.y += rect.h / 2;
1157                 break;
1158             case 6:
1159                 // Align bottom left
1160                 rect.y += rect.h;
1161                 break;
1162             case 7:
1163                 // Align bottom center
1164                 rect.x += rect.w / 2;
1165                 rect.y += rect.h;
1166                 break;
1167             case 8:
1168                 // Align bottom right
1169                 rect.x += rect.w;
1170                 rect.y += rect.h;
1171                 break;
1172             default:
1173                 break;
1174             }
1175             switch (targetAlign) {
1176                 case 1:
1177                 // Align top center
1178                 rect.x -= kfrData[2].toInt() / 2;
1179                 break;
1180             case 2:
1181                 // Align top right
1182                 rect.x -= kfrData[2].toInt();
1183                 break;
1184             case 3:
1185                 // Align left center
1186                 rect.y -= kfrData[3].toInt() / 2;
1187                 break;
1188             case 4:
1189                 // Align center
1190                 rect.x -= kfrData[2].toInt() / 2;
1191                 rect.y -= kfrData[3].toInt() / 2;
1192                 break;
1193             case 5:
1194                 // Align right center
1195                 rect.x -= kfrData[2].toInt();
1196                 rect.y -= kfrData[3].toInt() / 2;
1197                 break;
1198             case 6:
1199                 // Align bottom left
1200                 rect.y -= kfrData[3].toInt();
1201                 break;
1202             case 7:
1203                 // Align bottom center
1204                 rect.x -= kfrData[2].toInt() / 2;
1205                 rect.y -= kfrData[3].toInt();
1206                 break;
1207             case 8:
1208                 // Align bottom right
1209                 rect.x -= kfrData[2].toInt();
1210                 rect.y -= kfrData[3].toInt();
1211                 break;
1212             default:
1213                 break;
1214             }
1215         }
1216         rect.x += m_offsetX.value();
1217         rect.y += m_offsetY.value();
1218         switch (convertMode) {
1219             case ImportRoles::RotoData:
1220                 break;
1221             case ImportRoles::FullGeometry:
1222                 kfrData[0] = locale.toString(int(rect.x));
1223                 kfrData[1] = locale.toString(int(rect.y));
1224                 kfrData[2] = locale.toString(int(rect.w));
1225                 kfrData[3] = locale.toString(int(rect.h));
1226                 kfrData[4] = QString::number(rect.o);
1227                 break;
1228             case ImportRoles::Position:
1229                 kfrData[0] = locale.toString(int(rect.x));
1230                 kfrData[1] = locale.toString(int(rect.y));
1231                 break;
1232             case ImportRoles::InvertedPosition:
1233                 kfrData[0] = locale.toString(int(-rect.x));
1234                 kfrData[1] = locale.toString(int(-rect.y));
1235                 break;
1236             case ImportRoles::OffsetPosition:
1237                 kfrData[0] = locale.toString(int(firstRect.x - rect.x));
1238                 kfrData[1] = locale.toString(int(firstRect.y - rect.y));
1239                 break;
1240             case ImportRoles::SimpleValue:
1241             case ImportRoles::XOnly:
1242                 kfrData[0] = locale.toString(int(rect.x));
1243                 break;
1244             case ImportRoles::YOnly:
1245                 kfrData[1] = locale.toString(int(rect.y));
1246                 break;
1247             case ImportRoles::WidthOnly:
1248                 kfrData[2] = locale.toString(int(rect.w));
1249                 break;
1250             case ImportRoles::HeightOnly:
1251                 kfrData[3] = locale.toString(int(rect.h));
1252                 break;
1253         }
1254         // map the fake rectangle internaly to the right params
1255         QString name = ix.data(AssetParameterModel::NameRole).toString();
1256         QSize frameSize = pCore->getCurrentFrameSize();
1257         QString current;
1258         if (name.contains("Position X")
1259                 && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::YOnly) ) {
1260             current = kfrData[0].toDouble() / frameSize.width();
1261             if (convertMode == ImportRoles::FullGeometry) {
1262                 current = current.toDouble() + rect.w / frameSize.width() / 2;
1263             }
1264         } else if (name.contains("Position Y")
1265                         && !(convertMode == ImportRoles::WidthOnly || convertMode == ImportRoles::HeightOnly || convertMode == ImportRoles::XOnly)) {
1266             current = kfrData[1].toDouble() / frameSize.height();
1267             if (convertMode == ImportRoles::FullGeometry) {
1268                 current = current.toDouble() + rect.h / frameSize.height() / 2;
1269             }
1270         } else if (name.contains("Size X")
1271                         && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::WidthOnly)) {
1272             current = kfrData[2].toDouble() / frameSize.width() / 2;
1273         } else if (name.contains("Size Y")
1274                         && (convertMode == ImportRoles::FullGeometry || convertMode == ImportRoles::InvertedPosition || convertMode == ImportRoles::OffsetPosition || convertMode == ImportRoles::HeightOnly)) {
1275             current = kfrData[3].toDouble() / frameSize.height() / 2;
1276         } else if(fakeRect){
1277             current = QString::number(animData->anim_get_double("original", frame));
1278         } else {
1279             current = kfrData.join(QLatin1Char(' '));
1280         }
1281         animData->anim_set("key2", current.toUtf8().constData(), frame - m_inPoint->getPosition() + m_offsetPoint->getPosition());
1282     }
1283     std::shared_ptr<Mlt::Animation> anim2(new Mlt::Animation(animData->get_animation("key2")));
1284     anim2->interpolate();
1285     m_model->getAsset()->set(paramName.toUtf8().constData(), anim2->serialize_cut());
1286     if (m_model->getOwnerId().first == ObjectType::BinClip) {
1287         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1288     } else {
1289         pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1290     }
1291     emit updateQmlView();
1292 }
1293 
reject()1294 void KeyframeImport::reject()
1295 {
1296     if (m_targetCombo == nullptr) {
1297         // no data to import, close
1298         QDialog::reject();
1299         return;
1300     }
1301     for (int i = 0; i < m_targetCombo->count(); i++) {
1302         QPersistentModelIndex ix = m_targetCombo->itemData(i).toModelIndex();
1303         if (m_originalParams.contains(ix)) {
1304             QString paramName = m_model->data(ix, AssetParameterModel::NameRole).toString();
1305             m_model->getAsset()->set(paramName.toUtf8().constData(), m_originalParams.value(ix).toUtf8().constData());
1306         }
1307     }
1308     if (m_model->getOwnerId().first == ObjectType::BinClip) {
1309         pCore->getMonitor(Kdenlive::ClipMonitor)->refreshMonitor();
1310     } else {
1311         pCore->getMonitor(Kdenlive::ProjectMonitor)->refreshMonitor();
1312     }
1313     emit updateQmlView();
1314     QDialog::reject();
1315 }
1316 
accept()1317 void KeyframeImport::accept()
1318 {
1319     importSelectedData();
1320     QDialog::accept();
1321 }
1322