keyframeimport.cpp 32.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/***************************************************************************
 *   Copyright (C) 2016 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
 *   This file is part of Kdenlive. See www.kdenlive.org.                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License as        *
 *   published by the Free Software Foundation; either version 2 of        *
 *   the License or (at your option) version 3 or any later version        *
 *   accepted by the membership of KDE e.V. (or its successor approved     *
 *   by the membership of KDE e.V.), which shall act as a proxy            *
 *   defined in Section 14 of version 3 of the license.                    *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 ***************************************************************************/

Nicolas Carion's avatar
Nicolas Carion committed
22
#include "klocalizedstring.h"
Nicolas Carion's avatar
Nicolas Carion committed
23
24
#include <QCheckBox>
#include <QComboBox>
25
#include <QDialogButtonBox>
Nicolas Carion's avatar
Nicolas Carion committed
26
#include <QHBoxLayout>
27
28
29
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
30
31
#include <QLabel>
#include <QPainter>
Nicolas Carion's avatar
Nicolas Carion committed
32
33
#include <QSpinBox>
#include <QVBoxLayout>
Nicolas Carion's avatar
Nicolas Carion committed
34
#include <utility>
35

36
#include "assets/keyframes/view/keyframeview.hpp"
37
#include "core.h"
38
#include "doc/kdenlivedoc.h"
39
#include "kdenlivesettings.h"
40
#include "keyframeimport.h"
41
#include "profiles/profilemodel.hpp"
42
#include "widgets/positionwidget.h"
43
#include <macros.hpp>
44

45
#include "mlt++/MltAnimation.h"
46
#include "mlt++/MltProperties.h"
47

48
49

KeyframeImport::KeyframeImport(const QString &animData, std::shared_ptr<AssetParameterModel> model, QList<QPersistentModelIndex> indexes,
50
                               QWidget *parent)
51
    : QDialog(parent)
Nicolas Carion's avatar
Nicolas Carion committed
52
    , m_model(std::move(model))
53
    , m_indexes(indexes)
54
    , m_supportsAnim(false)
55
56
    , m_previewLabel(nullptr)
    , m_isReady(false)
57
{
Nicolas Carion's avatar
Nicolas Carion committed
58
59
    auto *lay = new QVBoxLayout(this);
    auto *l1 = new QHBoxLayout;
Pino Toscano's avatar
Pino Toscano committed
60
    QLabel *lab = new QLabel(i18n("Data to import:"), this);
61
62
63
64
    l1->addWidget(lab);

    m_dataCombo = new QComboBox(this);
    l1->addWidget(m_dataCombo);
65
    l1->addStretch(10);
66
    lay->addLayout(l1);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
67
68
    int in = -1;
    int out = -1;
69
    // Set  up data
70
    auto json = QJsonDocument::fromJson(animData.toUtf8());
71
72
73
74
75
76
77
    if (!json.isArray()) {
        qDebug() << "Error : Json file should be an array";
        // Try to build data from a single value
        QJsonArray list;
        QJsonObject currentParam;
        currentParam.insert(QLatin1String("name"), QStringLiteral("data"));
        currentParam.insert(QLatin1String("value"), animData);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
78
79
80
81
82
83
84
        bool ok;
        QString firstFrame = animData.section(QLatin1Char('='), 0, 0);
        in = firstFrame.toInt(&ok);
        if (!ok) {
            firstFrame.chop(1);
            in = firstFrame.toInt(&ok);
        }
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
        QString first = animData.section(QLatin1Char('='), 1, 1);
        if (!first.isEmpty()) {
            int spaces = first.count(QLatin1Char(' '));
            switch (spaces) {
                case 0:
                    currentParam.insert(QLatin1String("type"), QJsonValue((int)ParamType::Animated));
                    break;
                default:
                    currentParam.insert(QLatin1String("type"), QJsonValue((int)ParamType::AnimatedRect));
                    break;
            }
            //currentParam.insert(QLatin1String("min"), QJsonValue(min));
            //currentParam.insert(QLatin1String("max"), QJsonValue(max));
            list.push_back(currentParam);
            json = QJsonDocument(list);
        }
    }
102
103
104
105
106
    if (!json.isArray()) {
        qDebug() << "Error : Json file should be an array";
        return;
    }
    auto list = json.array();
107
    int ix = 0;
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    for (const auto &entry : list) {
        if (!entry.isObject()) {
            qDebug() << "Warning : Skipping invalid marker data";
            continue;
        }
        auto entryObj = entry.toObject();
        if (!entryObj.contains(QLatin1String("name"))) {
            qDebug() << "Warning : Skipping invalid marker data (does not contain name)";
            continue;
        }
        QString name = entryObj[QLatin1String("name")].toString();
        QString value = entryObj[QLatin1String("value")].toString();
        int type = entryObj[QLatin1String("type")].toInt(0);
        double min = entryObj[QLatin1String("min")].toDouble(0);
        double max = entryObj[QLatin1String("max")].toDouble(0);
123
124
125
126
127
128
        if (in == -1) {
            in = entryObj[QLatin1String("in")].toInt(0);
        }
        if (out == -1) {
            out = entryObj[QLatin1String("out")].toInt(0);
        }
129
130
131
132
133
        m_dataCombo->insertItem(ix, name);
        m_dataCombo->setItemData(ix, value, Qt::UserRole);
        m_dataCombo->setItemData(ix, type, Qt::UserRole + 1);
        m_dataCombo->setItemData(ix, min, Qt::UserRole + 2);
        m_dataCombo->setItemData(ix, max, Qt::UserRole + 3);
134
135
136
137
138
139
140
        ix++;
    }
    m_previewLabel = new QLabel(this);
    m_previewLabel->setMinimumSize(100, 150);
    m_previewLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    m_previewLabel->setScaledContents(true);
    lay->addWidget(m_previewLabel);
141
    // Zone in / out
142
143
    in = qMax(0, in);
    if (out <= 0) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
144
        out = in + m_model->data(indexes.first(), AssetParameterModel::ParentDurationRole).toInt();
145
146
    }
    m_inPoint = new PositionWidget(i18n("In"), in, 0, out, pCore->currentDoc()->timecode(), QString(), this);
147
    connect(m_inPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
148
    lay->addWidget(m_inPoint);
149
    m_outPoint = new PositionWidget(i18n("Out"), out, in, out, pCore->currentDoc()->timecode(), QString(), this);
150
    connect(m_outPoint, &PositionWidget::valueChanged, this, &KeyframeImport::updateDisplay);
151
152
153
    lay->addWidget(m_outPoint);

    // Check what kind of parameters are in our target
154
    for (const QPersistentModelIndex &idx : indexes) {
Nicolas Carion's avatar
Nicolas Carion committed
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
        } else if (type == ParamType::AnimatedRect) {
159
            m_geometryTargets.insert(m_model->data(idx, Qt::DisplayRole).toString(), idx);
160
161
        }
    }
162

163
164
165
166
    l1 = new QHBoxLayout;
    m_targetCombo = new QComboBox(this);
    m_sourceCombo = new QComboBox(this);
    ix = 0;
167
    /*if (!m_geometryTargets.isEmpty()) {
168
169
170
        m_sourceCombo->insertItem(ix, i18n("Geometry"));
        m_sourceCombo->setItemData(ix, QString::number(10), Qt::UserRole);
        ix++;
171
172
173
        m_sourceCombo->insertItem(ix, i18n("Position"));
        m_sourceCombo->setItemData(ix, QString::number(11), Qt::UserRole);
        ix++;
174
175
176
177
178
179
180
181
182
183
184
185
186
187
    }
    if (!m_simpleTargets.isEmpty()) {
        m_sourceCombo->insertItem(ix, i18n("X"));
        m_sourceCombo->setItemData(ix, QString::number(0), Qt::UserRole);
        ix++;
        m_sourceCombo->insertItem(ix, i18n("Y"));
        m_sourceCombo->setItemData(ix, QString::number(1), Qt::UserRole);
        ix++;
        m_sourceCombo->insertItem(ix, i18n("Width"));
        m_sourceCombo->setItemData(ix, QString::number(2), Qt::UserRole);
        ix++;
        m_sourceCombo->insertItem(ix, i18n("Height"));
        m_sourceCombo->setItemData(ix, QString::number(3), Qt::UserRole);
        ix++;
188
    }*/
189
    connect(m_sourceCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateRange()));
190
191
    m_alignCombo = new QComboBox(this);
    m_alignCombo->addItems(QStringList() << i18n("Align top left") << i18n("Align center") << i18n("Align bottom right"));
192
193
194
195
196
197
    lab = new QLabel(i18n("Map "), this);
    QLabel *lab2 = new QLabel(i18n(" to "), this);
    l1->addWidget(lab);
    l1->addWidget(m_sourceCombo);
    l1->addWidget(lab2);
    l1->addWidget(m_targetCombo);
198
    l1->addWidget(m_alignCombo);
199
200
    l1->addStretch(10);
    ix = 0;
201
    QMap<QString, QModelIndex>::const_iterator j = m_geometryTargets.constBegin();
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    while (j != m_geometryTargets.constEnd()) {
        m_targetCombo->insertItem(ix, j.key());
        m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
        ++j;
        ix++;
    }
    ix = 0;
    j = m_simpleTargets.constBegin();
    while (j != m_simpleTargets.constEnd()) {
        m_targetCombo->insertItem(ix, j.key());
        m_targetCombo->setItemData(ix, j.value(), Qt::UserRole);
        ++j;
        ix++;
    }
    if (m_simpleTargets.count() + m_geometryTargets.count() > 1) {
        // Target contains several animatable parameters, propose choice
    }
    lay->addLayout(l1);

    // Output offset
222
223
    int clipIn = m_model->data(indexes.first(), AssetParameterModel::ParentInRole).toInt();
    m_offsetPoint = new PositionWidget(i18n("Offset"), clipIn, 0, clipIn + m_model->data(indexes.first(), AssetParameterModel::ParentDurationRole).toInt(), pCore->currentDoc()->timecode(), "", this);
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    lay->addWidget(m_offsetPoint);

    // Source range
    m_sourceRangeLabel = new QLabel(i18n("Source range %1 to %2", 0, 100), this);
    lay->addWidget(m_sourceRangeLabel);

    // update range info
    connect(m_targetCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDestinationRange()));

    // Destination range
    l1 = new QHBoxLayout;
    lab = new QLabel(i18n("Destination range"), this);

    l1->addWidget(lab);
    l1->addWidget(&m_destMin);
    l1->addWidget(&m_destMax);
    lay->addLayout(l1);

242
    l1 = new QHBoxLayout;
243
    m_limitRange = new QCheckBox(i18n("Actual range only"), this);
Laurent Montel's avatar
Laurent Montel committed
244
245
    connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateRange);
    connect(m_limitRange, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
246
247
    l1->addWidget(m_limitRange);
    l1->addStretch(10);
248
249
    lay->addLayout(l1);
    l1 = new QHBoxLayout;
250
251
    m_limitKeyframes = new QCheckBox(i18n("Limit keyframe number"), this);
    m_limitKeyframes->setChecked(true);
252
253
254
    m_limitNumber = new QSpinBox(this);
    m_limitNumber->setMinimum(1);
    m_limitNumber->setValue(20);
255
    l1->addWidget(m_limitKeyframes);
256
    l1->addWidget(m_limitNumber);
257
    l1->addStretch(10);
258
    lay->addLayout(l1);
259
    connect(m_limitKeyframes, &QCheckBox::toggled, m_limitNumber, &QSpinBox::setEnabled);
Laurent Montel's avatar
Laurent Montel committed
260
    connect(m_limitKeyframes, &QAbstractButton::toggled, this, &KeyframeImport::updateDisplay);
261
    connect(m_limitNumber, SIGNAL(valueChanged(int)), this, SLOT(updateDisplay()));
262
    connect(m_dataCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(updateDataDisplay()));
263
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
Laurent Montel's avatar
Laurent Montel committed
264
265
    connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
266
    lay->addWidget(buttonBox);
267
    m_isReady = true;
268
    updateDestinationRange();
269
270
271
    updateDataDisplay();
}

Nicolas Carion's avatar
Nicolas Carion committed
272
KeyframeImport::~KeyframeImport() = default;
273
274
275
276
277
278
279
280
281

void KeyframeImport::resizeEvent(QResizeEvent *ev)
{
    QWidget::resizeEvent(ev);
    updateDisplay();
}

void KeyframeImport::updateDataDisplay()
{
282
    QString comboData = m_dataCombo->currentData().toString();
Nicolas Carion's avatar
Nicolas Carion committed
283
    auto type = m_dataCombo->currentData(Qt::UserRole + 1).value<ParamType>();
284
    m_maximas = KeyframeModel::getRanges(comboData, m_model);
285
286
287
    m_sourceCombo->clear();
    if (type == ParamType::KeyframeParam) {
        // 1 dimensional param.
288
        m_sourceCombo->addItem(m_dataCombo->currentText(), ImportRoles::SimpleValue);
289
290
291
        updateRange();
        return;
    }
292
293
    double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
    double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
294
295
296
297
    m_sourceCombo->addItem(i18n("Geometry"), ImportRoles::FullGeometry);
    m_sourceCombo->addItem(i18n("Position"), ImportRoles::Position);
    m_sourceCombo->addItem(i18n("X"), ImportRoles::XOnly);
    m_sourceCombo->addItem(i18n("Y"), ImportRoles::YOnly);
298
    if (wDist > 0) {
299
        m_sourceCombo->addItem(i18n("Width"), ImportRoles::WidthOnly);
300
301
    }
    if (hDist > 0) {
302
        m_sourceCombo->addItem(i18n("Height"), ImportRoles::HeightOnly);
303
    }
304
    updateRange();
305
    /*if (!m_inPoint->isValid()) {
306
307
        m_inPoint->blockSignals(true);
        m_outPoint->blockSignals(true);
308
        // m_inPoint->setRange(0, m_keyframeView->duration);
309
        m_inPoint->setPosition(0);
310
        m_outPoint->setPosition(m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::ParentDurationRole).toInt());
311
312
        m_inPoint->blockSignals(false);
        m_outPoint->blockSignals(false);
313
    }*/
314
315
}

316
void KeyframeImport::updateRange()
317
{
318
    int pos = m_sourceCombo->currentData().toInt();
319
    m_alignCombo->setEnabled(pos == ImportRoles::Position);
320
321
322
    QString rangeText;
    if (m_limitRange->isChecked()) {
        switch (pos) {
323
324
            case ImportRoles::SimpleValue:
            case ImportRoles::XOnly:
325
326
            rangeText = i18n("Source range %1 to %2", m_maximas.at(0).x(), m_maximas.at(0).y());
            break;
327
        case ImportRoles::YOnly:
328
329
            rangeText = i18n("Source range %1 to %2", m_maximas.at(1).x(), m_maximas.at(1).y());
            break;
330
        case ImportRoles::WidthOnly:
331
332
            rangeText = i18n("Source range %1 to %2", m_maximas.at(2).x(), m_maximas.at(2).y());
            break;
333
        case ImportRoles::HeightOnly:
334
335
336
337
338
            rangeText = i18n("Source range %1 to %2", m_maximas.at(3).x(), m_maximas.at(3).y());
            break;
        default:
            rangeText = i18n("Source range: (%1-%2), (%3-%4)", m_maximas.at(0).x(), m_maximas.at(0).y(), m_maximas.at(1).x(), m_maximas.at(1).y());
            break;
339
340
        }
    } else {
341
342
        int profileWidth = pCore->getCurrentProfile()->width();
        int profileHeight = pCore->getCurrentProfile()->height();
343
        switch (pos) {
344
345
346
347
        case ImportRoles::SimpleValue:
            rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
            break;
        case ImportRoles::XOnly:
348
349
            rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
            break;
350
        case ImportRoles::YOnly:
351
352
            rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
            break;
353
        case ImportRoles::WidthOnly:
354
355
            rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
            break;
356
        case ImportRoles::HeightOnly:
357
358
359
            rangeText = i18n("Source range %1 to %2", qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
            break;
        default:
Nicolas Carion's avatar
Nicolas Carion committed
360
361
            rangeText = i18n("Source range: (%1-%2), (%3-%4)", qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()),
                             qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
362
            break;
363
364
365
        }
    }
    m_sourceRangeLabel->setText(rangeText);
366
    updateDisplay();
367
368
}

369
void KeyframeImport::updateDestinationRange()
370
{
371
372
373
374
375
376
    if (m_simpleTargets.contains(m_targetCombo->currentText())) {
        // 1 dimension target
        m_destMin.setEnabled(true);
        m_destMax.setEnabled(true);
        m_limitRange->setEnabled(true);
        QString tag = m_targetCombo->currentData().toString();
377
378
379
380
381
382
        double min = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MinRole).toDouble();
        double max = m_model->data(m_targetCombo->currentData().toModelIndex(), AssetParameterModel::MaxRole).toDouble();
        m_destMin.setRange(min, max);
        m_destMax.setRange(min, max);
        m_destMin.setValue(min);
        m_destMax.setValue(max);
383
    } else {
384
        int profileWidth = pCore->getCurrentProfile()->width();
385
386
        m_destMin.setRange(-2 * profileWidth, 2 * profileWidth);
        m_destMax.setRange(-2 * profileWidth, 2 * profileWidth);
387
388
389
390
        m_destMin.setEnabled(false);
        m_destMax.setEnabled(false);
        m_limitRange->setEnabled(false);
    }
391
392
}

393
void KeyframeImport::updateDisplay()
394
{
395
396
397
398
    if (!m_isReady) {
        // Not ready
        return;
    }
399
400
    QPixmap pix(m_previewLabel->width(), m_previewLabel->height());
    pix.fill(Qt::transparent);
Laurent Montel's avatar
Laurent Montel committed
401
    QList<QPoint> maximas;
402
    int selectedtarget = m_sourceCombo->currentData().toInt();
403
404
    int profileWidth = pCore->getCurrentProfile()->width();
    int profileHeight = pCore->getCurrentProfile()->height();
Laurent Montel's avatar
Laurent Montel committed
405
    if (!m_maximas.isEmpty()) {
406
        if (m_maximas.at(0).x() == m_maximas.at(0).y() || (selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
407
408
            maximas << QPoint();
        } else {
409
            if (m_limitRange->isChecked()) {
410
                maximas << m_maximas.at(0);
411
            } else {
412
413
414
415
416
417
                QPoint p1;
                if (selectedtarget == ImportRoles::SimpleValue) {  
                    p1 = QPoint(qMin(0, m_maximas.at(0).x()), m_maximas.at(0).y());
                } else {
                    p1 = QPoint(qMin(0, m_maximas.at(0).x()), qMax(profileWidth, m_maximas.at(0).y()));
                }
418
419
420
                maximas << p1;
            }
        }
421
422
    }
    if (m_maximas.count() > 1) {
423
        if (m_maximas.at(1).x() == m_maximas.at(1).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::HeightOnly)) {
424
425
            maximas << QPoint();
        } else {
426
            if (m_limitRange->isChecked()) {
427
                maximas << m_maximas.at(1);
428
            } else {
429
430
431
432
                QPoint p2(qMin(0, m_maximas.at(1).x()), qMax(profileHeight, m_maximas.at(1).y()));
                maximas << p2;
            }
        }
433
434
    }
    if (m_maximas.count() > 2) {
435
        if (m_maximas.at(2).x() == m_maximas.at(2).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::YOnly || selectedtarget == ImportRoles::HeightOnly)) {
436
437
            maximas << QPoint();
        } else {
438
            if (m_limitRange->isChecked()) {
439
                maximas << m_maximas.at(2);
440
            } else {
441
442
443
444
                QPoint p3(qMin(0, m_maximas.at(2).x()), qMax(profileWidth, m_maximas.at(2).y()));
                maximas << p3;
            }
        }
445
446
    }
    if (m_maximas.count() > 3) {
447
        if (m_maximas.at(3).x() == m_maximas.at(3).y() || (selectedtarget == ImportRoles::XOnly || selectedtarget == ImportRoles::WidthOnly || selectedtarget == ImportRoles::YOnly)) {
448
449
            maximas << QPoint();
        } else {
450
            if (m_limitRange->isChecked()) {
451
                maximas << m_maximas.at(3);
452
            } else {
453
454
455
456
457
                QPoint p4(qMin(0, m_maximas.at(3).x()), qMax(profileHeight, m_maximas.at(3).y()));
                maximas << p4;
            }
        }
    }
458
459
    drawKeyFrameChannels(pix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0,
                         palette().text().color());
460
    m_previewLabel->setPixmap(pix);
461
462
}

463
QString KeyframeImport::selectedData() const
464
{
465
466
467
468
469
470
471
    // return serialized keyframes
    if (m_simpleTargets.contains(m_targetCombo->currentText())) {
        // Exporting a 1 dimension animation
        int ix = m_sourceCombo->currentData().toInt();
        QPoint maximas;
        if (m_limitRange->isChecked()) {
            maximas = m_maximas.at(ix);
472
        } else if (ix == ImportRoles::WidthOnly) {
473
            // Width maximas
474
            maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->width()));
475
476
        } else {
            // Height maximas
477
            maximas = QPoint(qMin(m_maximas.at(ix).x(), 0), qMax(m_maximas.at(ix).y(), pCore->getCurrentProfile()->height()));
478
        }
479
        std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
480
481
482
        std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
        animData->anim_get_double("key", m_inPoint->getPosition(), m_outPoint->getPosition());
        return anim->serialize_cut();
483
484
        // m_keyframeView->getSingleAnimation(ix, m_inPoint->getPosition(), m_outPoint->getPosition(), m_offsetPoint->getPosition(),
        // m_limitKeyframes->isChecked() ? m_limitNumber->value() : 0, maximas, m_destMin.value(), m_destMax.value());
485
    }
486
487
488
489
490
491
492
493
    //return QString();
    std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
    std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
    animData->anim_get_rect("key", m_inPoint->getPosition(), m_outPoint->getPosition());
    return anim->serialize_cut();

    /*int pos = m_sourceCombo->currentData().toInt();
    m_keyframeView->getOffsetAnimation(m_inPoint->getPosition(), m_outPoint->getPosition(), m_offsetPoint->getPosition(), m_limitKeyframes->isChecked() ?*/
494
    // m_limitNumber->value() : 0, m_supportsAnim, pos == 11, rectOffset);
495
496
}

497
QString KeyframeImport::selectedTarget() const
498
{
499
    return m_targetCombo->currentData().toString();
500
}
501
502
503

void KeyframeImport::drawKeyFrameChannels(QPixmap &pix, int in, int out, int limitKeyframes, const QColor &textColor)
{
504
505
    qDebug()<<"============= DRAWING KFR CHANNS: "<<m_dataCombo->currentData().toString();
    std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, m_dataCombo->currentData().toString());
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
    QRect br(0, 0, pix.width(), pix.height());
    double frameFactor = (double)(out - in) / br.width();
    int offset = 1;
    if (limitKeyframes > 0) {
        offset = (out - in) / limitKeyframes / frameFactor;
    }
    double min = m_dataCombo->currentData(Qt::UserRole + 2).toDouble();
    double max = m_dataCombo->currentData(Qt::UserRole + 3).toDouble();
    double xDist;
    if (max > min) {
        xDist = max - min;
    } else {
        xDist = m_maximas.at(0).y() - m_maximas.at(0).x();
    }
    double yDist = m_maximas.at(1).y() - m_maximas.at(1).x();
    double wDist = m_maximas.at(2).y() - m_maximas.at(2).x();
    double hDist = m_maximas.at(3).y() - m_maximas.at(3).x();
    double xOffset = m_maximas.at(0).x();
    double yOffset = m_maximas.at(1).x();
    double wOffset = m_maximas.at(2).x();
    double hOffset = m_maximas.at(3).x();
    QColor cX(255, 0, 0, 100);
    QColor cY(0, 255, 0, 100);
    QColor cW(0, 0, 255, 100);
    QColor cH(255, 255, 0, 100);
    // Draw curves labels
    QPainter painter;
    painter.begin(&pix);
    QRectF txtRect = painter.boundingRect(br, QStringLiteral("t"));
    txtRect.setX(2);
    txtRect.setWidth(br.width() - 4);
    txtRect.moveTop(br.height() - txtRect.height());
    QRectF drawnText;
    int maxHeight = br.height() - txtRect.height() - 2;
    painter.setPen(textColor);
    int rectSize = txtRect.height() / 2;
    if (xDist > 0) {
        painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cX);
        txtRect.setX(txtRect.x() + rectSize * 2);
545
546
        painter.drawText(txtRect, 0, i18nc("X as in x coordinate", "X") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(0).x()).arg(m_maximas.at(0).y()),
                         &drawnText);
547
548
549
550
551
552
553
    }
    if (yDist > 0) {
        if (drawnText.isValid()) {
            txtRect.setX(drawnText.right() + rectSize);
        }
        painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cY);
        txtRect.setX(txtRect.x() + rectSize * 2);
554
555
        painter.drawText(txtRect, 0, i18nc("Y as in y coordinate", "Y") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(1).x()).arg(m_maximas.at(1).y()),
                         &drawnText);
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
    }
    if (wDist > 0) {
        if (drawnText.isValid()) {
            txtRect.setX(drawnText.right() + rectSize);
        }
        painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cW);
        txtRect.setX(txtRect.x() + rectSize * 2);
        painter.drawText(txtRect, 0, i18n("Width") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(2).x()).arg(m_maximas.at(2).y()), &drawnText);
    }
    if (hDist > 0) {
        if (drawnText.isValid()) {
            txtRect.setX(drawnText.right() + rectSize);
        }
        painter.fillRect(txtRect.x(), txtRect.top() + rectSize / 2, rectSize, rectSize, cH);
        txtRect.setX(txtRect.x() + rectSize * 2);
        painter.drawText(txtRect, 0, i18n("Height") + QStringLiteral(" (%1-%2)").arg(m_maximas.at(3).x()).arg(m_maximas.at(3).y()), &drawnText);
    }

    // Draw curves
    for (int i = 0; i < br.width(); i++) {
        mlt_rect rect = animData->anim_get_rect("key", (int)(i * frameFactor) + in);
        if (xDist > 0) {
            painter.setPen(cX);
            int val = (rect.x - xOffset) * maxHeight / xDist;
580
            qDebug() << "// DRAWINC CURVE : " << rect.x <<", POS: "<<((int)(i * frameFactor) + in)<< ", RESULT: " << val;
581
582
583
584
585
586
587
588
589
590
            painter.drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (yDist > 0) {
            painter.setPen(cY);
            int val = (rect.y - yOffset) * maxHeight / yDist;
            painter.drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (wDist > 0) {
            painter.setPen(cW);
            int val = (rect.w - wOffset) * maxHeight / wDist;
591
            qDebug() << "// OFFSET: " << wOffset << ", maxH: " << maxHeight << ", wDIst:" << wDist << " = " << val;
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
            painter.drawLine(i, maxHeight - val, i, maxHeight);
        }
        if (hDist > 0) {
            painter.setPen(cH);
            int val = (rect.h - hOffset) * maxHeight / hDist;
            painter.drawLine(i, maxHeight - val, i, maxHeight);
        }
    }
    if (offset > 1) {
        // Overlay limited keyframes curve
        cX.setAlpha(255);
        cY.setAlpha(255);
        cW.setAlpha(255);
        cH.setAlpha(255);
        mlt_rect rect1 = animData->anim_get_rect("key", in);
        int prevPos = 0;
        for (int i = offset; i < br.width(); i += offset) {
            mlt_rect rect2 = animData->anim_get_rect("key", (int)(i * frameFactor) + in);
            if (xDist > 0) {
                painter.setPen(cX);
                int val1 = (rect1.x - xOffset) * maxHeight / xDist;
                int val2 = (rect2.x - xOffset) * maxHeight / xDist;
                painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (yDist > 0) {
                painter.setPen(cY);
                int val1 = (rect1.y - yOffset) * maxHeight / yDist;
                int val2 = (rect2.y - yOffset) * maxHeight / yDist;
                painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (wDist > 0) {
                painter.setPen(cW);
                int val1 = (rect1.w - wOffset) * maxHeight / wDist;
                int val2 = (rect2.w - wOffset) * maxHeight / wDist;
                painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            if (hDist > 0) {
                painter.setPen(cH);
                int val1 = (rect1.h - hOffset) * maxHeight / hDist;
                int val2 = (rect2.h - hOffset) * maxHeight / hDist;
                painter.drawLine(prevPos, maxHeight - val1, i, maxHeight - val2);
            }
            rect1 = rect2;
            prevPos = i;
        }
    }
}
639
640
641
642
643
644
645
646
647
648
649
650
651

void KeyframeImport::importSelectedData()
{
    // Simple double value
    std::shared_ptr<Mlt::Properties> animData = KeyframeModel::getAnimation(m_model, selectedData());
    std::shared_ptr<Mlt::Animation> anim(new Mlt::Animation(animData->get_animation("key")));
    std::shared_ptr<KeyframeModelList> kfrModel = m_model->getKeyframeModel();
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    // Geometry target
    QPoint rectOffset;
    int finalAlign = m_alignCombo->currentIndex();
    QLocale locale;
652
    locale.setNumberOptions(QLocale::OmitGroupSeparator);
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
    for (const auto &ix : m_indexes) {
        // update keyframes in other indexes
        KeyframeModel *km = kfrModel->getKeyModel(ix);
        qDebug()<<"== "<<ix<<" = "<<m_targetCombo->currentData().toModelIndex();
        if (ix == m_targetCombo->currentData().toModelIndex()) {
            // Import our keyframes
            int frame = 0;
            KeyframeImport::ImportRoles convertMode = static_cast<KeyframeImport::ImportRoles> (m_sourceCombo->currentData().toInt());
            for (int i = 0; i < anim->key_count(); i++) {
                frame = anim->key_get_frame(i);
                QVariant current = km->getInterpolatedValue(frame);
                if (convertMode == ImportRoles::SimpleValue) {
                    double dval = animData->anim_get_double("key", frame);
                    km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), dval, true, undo, redo);
                    continue;
                }
                QStringList kfrData = current.toString().split(QLatin1Char(' '));
                // Safety check
                int size = kfrData.size();
                switch (convertMode) {
                    case ImportRoles::FullGeometry:
                    case ImportRoles::HeightOnly:
                    case ImportRoles::WidthOnly:
                        if (size < 4) {
                            continue;
                        }
                        break;
                    case ImportRoles::Position:
                    case ImportRoles::YOnly:
                        if (size < 2) {
                            continue;
                        }
                        break;
                    default:
                        if (size == 0) {
                            continue;
                        }
                        break;
                }
                mlt_rect rect = animData->anim_get_rect("key", frame);
                if (convertMode == ImportRoles::Position) {
                    switch (finalAlign) {
                    case 1:
                        // Align center
                        rect.x += rect.w / 2;
                        rect.y += rect.h / 2;
                        break;
                    case 2:
                        //Align bottom right
                        rect.x += rect.w;
                        rect.y += rect.h;
                        break;
                    default:
                        break;
                    }
                }
                switch (convertMode) {
                    case ImportRoles::FullGeometry:
711
712
713
714
                        kfrData[0] = locale.toString((int)rect.x);
                        kfrData[1] = locale.toString((int)rect.y);
                        kfrData[2] = locale.toString((int)rect.w);
                        kfrData[3] = locale.toString((int)rect.h);
715
716
                        break;
                    case ImportRoles::Position:
717
718
                        kfrData[0] = locale.toString((int)rect.x);
                        kfrData[1] = locale.toString((int)rect.y);
719
720
721
                        break;
                    case ImportRoles::SimpleValue:
                    case ImportRoles::XOnly:
722
                        kfrData[0] = locale.toString((int)rect.x);
723
724
                        break;
                    case ImportRoles::YOnly:
725
                        kfrData[1] = locale.toString((int)rect.y);
726
727
                        break;
                    case ImportRoles::WidthOnly:
728
                        kfrData[2] = locale.toString((int)rect.w);
729
730
                        break;
                    case ImportRoles::HeightOnly:
731
                        kfrData[3] = locale.toString((int)rect.h);
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
                        break;
                    default:
                        break;
                }
                current = kfrData.join(QLatin1Char(' '));
                km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), current, true, undo, redo);
            }
        } else {
            int frame = 0;
            for (int i = 0; i < anim->key_count(); i++) {
                frame = anim->key_get_frame(i);
                //frame += (m_inPoint->getPosition() - m_offsetPoint->getPosition());
                QVariant current = km->getInterpolatedValue(frame);
                km->addKeyframe(GenTime(frame - m_inPoint->getPosition() + m_offsetPoint->getPosition(), pCore->getCurrentFps()), (KeyframeType)KdenliveSettings::defaultkeyframeinterp(), current, true, undo, redo);
            }
        }
    }
    pCore->pushUndo(undo, redo, i18n("Import keyframes from clipboard"));
}

int KeyframeImport::getImportType() const
{
    if (m_simpleTargets.contains(m_targetCombo->currentText())) {
        return -1;
    }
    return m_sourceCombo->currentData().toInt();
}