effectstackmodel.cpp 58.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
/***************************************************************************
 *   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 "effectstackmodel.hpp"
22
#include "assets/keyframes/model/keyframemodellist.hpp"
23
#include "core.h"
24
#include "mainwindow.h"
25
#include "doc/docundostack.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
26
27
#include "effectgroupmodel.hpp"
#include "effectitemmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
28
#include "effects/effectsrepository.hpp"
29
#include "macros.hpp"
30
31
#include "timeline2/model/timelinemodel.hpp"
#include <profiles/profilemodel.hpp>
Nicolas Carion's avatar
Nicolas Carion committed
32
#include <stack>
Nicolas Carion's avatar
linting    
Nicolas Carion committed
33
#include <utility>
Nicolas Carion's avatar
Nicolas Carion committed
34
#include <vector>
35

36
EffectStackModel::EffectStackModel(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack)
37
    : AbstractTreeModel()
38
    , m_effectStackEnabled(true)
Nicolas Carion's avatar
Nicolas Carion committed
39
    , m_ownerId(std::move(ownerId))
Nicolas Carion's avatar
Nicolas Carion committed
40
    , m_undoStack(std::move(undo_stack))
41
    , m_lock(QReadWriteLock::Recursive)
42
    , m_loadingExisting(false)
43
{
44
    m_masterService = std::move(service);
45
46
}

47
std::shared_ptr<EffectStackModel> EffectStackModel::construct(std::weak_ptr<Mlt::Service> service, ObjectId ownerId, std::weak_ptr<DocUndoStack> undo_stack)
48
{
Nicolas Carion's avatar
Nicolas Carion committed
49
    std::shared_ptr<EffectStackModel> self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack)));
50
    self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true);
51
    return self;
52
53
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
54
55
void EffectStackModel::resetService(std::weak_ptr<Mlt::Service> service)
{
56
    QWriteLocker locker(&m_lock);
57
58
    m_masterService = std::move(service);
    m_childServices.clear();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
59
60
    // replant all effects in new service
    for (int i = 0; i < rootItem->childCount(); ++i) {
61
        std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plant(m_masterService);
62
63
64
65
66
    }
}

void EffectStackModel::addService(std::weak_ptr<Mlt::Service> service)
{
67
    QWriteLocker locker(&m_lock);
68
69
70
71
72
73
74
75
76
77
    m_childServices.emplace_back(std::move(service));
    for (int i = 0; i < rootItem->childCount(); ++i) {
        std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->plantClone(m_childServices.back());
    }
}

void EffectStackModel::loadService(std::weak_ptr<Mlt::Service> service)
{
    QWriteLocker locker(&m_lock);
    m_childServices.emplace_back(std::move(service));
78
    for (int i = 0; i < rootItem->childCount(); ++i) {
79
        std::static_pointer_cast<EffectItemModel>(rootItem->child(i))->loadClone(m_childServices.back());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
80
81
    }
}
82

Nicolas Carion's avatar
Nicolas Carion committed
83
void EffectStackModel::removeService(const std::shared_ptr<Mlt::Service> &service)
84
{
85
    QWriteLocker locker(&m_lock);
86
    std::vector<int> to_delete;
87
88
    for (int i = int(m_childServices.size()) - 1; i >= 0; --i) {
        auto ptr = m_childServices[uint(i)].lock();
89
        if (ptr && service->get_int("_childid") == ptr->get_int("_childid")) {
90
91
92
            for (int j = 0; j < rootItem->childCount(); ++j) {
                std::static_pointer_cast<EffectItemModel>(rootItem->child(j))->unplantClone(ptr);
            }
93
94
95
96
            to_delete.push_back(i);
        }
    }
    for (int i : to_delete) {
97
        m_childServices.erase(m_childServices.begin() + i);
98
99
    }
}
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
100

101
102
103
void EffectStackModel::removeCurrentEffect()
{
    int ix = 0;
104
    if (auto ptr = m_masterService.lock()) {
105
106
107
108
109
110
111
112
113
114
115
        ix = ptr->get_int("kdenlive:activeeffect");
    }
    if (ix < 0) {
        return;
    }
    std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(ix));
    if (effect) {
        removeEffect(effect);
    }
}

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
void EffectStackModel::removeAllEffects(Fun &undo, Fun & redo)
{
    QWriteLocker locker(&m_lock);
    int current = -1;
    if (auto srv = m_masterService.lock()) {
        current = srv->get_int("kdenlive:activeeffect");
    }
    while (rootItem->childCount() > 0) {
        std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(0));
        int parentId = -1;
        if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
        Fun local_undo = addItem_lambda(effect, parentId);
        Fun local_redo = removeItem_lambda(effect->getId());
        local_redo();
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    }
    std::unordered_set<int> fadeIns = m_fadeIns;
    std::unordered_set<int> fadeOuts = m_fadeOuts;
    Fun undo_current = [this, current, fadeIns, fadeOuts]() {
        if (auto srv = m_masterService.lock()) {
            srv->set("kdenlive:activeeffect", current);
        }
        m_fadeIns = fadeIns;
        m_fadeOuts = fadeOuts;
        QVector<int> roles = {TimelineModel::EffectNamesRole};
        if (!m_fadeIns.empty()) {
            roles << TimelineModel::FadeInRole;
        }
        if (!m_fadeOuts.empty()) {
            roles << TimelineModel::FadeOutRole;
        }
        emit dataChanged(QModelIndex(), QModelIndex(), roles);
        pCore->updateItemKeyframes(m_ownerId);
        return true;
    };
    Fun redo_current = [this]() {
        if (auto srv = m_masterService.lock()) {
            srv->set("kdenlive:activeeffect", -1);
        }
        QVector<int> roles = {TimelineModel::EffectNamesRole};
        if (!m_fadeIns.empty()) {
            roles << TimelineModel::FadeInRole;
        }
        if (!m_fadeOuts.empty()) {
            roles << TimelineModel::FadeOutRole;
        }
        m_fadeIns.clear();
        m_fadeOuts.clear();
        emit dataChanged(QModelIndex(), QModelIndex(), roles);
        pCore->updateItemKeyframes(m_ownerId);
        return true;
    };
    redo_current();
    PUSH_LAMBDA(redo_current, redo);
    PUSH_LAMBDA(undo_current, undo);
}

Nicolas Carion's avatar
Nicolas Carion committed
173
void EffectStackModel::removeEffect(const std::shared_ptr<EffectItemModel> &effect)
174
{
175
    qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!";
176
    QWriteLocker locker(&m_lock);
Nicolas Carion's avatar
Nicolas Carion committed
177
    Q_ASSERT(m_allItems.count(effect->getId()) > 0);
178
179
    int parentId = -1;
    if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
180
    int current = 0;
181
182
183
184
    if (auto srv = m_masterService.lock()) {
        current = srv->get_int("kdenlive:activeeffect");
        if (current >= rootItem->childCount() - 1) {
            srv->set("kdenlive:activeeffect", --current);
185
186
        }
    }
187
    int currentRow = effect->row();
188
    Fun undo = addItem_lambda(effect, parentId);
189
190
191
192
    if (currentRow != rowCount() - 1) {
        Fun move = moveItem_lambda(effect->getId(), currentRow, true);
        PUSH_LAMBDA(move, undo);
    }
193
194
195
    Fun redo = removeItem_lambda(effect->getId());
    bool res = redo();
    if (res) {
Nicolas Carion's avatar
Nicolas Carion committed
196
197
198
199
200
201
        int inFades = int(m_fadeIns.size());
        int outFades = int(m_fadeOuts.size());
        m_fadeIns.erase(effect->getId());
        m_fadeOuts.erase(effect->getId());
        inFades = int(m_fadeIns.size()) - inFades;
        outFades = int(m_fadeOuts.size()) - outFades;
202
        QString effectName = EffectsRepository::get()->getName(effect->getAssetId());
203
        Fun update = [this, current, inFades, outFades]() {
204
            // Required to build the effect view
205
206
207
208
209
210
211
            if (current < 0 || rowCount() == 0) {
                // Stack is now empty
                emit dataChanged(QModelIndex(), QModelIndex(), {});
            } else {
                QVector<int> roles = {TimelineModel::EffectNamesRole};
                if (inFades < 0) {
                    roles << TimelineModel::FadeInRole;
212
                }
213
214
215
                if (outFades < 0) {
                    roles << TimelineModel::FadeOutRole;
                }
216
                qDebug() << "// EMITTING UNDO DATA CHANGE: " << roles;
217
                emit dataChanged(QModelIndex(), QModelIndex(), roles);
218
            }
219
            // TODO: only update if effect is fade or keyframe
220
            /*if (inFades < 0) {
221
                pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
222
            } else if (outFades < 0) {
223
                pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
224
            }*/
225
226
227
            pCore->updateItemKeyframes(m_ownerId);
            return true;
        };
228
        Fun update2 = [this, inFades, outFades]() {
229
            // Required to build the effect view
230
            QVector<int> roles = {TimelineModel::EffectNamesRole};
231
232
            // TODO: only update if effect is fade or keyframe
            if (inFades < 0) {
233
                roles << TimelineModel::FadeInRole;
234
            } else if (outFades < 0) {
235
                roles << TimelineModel::FadeOutRole;
236
            }
237
            qDebug() << "// EMITTING REDO DATA CHANGE: " << roles;
238
            emit dataChanged(QModelIndex(), QModelIndex(), roles);
239
240
241
            pCore->updateItemKeyframes(m_ownerId);
            return true;
        };
242
243
        update();
        PUSH_LAMBDA(update, redo);
244
        PUSH_LAMBDA(update2, undo);
245
        PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName));
246
    } else {
247
        qDebug() << "..........FAILED EFFECT DELETION";
248
    }
249
250
}

251
bool EffectStackModel::copyXmlEffect(QDomElement effect)
252
253
254
{
    std::function<bool(void)> undo = []() { return true; };
    std::function<bool(void)> redo = []() { return true; };
255
256
257
    bool result = fromXml(effect, undo, redo);
    if (result) {
        PUSH_UNDO(undo, redo, i18n("Copy effect"));
258
259
260
261
    }
    return result;
}

262
263
QDomElement EffectStackModel::toXml(QDomDocument &document)
{
264
    QDomElement container = document.createElement(QStringLiteral("effects"));
265
266
    int currentIn = pCore->getItemIn(m_ownerId);
    container.setAttribute(QStringLiteral("parentIn"), currentIn);
267
    for (int i = 0; i < rootItem->childCount(); ++i) {
268
269
270
        std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
        QDomElement sub = document.createElement(QStringLiteral("effect"));
        sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
271
272
273
274
275
276
        int filterIn = sourceEffect->filter().get_int("in");
        int filterOut = sourceEffect->filter().get_int("out");
        if (filterOut > filterIn) {
            sub.setAttribute(QStringLiteral("in"), filterIn);
            sub.setAttribute(QStringLiteral("out"), filterOut);
        }
277
278
279
280
281
282
283
        QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
        for (const QString &param : passProps) {
            int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
            if (paramVal > 0) {
                Xml::setXmlProperty(sub, param, QString::number(paramVal));
            }
        }
284
        QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters();
Vincent Pinon's avatar
Vincent Pinon committed
285
        for (const auto &param : qAsConst(params)) {
286
            Xml::setXmlProperty(sub, param.first, param.second.toString());
287
288
        }
        container.appendChild(sub);
289
290
    }
    return container;
291
292
}

293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
QDomElement EffectStackModel::rowToXml(int row, QDomDocument &document)
{
    QDomElement container = document.createElement(QStringLiteral("effects"));
    if (row < 0 || row >= rootItem->childCount()) {
        return container;
    }
    int currentIn = pCore->getItemIn(m_ownerId);
    container.setAttribute(QStringLiteral("parentIn"), currentIn);
    std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(rootItem->child(row));
    QDomElement sub = document.createElement(QStringLiteral("effect"));
    sub.setAttribute(QStringLiteral("id"), sourceEffect->getAssetId());
    int filterIn = sourceEffect->filter().get_int("in");
    int filterOut = sourceEffect->filter().get_int("out");
    if (filterOut > filterIn) {
        sub.setAttribute(QStringLiteral("in"), filterIn);
        sub.setAttribute(QStringLiteral("out"), filterOut);
    }
    QStringList passProps {QStringLiteral("disable"), QStringLiteral("kdenlive:collapsed")};
    for (const QString &param : passProps) {
        int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
        if (paramVal > 0) {
            Xml::setXmlProperty(sub, param, QString::number(paramVal));
        }
    }
    QVector<QPair<QString, QVariant>> params = sourceEffect->getAllParameters();
Vincent Pinon's avatar
Vincent Pinon committed
318
    for (const auto &param : qAsConst(params)) {
319
        Xml::setXmlProperty(sub, param.first, param.second.toString());
320
321
322
323
324
    }
    container.appendChild(sub);
    return container;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
325
bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo)
326
327
{
    QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect"));
328
    int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt();
329
    qDebug()<<"// GOT PREVIOUS PARENTIN: "<<parentIn<<"\n\n=======\n=======\n\n";
330
    int currentIn = pCore->getItemIn(m_ownerId);
331
    PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
332
    bool effectAdded = false;
333
334
335
    for (int i = 0; i < nodeList.count(); ++i) {
        QDomElement node = nodeList.item(i).toElement();
        const QString effectId = node.attribute(QStringLiteral("id"));
336
337
        AssetListType::AssetType type = EffectsRepository::get()->getType(effectId);
        bool isAudioEffect = type == AssetListType::AssetType::Audio || type == AssetListType::AssetType::CustomAudio;
338
339
340
341
342
343
344
        if (isAudioEffect) {
            if (state != PlaylistState::AudioOnly) {
                continue;
            }
        } else if (state != PlaylistState::VideoOnly) {
            continue;
        }
345
346
347
348
        if (EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
            pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), InformationMessage);
            return false;
        }
349
350
351
352
353
        bool effectEnabled = true;
        if (Xml::hasXmlProperty(node, QLatin1String("disable"))) {
            effectEnabled = Xml::getXmlProperty(node, QLatin1String("disable")).toInt() != 1;
        }
        auto effect = EffectItemModel::construct(effectId, shared_from_this(), effectEnabled);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
354
355
356
357
358
        const QString in = node.attribute(QStringLiteral("in"));
        const QString out = node.attribute(QStringLiteral("out"));
        if (!out.isEmpty()) {
            effect->filter().set("in", in.toUtf8().constData());
            effect->filter().set("out", out.toUtf8().constData());
359
        }
360
        QStringList keyframeParams = effect->getKeyframableParameters();
361
362
363
364
        QVector<QPair<QString, QVariant>> parameters;
        QDomNodeList params = node.elementsByTagName(QStringLiteral("property"));
        for (int j = 0; j < params.count(); j++) {
            QDomElement pnode = params.item(j).toElement();
365
            const QString pName = pnode.attribute(QStringLiteral("name"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
366
367
368
            if (pName == QLatin1String("in") || pName == QLatin1String("out")) {
                continue;
            }
369
            if (keyframeParams.contains(pName)) {
Yuri Chornoivan's avatar
Yuri Chornoivan committed
370
                // This is a keyframable parameter, fix offset
371
372
373
374
375
                QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn);
                parameters.append(QPair<QString, QVariant>(pName, QVariant(pValue)));
            } else {
                parameters.append(QPair<QString, QVariant>(pName, QVariant(pnode.text())));
            }
376
377
378
379
380
        }
        effect->setParameters(parameters);
        Fun local_undo = removeItem_lambda(effect->getId());
        // TODO the parent should probably not always be the root
        Fun local_redo = addItem_lambda(effect, rootItem->getId());
381
        effect->prepareKeyframes();
382
383
384
        connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
        connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
        if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
Nicolas Carion's avatar
Nicolas Carion committed
385
            m_fadeIns.insert(effect->getId());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
386
387
388
            int duration = effect->filter().get_length() - 1;
            effect->filter().set("in", currentIn);
            effect->filter().set("out", currentIn + duration);
389
        } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
Nicolas Carion's avatar
Nicolas Carion committed
390
            m_fadeOuts.insert(effect->getId());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
391
392
393
394
            int duration = effect->filter().get_length() - 1;
            int filterOut = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
            effect->filter().set("in", filterOut - duration);
            effect->filter().set("out", filterOut);
395
396
        }
        local_redo();
397
        effectAdded = true;
398
399
        UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
    }
400
    if (effectAdded) {
401
        Fun update = [this]() {
402
            emit dataChanged(QModelIndex(), QModelIndex(), {});
403
404
405
406
407
408
            return true;
        };
        update();
        PUSH_LAMBDA(update, redo);
        PUSH_LAMBDA(update, undo);
    }
409
    return effectAdded;
410
411
}

412
bool EffectStackModel::copyEffect(const std::shared_ptr<AbstractEffectItem> &sourceItem, PlaylistState::ClipState state, bool logUndo)
413
{
414
    QWriteLocker locker(&m_lock);
415
    if (sourceItem->childCount() > 0) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
416
        // TODO: group
417
418
419
420
421
422
423
424
425
426
        return false;
    }
    bool audioEffect = sourceItem->isAudio();
    if (audioEffect) {
        if (state == PlaylistState::VideoOnly) {
            // This effect cannot be used
            return false;
        }
    } else if (state == PlaylistState::AudioOnly) {
        return false;
427
428
    }
    std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(sourceItem);
429
    const QString effectId = sourceEffect->getAssetId();
430
431
432
433
    if (EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
        pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), InformationMessage);
        return false;
    }
434
435
    bool enabled = sourceEffect->isEnabled();
    auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled);
436
    effect->setParameters(sourceEffect->getAllParameters());
437
438
439
    if (!enabled) {
        effect->filter().set("disable", 1);
    }
440
441
    effect->filter().set("in", sourceEffect->filter().get_int("in"));
    effect->filter().set("out", sourceEffect->filter().get_int("out"));
442
    Fun local_undo = removeItem_lambda(effect->getId());
443
    // TODO the parent should probably not always be the root
444
    Fun local_redo = addItem_lambda(effect, rootItem->getId());
445
    effect->prepareKeyframes();
446
    connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
447
    connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
448
    QVector<int> roles = {TimelineModel::EffectNamesRole};
449
    if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
Nicolas Carion's avatar
Nicolas Carion committed
450
        m_fadeIns.insert(effect->getId());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
451
452
453
454
        int duration = effect->filter().get_length() - 1;
        int in = pCore->getItemIn(m_ownerId);
        effect->filter().set("in", in);
        effect->filter().set("out", in + duration);
455
        roles << TimelineModel::FadeInRole;
456
    } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
Nicolas Carion's avatar
Nicolas Carion committed
457
        m_fadeOuts.insert(effect->getId());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
458
459
460
461
        int duration = effect->filter().get_length() - 1;
        int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
        effect->filter().set("in", out - duration);
        effect->filter().set("out", out);
462
        roles << TimelineModel::FadeOutRole;
463
    }
464
465
    bool res = local_redo();
    if (res) {
466
467
        Fun update = [this, roles]() {
            emit dataChanged(QModelIndex(), QModelIndex(), roles);
468
469
            return true;
        };
470
471
472
473
474
475
        update();
        if (logUndo) {
            PUSH_LAMBDA(update, local_redo);
            PUSH_LAMBDA(update, local_undo);
            pCore->pushUndo(local_undo, local_redo, i18n("Paste effect"));
        }
476
    }
477
    return res;
478
479
}

480
bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent)
481
{
482
    QWriteLocker locker(&m_lock);
483
484
485
486
    if (EffectsRepository::get()->isUnique(effectId) && hasEffect(effectId))  {
        pCore->displayMessage(i18n("Effect %1 cannot be added twice.", EffectsRepository::get()->getName(effectId)), InformationMessage);
        return false;
    }
487
488
    std::unordered_set<int> previousFadeIn = m_fadeIns;
    std::unordered_set<int> previousFadeOut = m_fadeOuts;
489
490
491
492
    if (EffectsRepository::get()->isGroup(effectId)) {
        QDomElement doc = EffectsRepository::get()->getXml(effectId);
        return copyXmlEffect(doc);
    }
493
    auto effect = EffectItemModel::construct(effectId, shared_from_this());
494
495
496
497
498
499
500
501
502
503
    PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
    if (effect->isAudio()) {
        if (state == PlaylistState::VideoOnly) {
            // Cannot add effect to this clip
            return false;
        }
    } else if (state == PlaylistState::AudioOnly) {
        // Cannot add effect to this clip
        return false;
    }
504
505
506
    Fun undo = removeItem_lambda(effect->getId());
    // TODO the parent should probably not always be the root
    Fun redo = addItem_lambda(effect, rootItem->getId());
507
    effect->prepareKeyframes();
508
    connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
509
    connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
510
511
    int currentActive = getActiveEffect();
    if (makeCurrent) {
512
513
        if (auto srvPtr = m_masterService.lock()) {
            srvPtr->set("kdenlive:activeeffect", rowCount());
514
515
        }
    }
516
517
    bool res = redo();
    if (res) {
518
519
        int inFades = 0;
        int outFades = 0;
520
        if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
521
522
523
524
            int duration = effect->filter().get_length() - 1;
            int in = pCore->getItemIn(m_ownerId);
            effect->filter().set("in", in);
            effect->filter().set("out", in + duration);
525
            inFades++;
526
        } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
527
528
529
530
            /*int duration = effect->filter().get_length() - 1;
            int out = pCore->getItemIn(m_ownerId) + pCore->getItemDuration(m_ownerId) - 1;
            effect->filter().set("in", out - duration);
            effect->filter().set("out", out);*/
531
            outFades++;
532
533
        } else if (m_ownerId.first == ObjectType::TimelineTrack) {
            effect->filter().set("out", pCore->getItemDuration(m_ownerId));
534
        }
535
        Fun update = [this, inFades, outFades]() {
536
            // TODO: only update if effect is fade or keyframe
537
            QVector<int> roles = {TimelineModel::EffectNamesRole};
538
            if (inFades > 0) {
539
                roles << TimelineModel::FadeInRole;
540
            } else if (outFades > 0) {
541
                roles << TimelineModel::FadeOutRole;
542
            }
543
            pCore->updateItemKeyframes(m_ownerId);
544
            emit dataChanged(QModelIndex(), QModelIndex(), roles);
545
546
            return true;
        };
547
548
549
550
551
552
553
554
555
556
557
558
559
560
        Fun update_undo = [this, inFades, outFades, previousFadeIn, previousFadeOut]() {
            // TODO: only update if effect is fade or keyframe
            QVector<int> roles = {TimelineModel::EffectNamesRole};
            if (inFades > 0) {
                m_fadeIns = previousFadeIn;
                roles << TimelineModel::FadeInRole;
            } else if (outFades > 0) {
                m_fadeOuts = previousFadeOut;
                roles << TimelineModel::FadeOutRole;
            }
            pCore->updateItemKeyframes(m_ownerId);
            emit dataChanged(QModelIndex(), QModelIndex(), roles);
            return true;
        };
561
        update();
562
        PUSH_LAMBDA(update, redo);
563
        PUSH_LAMBDA(update_undo, undo);
564
        PUSH_UNDO(undo, redo, i18n("Add effect %1", EffectsRepository::get()->getName(effectId)));
565
    } else if (makeCurrent) {
566
567
        if (auto srvPtr = m_masterService.lock()) {
            srvPtr->set("kdenlive:activeeffect", currentActive);
568
        }
569
    }
570
    return res;
571
572
}

573
574
bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo,
                                         bool logUndo)
575
{
576
    QWriteLocker locker(&m_lock);
577
578
579
    const int fadeInDuration = getFadePosition(true);
    const int fadeOutDuration = getFadePosition(false);
    int out = newIn + duration;
580
581
582
583
584
585
586
    for (const auto &leaf : rootItem->getLeaves()) {
        std::shared_ptr<AbstractEffectItem> item = std::static_pointer_cast<AbstractEffectItem>(leaf);
        if (item->effectItemType() == EffectItemType::Group) {
            // probably an empty group, ignore
            continue;
        }
        std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(leaf);
Nicolas Carion's avatar
Nicolas Carion committed
587
        if (fadeInDuration > 0 && m_fadeIns.count(leaf->getId()) > 0) {
588
589
            int oldEffectIn = qMax(0, effect->filter().get_in());
            int oldEffectOut = effect->filter().get_out();
590
            qDebug() << "--previous effect: " << oldEffectIn << "-" << oldEffectOut;
591
592
            int effectDuration = qMin(effect->filter().get_length() - 1, duration);
            if (!adjustFromEnd && (oldIn != newIn || duration != oldDuration)) {
593
                // Clip start was resized, adjust effect in / out
594
                Fun operation = [effect, newIn, effectDuration, logUndo]() {
595
                    effect->setParameter(QStringLiteral("in"), newIn, false);
596
                    effect->setParameter(QStringLiteral("out"), newIn + effectDuration, logUndo);
597
                    qDebug() << "--new effect: " << newIn << "-" << newIn + effectDuration;
598
599
                    return true;
                };
600
601
602
603
                bool res = operation();
                if (!res) {
                    return false;
                }
604
                Fun reverse = [effect, oldEffectIn, oldEffectOut, logUndo]() {
605
                    effect->setParameter(QStringLiteral("in"), oldEffectIn, false);
606
                    effect->setParameter(QStringLiteral("out"), oldEffectOut, logUndo);
607
608
609
610
611
612
613
614
615
616
617
                    return true;
                };
                PUSH_LAMBDA(operation, redo);
                PUSH_LAMBDA(reverse, undo);
            } else if (effectDuration < oldEffectOut - oldEffectIn || (logUndo && effect->filter().get_int("_refout") > 0)) {
                // Clip length changed, shorter than effect length so resize
                int referenceEffectOut = effect->filter().get_int("_refout");
                if (referenceEffectOut <= 0) {
                    referenceEffectOut = oldEffectOut;
                    effect->filter().set("_refout", referenceEffectOut);
                }
618
                Fun operation = [effect, oldEffectIn, effectDuration, logUndo]() {
619
                    effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo);
620
621
                    return true;
                };
622
623
624
625
626
                bool res = operation();
                if (!res) {
                    return false;
                }
                if (logUndo) {
627
                    Fun reverse = [effect, referenceEffectOut]() {
628
                        effect->setParameter(QStringLiteral("out"), referenceEffectOut, true);
629
                        effect->filter().set("_refout", (char *)nullptr);
630
631
632
633
634
635
                        return true;
                    };
                    PUSH_LAMBDA(operation, redo);
                    PUSH_LAMBDA(reverse, undo);
                }
            }
Nicolas Carion's avatar
Nicolas Carion committed
636
        } else if (fadeOutDuration > 0 && m_fadeOuts.count(leaf->getId()) > 0) {
637
            int effectDuration = qMin(fadeOutDuration, duration);
Vincent Pinon's avatar
Vincent Pinon committed
638
639
            int newFadeIn = out - effectDuration;
            int oldFadeIn = effect->filter().get_int("in");
640
641
642
            int oldOut = effect->filter().get_int("out");
            int referenceEffectIn = effect->filter().get_int("_refin");
            if (referenceEffectIn <= 0) {
Vincent Pinon's avatar
Vincent Pinon committed
643
                referenceEffectIn = oldFadeIn;
644
645
                effect->filter().set("_refin", referenceEffectIn);
            }
646
            Fun operation = [effect, newFadeIn, out, logUndo]() {
Vincent Pinon's avatar
Vincent Pinon committed
647
                effect->setParameter(QStringLiteral("in"), newFadeIn, false);
648
                effect->setParameter(QStringLiteral("out"), out, logUndo);
649
650
                return true;
            };
651
652
653
654
655
            bool res = operation();
            if (!res) {
                return false;
            }
            if (logUndo) {
656
                Fun reverse = [effect, referenceEffectIn, oldOut]() {
657
                    effect->setParameter(QStringLiteral("in"), referenceEffectIn, false);
658
                    effect->setParameter(QStringLiteral("out"), oldOut, true);
659
                    effect->filter().set("_refin", (char *)nullptr);
660
661
662
663
664
                    return true;
                };
                PUSH_LAMBDA(operation, redo);
                PUSH_LAMBDA(reverse, undo);
            }
665
666
        } else {
            // Not a fade effect, check for keyframes
667
668
            std::shared_ptr<KeyframeModelList> keyframes = effect->getKeyframeModel();
            if (keyframes != nullptr) {
669
                // Effect has keyframes, update these
670
                keyframes->resizeKeyframes(oldIn, oldIn + oldDuration, newIn, out - 1, offset, adjustFromEnd, undo, redo);
671
                QModelIndex index = getIndexFromItem(effect);
672
                Fun refresh = [effect, index]() {
Vincent Pinon's avatar
Vincent Pinon committed
673
                    emit effect->dataChanged(index, index, QVector<int>());
674
675
676
677
678
                    return true;
                };
                refresh();
                PUSH_LAMBDA(refresh, redo);
                PUSH_LAMBDA(refresh, undo);
679
            } else {
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
                qDebug() << "// NULL Keyframes---------";
            }
            if (m_ownerId.first == ObjectType::TimelineTrack) {
                int oldEffectOut = effect->filter().get_out();
                Fun operation = [effect, out, logUndo]() {
                    effect->setParameter(QStringLiteral("out"), out, logUndo);
                    return true;
                };
                bool res = operation();
                if (!res) {
                    return false;
                }
                if (logUndo) {
                    Fun reverse = [effect, oldEffectOut]() {
                        effect->setParameter(QStringLiteral("out"), oldEffectOut, true);
695
696
                        return true;
                    };
697
698
                    PUSH_LAMBDA(operation, redo);
                    PUSH_LAMBDA(reverse, undo);
699
                }
700
            }
701
702
703
        }
    }
    return true;
704
705
}

706
bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade, bool logUndo)
707
{
708
    QWriteLocker locker(&m_lock);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
709
710
    if (fromStart) {
        // Fade in
Nicolas Carion's avatar
Nicolas Carion committed
711
        if (m_fadeIns.empty()) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
712
713
714
715
716
717
718
            if (audioFade) {
                appendEffect(QStringLiteral("fadein"));
            }
            if (videoFade) {
                appendEffect(QStringLiteral("fade_from_black"));
            }
        }
Nicolas Carion's avatar
Nicolas Carion committed
719
        QList<QModelIndex> indexes;
720
        auto ptr = m_masterService.lock();
721
722
723
724
        int in = 0;
        if (ptr) {
            in = ptr->get_int("in");
        }
725
        int oldDuration = -1;
726
        for (int i = 0; i < rootItem->childCount(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
727
            if (m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) {
728
                std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
729
730
731
                if (oldDuration == -1) {
                    oldDuration = effect->filter().get_length();
                }
732
                effect->filter().set("in", in);
733
                duration = qMin(pCore->getItemDuration(m_ownerId), duration);
734
                effect->filter().set("out", in + duration);
735
736
                indexes << getIndexFromItem(effect);
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
737
        }
738
739
        if (!indexes.isEmpty()) {
            emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
740
            pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
741
742
            if (videoFade) {
                int min = pCore->getItemPosition(m_ownerId);
743
                QPair<int, int> range = {min, min + qMax(duration, oldDuration)};
744
745
746
747
748
                pCore->refreshProjectRange(range);
                if (logUndo) {
                    pCore->invalidateRange(range);
                }
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
749
750
751
        }
    } else {
        // Fade out
Nicolas Carion's avatar
Nicolas Carion committed
752
        if (m_fadeOuts.empty()) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
753
754
755
756
757
758
            if (audioFade) {
                appendEffect(QStringLiteral("fadeout"));
            }
            if (videoFade) {
                appendEffect(QStringLiteral("fade_to_black"));
            }
759
        }
760
        int in = 0;
761
        auto ptr = m_masterService.lock();
762
763
764
        if (ptr) {
            in = ptr->get_int("in");
        }
765
        int itemDuration = pCore->getItemDuration(m_ownerId);
766
        int out = in + itemDuration - 1;
767
        int oldDuration = -1;
Nicolas Carion's avatar
Nicolas Carion committed
768
        QList<QModelIndex> indexes;
769
        for (int i = 0; i < rootItem->childCount(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
770
            if (m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) {
771
                std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
772
773
774
                if (oldDuration == -1) {
                    oldDuration = effect->filter().get_length();
                }
775
                effect->filter().set("out", out);
776
                duration = qMin(itemDuration, duration);
777
778
779
                effect->filter().set("in", out - duration);
                indexes << getIndexFromItem(effect);
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
780
        }
781
782
        if (!indexes.isEmpty()) {
            emit dataChanged(indexes.first(), indexes.last(), QVector<int>());
783
            pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
784
785
            if (videoFade) {
                int min = pCore->getItemPosition(m_ownerId);
786
                QPair<int, int> range = {min + itemDuration - qMax(duration, oldDuration), min + itemDuration};
787
788
789
790
791
                pCore->refreshProjectRange(range);
                if (logUndo) {
                    pCore->invalidateRange(range);
                }
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
792
        }
793
794
795
796
    }
    return true;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
797
int EffectStackModel::getFadePosition(bool fromStart)
798
{
799
    QWriteLocker locker(&m_lock);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
800
    if (fromStart) {
Nicolas Carion's avatar
Nicolas Carion committed
801
        if (m_fadeIns.empty()) {
802
803
804
            return 0;
        }
        for (int i = 0; i < rootItem->childCount(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
805
            if (*(m_fadeIns.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) {
806
                std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
807
                return effect->filter().get_length() - 1;
808
            }
809
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
810
    } else {
Nicolas Carion's avatar
Nicolas Carion committed
811
        if (m_fadeOuts.empty()) {
812
813
814
            return 0;
        }
        for (int i = 0; i < rootItem->childCount(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
815
            if (*(m_fadeOuts.begin()) == std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) {
816
                std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
817
                return effect->filter().get_length() - 1;
818
            }
819
820
821
822
823
        }
    }
    return 0;
}

824
bool EffectStackModel::removeFade(bool fromStart)
825
{
826
    QWriteLocker locker(&m_lock);
827
    std::vector<int> toRemove;
828
    for (int i = 0; i < rootItem->childCount(); ++i) {
Nicolas Carion's avatar
Nicolas Carion committed
829
830
        if ((fromStart && m_fadeIns.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0) ||
            (!fromStart && m_fadeOuts.count(std::static_pointer_cast<TreeItem>(rootItem->child(i))->getId()) > 0)) {
831
            toRemove.push_back(i);
832
833
        }
    }
834
835
    // Let's put index in reverse order so we don't mess when deleting
    std::reverse( toRemove.begin(), toRemove.end() );
836
    for (int i : toRemove) {
837
838
        std::shared_ptr<EffectItemModel> effect = std::static_pointer_cast<EffectItemModel>(rootItem->child(i));
        removeEffect(effect);
839
840
841
842
    }
    return true;
}

Nicolas Carion's avatar
Nicolas Carion committed
843
void EffectStackModel::moveEffect(int destRow, const std::shared_ptr<AbstractEffectItem> &item)
844
{
845
    QWriteLocker locker(&m_lock);
846
847
848
849
850
851
    Q_ASSERT(m_allItems.count(item->getId()) > 0);
    int oldRow = item->row();
    Fun undo = moveItem_lambda(item->getId(), oldRow);
    Fun redo = moveItem_lambda(item->getId(), destRow);
    bool res = redo();
    if (res) {
852
        Fun update = [this]() {
Vincent Pinon's avatar
Vincent Pinon committed
853
            emit this->dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectNamesRole});
854
855
856
857
            return true;
        };
        update();
        UPDATE_UNDO_REDO(update, update, undo, redo);
858
        auto effectId = std::static_pointer_cast<EffectItemModel>(item)->getAssetId();
Yuri Chornoivan's avatar
Yuri Chornoivan committed
859
        PUSH_UNDO(undo, redo, i18n("Move effect %1", EffectsRepository::get()->getName(effectId)));
860
861
862
    }
}