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

#include "keyframemodellist.hpp"
23
#include "assets/model/assetcommand.hpp"
24
#include "assets/model/assetparametermodel.hpp"
25
#include "core.h"
Nicolas Carion's avatar
Nicolas Carion committed
26
#include "doc/docundostack.hpp"
27
#include "keyframemodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
28
29
#include "klocalizedstring.h"
#include "macros.hpp"
30
#include <kdenlivesettings.h>
31
32

#include <QDebug>
Nicolas Carion's avatar
Nicolas Carion committed
33
#include <utility>
34
KeyframeModelList::KeyframeModelList(std::weak_ptr<AssetParameterModel> model, const QModelIndex &index, std::weak_ptr<DocUndoStack> undo_stack)
Nicolas Carion's avatar
Nicolas Carion committed
35
36
    : m_model(std::move(model))
    , m_undoStack(std::move(undo_stack))
37
38
    , m_lock(QReadWriteLock::Recursive)
{
Nicolas Carion's avatar
Nicolas Carion committed
39
    qDebug() << "Construct keyframemodellist. Checking model:" << m_model.expired();
40
    addParameter(index);
41
42
43
    connect(m_parameters.begin()->second.get(), &KeyframeModel::modelChanged, this, &KeyframeModelList::modelChanged);
}

44
45
46
47
48
ObjectId KeyframeModelList::getOwnerId() const
{
    if (auto ptr = m_model.lock()) {
        return ptr->getOwnerId();
    }
Nicolas Carion's avatar
Nicolas Carion committed
49
    return {};
50
51
}

52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
const QString KeyframeModelList::getAssetId()
{
    if (auto ptr = m_model.lock()) {
        return ptr->getAssetId();
    }
    return {};
}

const QString KeyframeModelList::getAssetRow()
{
    if (auto ptr = m_model.lock()) {
        return ptr->getAssetMltId();
    }
    return QString();
}

68
void KeyframeModelList::addParameter(const QModelIndex &index)
69
{
Nicolas Carion's avatar
Nicolas Carion committed
70
    std::shared_ptr<KeyframeModel> parameter(new KeyframeModel(m_model, index, m_undoStack));
71
72
73
    m_parameters.insert({index, std::move(parameter)});
}

Nicolas Carion's avatar
Nicolas Carion committed
74
bool KeyframeModelList::applyOperation(const std::function<bool(std::shared_ptr<KeyframeModel>, Fun &, Fun &)> &op, const QString &undoString)
75
76
77
78
79
80
81
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

    bool res = true;
Nicolas Carion's avatar
Nicolas Carion committed
82
    for (const auto &param : m_parameters) {
83
84
85
86
87
88
89
90
91
92
93
94
        res = op(param.second, undo, redo);
        if (!res) {
            bool undone = undo();
            Q_ASSERT(undone);
            return res;
        }
    }
    if (res && !undoString.isEmpty()) {
        PUSH_UNDO(undo, redo, undoString);
    }
    return res;
}
95

96
97
98
99
100
bool KeyframeModelList::addKeyframe(GenTime pos, KeyframeType type)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
    bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
Nicolas Carion's avatar
Nicolas Carion committed
101
    auto op = [pos, type](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) {
102
        QVariant value = param->getInterpolatedValue(pos);
103
        return param->addKeyframe(pos, type, value, true, undo, redo);
104
105
106
107
    };
    return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}

108
109
110
111
112
113
bool KeyframeModelList::addKeyframe(int frame, double val)
{
    QWriteLocker locker(&m_lock);
    GenTime pos(frame, pCore->getCurrentFps());
    Q_ASSERT(m_parameters.size() > 0);
    bool update = (m_parameters.begin()->second->hasKeyframe(pos) > 0);
114
115
116
    bool isRectParam = false;
    if (m_inTimelineIndex.isValid()) {
        if (auto ptr = m_model.lock()) {
Nicolas Carion's avatar
Nicolas Carion committed
117
            auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value<ParamType>();
118
119
120
121
122
            if (tp == ParamType::AnimatedRect) {
                isRectParam = true;
            }
        }
    }
123
    auto op = [this, pos, val, isRectParam](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) {
124
        QVariant value;
125
126
127
128
129
130
131
132
133
134
135
136
        if (m_inTimelineIndex.isValid()) {
            if (m_parameters.at(m_inTimelineIndex) == param) {
                if (isRectParam) {
                    value = param->getInterpolatedValue(pos);
                    value = param->updateInterpolated(value, val);
                } else {
                    value = param->getNormalizedValue(val);
                }
            } else {
                value = param->getInterpolatedValue(pos);
            }
        } else if (m_parameters.begin()->second == param) {
137
138
139
140
            value = param->getNormalizedValue(val);
        } else {
            value = param->getInterpolatedValue(pos);
        }
141
        return param->addKeyframe(pos, KeyframeType(KdenliveSettings::defaultkeyframeinterp()), value, true, undo, redo);
142
143
144
145
    };
    return applyOperation(op, update ? i18n("Change keyframe type") : i18n("Add keyframe"));
}

146
147
148
149
bool KeyframeModelList::removeKeyframe(GenTime pos)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
Nicolas Carion's avatar
Nicolas Carion committed
150
    auto op = [pos](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->removeKeyframe(pos, undo, redo); };
151
152
153
    return applyOperation(op, i18n("Delete keyframe"));
}

154
155
156
157
158
159
160
161
162
bool KeyframeModelList::removeKeyframeWithUndo(GenTime pos, Fun &undo, Fun &redo)
{
    bool result = true;
    for (const auto &param : m_parameters) {
        result = result && param.second->removeKeyframe(pos, undo, redo);
    }
    return result;
}

163
164
165
166
167
168
169
170
171
bool KeyframeModelList::duplicateKeyframeWithUndo(GenTime srcPos, GenTime destPos, Fun &undo, Fun &redo)
{
    bool result = true;
    for (const auto &param : m_parameters) {
        result = result && param.second->duplicateKeyframe(srcPos, destPos, undo, redo);
    }
    return result;
}

172
173
174
175
bool KeyframeModelList::removeAllKeyframes()
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
Nicolas Carion's avatar
Nicolas Carion committed
176
    auto op = [](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->removeAllKeyframes(undo, redo); };
177
178
179
    return applyOperation(op, i18n("Delete all keyframes"));
}

180
181
182
183
184
185
186
187
bool KeyframeModelList::removeNextKeyframes(GenTime pos)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
    auto op = [pos](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->removeNextKeyframes(pos, undo, redo); };
    return applyOperation(op, i18n("Delete keyframes"));
}

188
189
190
191
bool KeyframeModelList::moveKeyframe(GenTime oldPos, GenTime pos, bool logUndo)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
192
    auto op = [oldPos, pos](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) { return param->moveKeyframe(oldPos, pos, QVariant(), undo, redo); };
193
    return applyOperation(op, logUndo ? i18nc("@action", "Move keyframe") : QString());
194
195
}

196
197
198
199
200
201
202
203
204
bool KeyframeModelList::moveKeyframeWithUndo(GenTime oldPos, GenTime pos, Fun &undo, Fun &redo)
{
    bool result = true;
    for (const auto &param : m_parameters) {
        result = result && param.second->moveKeyframe(oldPos, pos, QVariant(), undo, redo);
    }
    return result;
}

Nicolas Carion's avatar
Nicolas Carion committed
205
bool KeyframeModelList::updateKeyframe(GenTime oldPos, GenTime pos, const QVariant &normalizedVal, bool logUndo)
206
207
208
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.size() > 0);
209
210
211
    bool isRectParam = false;
    if (m_inTimelineIndex.isValid()) {
        if (auto ptr = m_model.lock()) {
Nicolas Carion's avatar
Nicolas Carion committed
212
            auto tp = ptr->data(m_inTimelineIndex, AssetParameterModel::TypeRole).value<ParamType>();
213
214
215
216
217
218
219
220
221
222
223
            if (tp == ParamType::AnimatedRect) {
                isRectParam = true;
            }
        }
    }
    auto op = [this, oldPos, pos, normalizedVal, isRectParam](std::shared_ptr<KeyframeModel> param, Fun &undo, Fun &redo) {
        QVariant value;
        if (m_inTimelineIndex.isValid()) {
            if (m_parameters.at(m_inTimelineIndex) == param) {
                if (isRectParam) {
                    if (normalizedVal.isValid()) {
224
225
226
227
228
229
                        double newValue = normalizedVal.toDouble();
                        if (auto ptr = m_model.lock()) {
                            if (ptr->getAssetId() != QLatin1String("qtblend")) {
                                newValue *= 100.;
                            }
                        }
230
                        value = param->getInterpolatedValue(oldPos);
231
                        value = param->updateInterpolated(value, newValue);
232
233
234
235
236
237
                    }
                } else {
                    value = normalizedVal;
                }
            }
        } else if (m_parameters.begin()->second == param) {
238
239
240
241
            value = normalizedVal;
        }
        return param->moveKeyframe(oldPos, pos, value, undo, redo);
    };
Yuri Chornoivan's avatar
Yuri Chornoivan committed
242
    return applyOperation(op, logUndo ? i18nc("@action", "Move keyframe") : QString());
243
244
}

245
bool KeyframeModelList::updateKeyframe(GenTime pos, const QVariant &value, const QPersistentModelIndex &index, QUndoCommand *parentCommand)
246
{
247
248
249
250
251
252
    if (singleKeyframe()) {
        bool ok = false;
        Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
        pos = kf.first;
    }
    if (auto ptr = m_model.lock()) {
253
254
255
        auto *command = new AssetKeyframeCommand(ptr, index, value, pos, parentCommand);
        if (parentCommand == nullptr) {
            pCore->pushUndo(command);
256
        } // clang-tidy: else "command" is leaked? no because is was pushed to parentCommand
257
    }
258
    return true; // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
259
260
}

261
262
263
264
265
266
267
268
269
270
271
bool KeyframeModelList::updateKeyframeType(GenTime pos, int type, const QPersistentModelIndex &index)
{
    QWriteLocker locker(&m_lock);
    Q_ASSERT(m_parameters.count(index) > 0);
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };
    if (singleKeyframe()) {
        bool ok = false;
        Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
        pos = kf.first;
    }
272
273
274
275
276
    // Update kf type in all parameters
    bool res = true;
    for (const auto &param : m_parameters) {
        res = res && param.second->updateKeyframeType(pos, type, undo, redo);
    }
277
278
279
280
281
    if (res) {
        PUSH_UNDO(undo, redo, i18n("Update keyframe"));
    }
    return res;
}
282

283
284
285
286
287
288
289
290
291
292
293
294
295
KeyframeType KeyframeModelList::keyframeType(GenTime pos) const
{
    QWriteLocker locker(&m_lock);
    if (singleKeyframe()) {
        bool ok = false;
        Keyframe kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &ok);
        return kf.second;
    }
    bool ok = false;
    Keyframe kf = m_parameters.begin()->second->getKeyframe(pos, &ok);
    return kf.second;
}

296
297
298
299
300
301
302
Keyframe KeyframeModelList::getKeyframe(const GenTime &pos, bool *ok) const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->getKeyframe(pos, ok);
}

303
304
305
306
307
308
309
bool KeyframeModelList::singleKeyframe() const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->singleKeyframe();
}

310
311
312
313
314
315
bool KeyframeModelList::isEmpty() const
{
    READ_LOCK();
    return (m_parameters.size() == 0 || m_parameters.begin()->second->rowCount() == 0);
}

316
317
318
319
320
321
322
323
int KeyframeModelList::count() const
{
    READ_LOCK();
    if (m_parameters.size() > 0)
        return m_parameters.begin()->second->rowCount();
    return 0;
}

324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
Keyframe KeyframeModelList::getNextKeyframe(const GenTime &pos, bool *ok) const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->getNextKeyframe(pos, ok);
}

Keyframe KeyframeModelList::getPrevKeyframe(const GenTime &pos, bool *ok) const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->getPrevKeyframe(pos, ok);
}

Keyframe KeyframeModelList::getClosestKeyframe(const GenTime &pos, bool *ok) const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->getClosestKeyframe(pos, ok);
}

bool KeyframeModelList::hasKeyframe(int frame) const
{
    READ_LOCK();
    Q_ASSERT(m_parameters.size() > 0);
    return m_parameters.begin()->second->hasKeyframe(frame);
}

352
353
void KeyframeModelList::refresh()
{
354
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
355
    for (const auto &param : m_parameters) {
356
357
358
        param.second->refresh();
    }
}
359

360
361
362
363
364
365
366
367
void KeyframeModelList::reset()
{
    QWriteLocker locker(&m_lock);
    for (const auto &param : m_parameters) {
        param.second->reset();
    }
}

Nicolas Carion's avatar
Nicolas Carion committed
368
QVariant KeyframeModelList::getInterpolatedValue(int pos, const QPersistentModelIndex &index) const
369
370
371
372
373
{
    READ_LOCK();
    Q_ASSERT(m_parameters.count(index) > 0);
    return m_parameters.at(index)->getInterpolatedValue(pos);
}
374
375
376

KeyframeModel *KeyframeModelList::getKeyModel()
{
377
378
379
380
381
382
383
384
385
386
    if (m_inTimelineIndex.isValid()) {
        return m_parameters.at(m_inTimelineIndex).get();
    }
    if (auto ptr = m_model.lock()) {
        for (const auto &param : m_parameters) {
            if (ptr->data(param.first, AssetParameterModel::ShowInTimelineRole) == true) {
                m_inTimelineIndex = param.first;
                return param.second.get();
            }
        }
387
388
389
    }
    return nullptr;
}
390

391
392
KeyframeModel *KeyframeModelList::getKeyModel(const QPersistentModelIndex &index)
{
393
    if (m_parameters.size() > 0 && m_parameters.find(index) != m_parameters.end()) {
394
395
396
397
398
        return m_parameters.at(index).get();
    }
    return nullptr;
}

399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
void KeyframeModelList::moveKeyframes(int oldIn, int oldOut, int in, int out, Fun &undo, Fun &redo)
{
    // Get list of keyframes positions
    QList<GenTime> positions = m_parameters.begin()->second->getKeyframePos();
    GenTime offset(in - oldIn, pCore->getCurrentFps());
    if (in > oldIn) {
        // Moving to the right, process in reverse order to prevent collisions
        std::sort(positions.begin(), positions.end(), std::greater<>());
    } else {
        std::sort(positions.begin(), positions.end());
    }
    for (const auto &param : m_parameters) {
        for (auto frame : qAsConst(positions)) {
            param.second->moveKeyframe(frame, frame + offset, QVariant(), undo, redo);
        }
    }
}


418
void KeyframeModelList::resizeKeyframes(int oldIn, int oldOut, int in, int out, int offset, bool adjustFromEnd, Fun &undo, Fun &redo)
419
420
421
{
    bool ok;
    bool ok2;
422
423
    QList<GenTime> positions;
    if (!adjustFromEnd) {
424
        if (offset != 0) {
425
426
427
428
429
430
            // this is an endless resize clip
            GenTime old_in(oldIn, pCore->getCurrentFps());
            GenTime new_in(in + offset, pCore->getCurrentFps());
            getKeyframe(new_in, &ok2);
            positions = m_parameters.begin()->second->getKeyframePos();
            std::sort(positions.begin(), positions.end());
431
            for (const auto &param : m_parameters) {
432
433
434
435
                if (offset > 0) {
                    QVariant value = param.second->getInterpolatedValue(new_in);
                    param.second->updateKeyframe(old_in, value, undo, redo);
                }
Vincent Pinon's avatar
Vincent Pinon committed
436
                for (auto frame : qAsConst(positions)) {
437
438
439
440
441
442
                    if (new_in > GenTime()) {
                        if (frame > new_in) {
                            param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
                            continue;
                        }
                    } else if (frame > GenTime()) {
443
                        param.second->moveKeyframe(frame, frame - new_in, QVariant(), undo, redo);
444
445
446
                        continue;
                    }
                    if (frame != GenTime()) {
447
448
449
450
                        param.second->removeKeyframe(frame, undo, redo);
                    }
                }
            }
451
        } else if (oldIn != in) {
452
453
454
455
456
            GenTime old_in(oldIn, pCore->getCurrentFps());
            GenTime new_in(in, pCore->getCurrentFps());
            Keyframe kf = getKeyframe(old_in, &ok);
            KeyframeType type = kf.second;
            getKeyframe(new_in, &ok2);
457
458
            if (!ok2) {
                // Add new in point
459
                for (const auto &param : m_parameters) {
460
461
                    QVariant value = param.second->getInterpolatedValue(new_in);
                    param.second->addKeyframe(new_in, type, value, true, undo, redo);
462
463
                }
            }
464
465
            if (ok) {
                // Remove previous in point
466
                for (const auto &param : m_parameters) {
467
468
469
470
471
472
473
474
475
476
477
478
                    param.second->removeKeyframe(old_in, undo, redo);
                }
            }
            // Remove all keyframes before in
            bool nextOk = false;
            kf = m_parameters.begin()->second->getNextKeyframe(GenTime(-1), &nextOk);
            GenTime pos;
            while (nextOk) {
                pos = kf.first;
                if (pos < new_in) {
                    for (const auto &param : m_parameters) {
                        param.second->removeKeyframe(pos, undo, redo);
479
                    }
480
481
482
                    kf = m_parameters.begin()->second->getNextKeyframe(pos, &nextOk);
                } else {
                    break;
483
484
                }
            }
485
            // qDebug()<<"/// \n\nKEYS TO DELETE: "<<positions<<"\n------------------------";
486
        }
487
    } else {
488
489
490
491
492
493
494
495
496
        GenTime old_out(oldOut, pCore->getCurrentFps());
        GenTime new_out(out, pCore->getCurrentFps());
        Keyframe kf = getKeyframe(old_out, &ok);
        KeyframeType type = kf.second;
        getKeyframe(new_out, &ok2);
        // Check keyframes after last position
        bool ok3;
        Keyframe toDel = getNextKeyframe(new_out, &ok3);
        if (ok && !ok2) {
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
            // Check if we have only 2 keyframes (in/out), in which case we move the out keyframe to new position
            bool ok4;
            kf = getPrevKeyframe(old_out, &ok4);
            if (ok4) {
                GenTime current_in(oldIn, pCore->getCurrentFps());
                qDebug()<<" = = = = = = = \n\nGOT 2 KF SITUATION: "<<current_in.frames(25)<<" = "<<kf.first.frames(25);
                if (kf.first == current_in) {
                    // We have a 2 keyframes situation, move last one to new out
                    for (const auto &param : m_parameters) {
                        param.second->moveKeyframe(old_out, new_out, QVariant(), undo, redo);
                    }
                    return;
                }
            }
            
512
513
            positions << old_out;
        }
514
515
516
517
        if (toDel.first == GenTime()) {
            // No keyframes
            return;
        }
518
        while (ok3) {
519
            if (!positions.contains(toDel.first)) {
520
521
522
523
                positions << toDel.first;
            }
            toDel = getNextKeyframe(toDel.first, &ok3);
        }
524
        if ((ok || positions.size() > 0) && !ok2) {
525
            for (const auto &param : m_parameters) {
526
527
                QVariant value = param.second->getInterpolatedValue(new_out);
                param.second->addKeyframe(new_out, type, value, true, undo, redo);
Vincent Pinon's avatar
Vincent Pinon committed
528
                for (auto frame : qAsConst(positions)) {
529
530
                    param.second->removeKeyframe(frame, undo, redo);
                }
531
532
533
534
            }
        }
    }
}
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551

void KeyframeModelList::checkConsistency()
{
    if (m_parameters.size() < 2) {
        return;
    }
    // Check keyframes in all parameters
    QList<GenTime> fullList;
    for (const auto &param : m_parameters) {
        QList<GenTime> list = param.second->getKeyframePos();
        for (auto &time : list) {
            if (!fullList.contains(time)) {
                fullList << time;
            }
        }
    }
    Fun local_update = []() { return true; };
552
    auto type = KeyframeType(KdenliveSettings::defaultkeyframeinterp());
553
554
555
556
557
    for (const auto &param : m_parameters) {
        QList<GenTime> list = param.second->getKeyframePos();
        for (auto &time : fullList) {
            if (!list.contains(time)) {
                qDebug()<<" = = = \n\n = = = = \n\nWARNING; MISSING KF DETECTED AT: "<<time.seconds()<<"\n\n= = = \n\n= = =";
558
                pCore->displayMessage(i18n("Missing keyframe detected at %1, automatically re-added", time.seconds()), ErrorMessage);
559
560
561
562
563
564
565
                QVariant missingVal = param.second->getInterpolatedValue(time);
                local_update = param.second->addKeyframe_lambda(time, type, missingVal, false);
                local_update();
            }
        }
    }
}
566
567
568
569
570
571
572
573
574
575

GenTime KeyframeModelList::getPosAtIndex(int ix)
{
    QList<GenTime> positions = m_parameters.begin()->second->getKeyframePos();
    std::sort(positions.begin(), positions.end());
    if (ix < 0 || ix >= positions.count()) {
        return GenTime();
    }
    return positions.at(ix);
}
576
577
578
579
580
581
582
583
584
585

QModelIndex KeyframeModelList::getIndexAtRow(int row)
{
    for (auto &w : m_parameters) {
        if (w.first.row() == row) {
            return w.first;
        }
    }
    return QModelIndex();
}