effectstackview.cpp 20.6 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 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/>. *
 ***************************************************************************/

#include "effectstackview.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
23
#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp"
24
#include "assets/assetpanel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
25
26
#include "assets/view/assetparameterview.hpp"
#include "builtstack.hpp"
27
#include "collapsibleeffectview.hpp"
28
#include "core.h"
29
#include "effects/effectstack/model/effectitemmodel.hpp"
Nicolas Carion's avatar
linting    
Nicolas Carion committed
30
#include "effects/effectstack/model/effectstackmodel.hpp"
Nicolas Carion's avatar
Nicolas Carion committed
31
#include "kdenlivesettings.h"
Nicolas Carion's avatar
Nicolas Carion committed
32
#include "monitor/monitor.h"
33

Nicolas Carion's avatar
linting    
Nicolas Carion committed
34
#include <QDrag>
35
#include <QDragEnterEvent>
36
#include <QFontDatabase>
Nicolas Carion's avatar
linting    
Nicolas Carion committed
37
#include <QMimeData>
Nicolas Carion's avatar
Nicolas Carion committed
38
#include <QMutexLocker>
39
#include <QScrollBar>
40
#include <QTreeView>
Nicolas Carion's avatar
linting    
Nicolas Carion committed
41
#include <QVBoxLayout>
42
43
44
45
#include <QInputDialog>
#include <QDir>

#include <KMessageBox>
Nicolas Carion's avatar
Nicolas Carion committed
46
#include <utility>
47

Nicolas Carion's avatar
linting    
Nicolas Carion committed
48
49
WidgetDelegate::WidgetDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
50
51
52
53
54
{
}

QSize WidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
55
    QSize s = QStyledItemDelegate::sizeHint(option, index);
56
57
58
59
60
61
62
63
64
65
66
67
    if (m_height.contains(index)) {
        s.setHeight(m_height.value(index));
    }
    return s;
}

void WidgetDelegate::setHeight(const QModelIndex &index, int height)
{
    m_height[index] = height;
    emit sizeHintChanged(index);
}

68
69
70
71
72
int WidgetDelegate::height(const QModelIndex &index) const
{
    return m_height.value(index);
}

73
74
75
76
77
78
79
void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem opt(option);
    initStyleOption(&opt, index);
    QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
    style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
}
80

81
EffectStackView::EffectStackView(AssetPanel *parent)
Nicolas Carion's avatar
linting    
Nicolas Carion committed
82
    : QWidget(parent)
Vincent Pinon's avatar
Vincent Pinon committed
83
    , m_model(nullptr)
84
    , m_thumbnailer(new AssetIconProvider(true))
85
86
87
{
    m_lay = new QVBoxLayout(this);
    m_lay->setContentsMargins(0, 0, 0, 0);
88
    m_lay->setSpacing(0);
89
    setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
90
    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
91
    setAcceptDrops(true);
92
    /*m_builtStack = new BuiltStack(parent);
93
    m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
94
    m_lay->addWidget(m_builtStack);
95
    m_builtStack->setVisible(KdenliveSettings::showbuiltstack());*/
96
    m_effectsTree = new QTreeView(this);
97
    m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
98
99
    m_effectsTree->setHeaderHidden(true);
    m_effectsTree->setRootIsDecorated(false);
100
    m_effectsTree->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
101
    QString style = QStringLiteral("QTreeView {border: none;}");
Nicolas Carion's avatar
linting    
Nicolas Carion committed
102
    // m_effectsTree->viewport()->setAutoFillBackground(false);
103
    m_effectsTree->setStyleSheet(style);
104
    m_effectsTree->setVisible(!KdenliveSettings::showbuiltstack());
105
    m_lay->addWidget(m_effectsTree);
106
    m_lay->addStretch(10);
107

108
109
110
    m_scrollTimer.setSingleShot(true);
    m_scrollTimer.setInterval(250);
    connect(&m_scrollTimer, &QTimer::timeout, this, &EffectStackView::checkScrollBar);
111
112
113
    
    m_timerHeight.setSingleShot(true);
    m_timerHeight.setInterval(50);
114
115
}

116
117
118
119
120
EffectStackView::~EffectStackView()
{
    delete m_thumbnailer;
}

121
122
123
void EffectStackView::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
124
125
126
127
128
        if (event->source() == this) {
            event->setDropAction(Qt::MoveAction);
        } else {
            event->setDropAction(Qt::CopyAction);
        }
129
130
131
132
133
134
135
136
137
138
        event->setAccepted(true);
    } else {
        event->setAccepted(false);
    }
}

void EffectStackView::dropEvent(QDropEvent *event)
{
    event->accept();
    QString effectId = event->mimeData()->data(QStringLiteral("kdenlive/effect"));
139
140
    int row = m_model->rowCount();
    for (int i = 0; i < m_model->rowCount(); i++) {
141
142
        auto item = m_model->getEffectStackRow(i);
        if (item->childCount() > 0) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
143
            // TODO: group
144
145
146
            continue;
        }
        std::shared_ptr<EffectItemModel> eff = std::static_pointer_cast<EffectItemModel>(item);
147
148
149
        QModelIndex ix = m_model->getIndexFromItem(eff);
        QWidget *w = m_effectsTree->indexWidget(ix);
        if (w && w->geometry().contains(event->pos())) {
Nicolas Carion's avatar
linting    
Nicolas Carion committed
150
            qDebug() << "// DROPPED ON EFF: " << eff->getAssetId();
151
152
153
154
155
            row = i;
            break;
        }
    }
    if (event->source() == this) {
156
157
        QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource"));
        int oldRow = sourceData.section(QLatin1Char('-'), 2, 2).toInt();
Nicolas Carion's avatar
linting    
Nicolas Carion committed
158
        qDebug() << "// MOVING EFFECT FROM : " << oldRow << " TO " << row;
159
160
161
        if (row == oldRow || (row == m_model->rowCount() && oldRow == row - 1)) {
            return;
        }
162
        m_model->moveEffect(row, m_model->getEffectStackRow(oldRow));
163
    } else {
164
        bool added = false;
165
        if (row < m_model->rowCount()) {
166
            if (m_model->appendEffect(effectId) && m_model->rowCount() > 0) {
167
168
169
                added = true;
                m_model->moveEffect(row, m_model->getEffectStackRow(m_model->rowCount() - 1));
            }
170
        } else {
171
            if (m_model->appendEffect(effectId) && m_model->rowCount() > 0) {
172
173
174
175
176
                added = true;
                std::shared_ptr<AbstractEffectItem> item = m_model->getEffectStackRow(m_model->rowCount() - 1);
                if (item) {
                    slotActivateEffect(std::static_pointer_cast<EffectItemModel>(item));
                }
177
            }
178
        }
179
180
        if (!added) {
            pCore->displayMessage(i18n("Cannot add effect to clip"), InformationMessage);
181
182
        } else {
            m_scrollTimer.start();
183
        }
184
    }
185
186
}

187
void EffectStackView::setModel(std::shared_ptr<EffectStackModel> model, const QSize frameSize)
188
{
Nicolas Carion's avatar
Nicolas Carion committed
189
    qDebug() << "MUTEX LOCK!!!!!!!!!!!! setmodel";
190
    m_mutex.lock();
191
    unsetModel(false);
192
    m_effectsTree->setFixedHeight(0);
Nicolas Carion's avatar
Nicolas Carion committed
193
    m_model = std::move(model);
194
    m_sourceFrameSize = frameSize;
195
196
197
198
199
200
201
    m_effectsTree->setModel(m_model.get());
    m_effectsTree->setItemDelegateForColumn(0, new WidgetDelegate(this));
    m_effectsTree->setColumnHidden(1, true);
    m_effectsTree->setAcceptDrops(true);
    m_effectsTree->setDragDropMode(QAbstractItemView::DragDrop);
    m_effectsTree->setDragEnabled(true);
    m_effectsTree->setUniformRowHeights(false);
202
    m_mutex.unlock();
Nicolas Carion's avatar
Nicolas Carion committed
203
    qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! setmodel";
204
    loadEffects();
205
    m_scrollTimer.start();
206
    connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh);
207
    connect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::changeEnabledState);
208
    connect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect);
Nicolas Carion's avatar
Nicolas Carion committed
209
    // m_builtStack->setModel(model, stackOwner());
210
211
}

212
213
214
215
216
217
218
219
220
221
222
223
224
void EffectStackView::changeEnabledState()
{
    int max = m_model->rowCount();
    int currentActive = m_model->getActiveEffect();
    if (currentActive < max && currentActive > -1) {
        auto item = m_model->getEffectStackRow(currentActive);
        QModelIndex ix = m_model->getIndexFromItem(item);
        CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix));
        w->updateScene();
    }
    emit updateEnabledState();
}

225
void EffectStackView::loadEffects()
226
{
227
    //QMutexLocker lock(&m_mutex);
228
    int max = m_model->rowCount();
229
    qDebug() << "MUTEX LOCK!!!!!!!!!!!! loadEffects COUNT: "<<max;
230
231
232
233
    if (max == 0) {
        // blank stack
        ObjectId item = m_model->getOwnerId();
        pCore->getMonitor(item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)->slotShowEffectScene(MonitorSceneDefault);
234
        updateTreeHeight();
235
236
237
        return;
    }
    int active = qBound(0, m_model->getActiveEffect(), max - 1);
238
    bool hasLift = false;
239
    QModelIndex activeIndex;
240
    connect(&m_timerHeight, &QTimer::timeout, this, &EffectStackView::updateTreeHeight, Qt::UniqueConnection);
241
    for (int i = 0; i < max; i++) {
242
        std::shared_ptr<AbstractEffectItem> item = m_model->getEffectStackRow(i);
243
        QSize size;
244
245
246
247
248
        if (item->childCount() > 0) {
            // group, create sub stack
            continue;
        }
        std::shared_ptr<EffectItemModel> effectModel = std::static_pointer_cast<EffectItemModel>(item);
249
        CollapsibleEffectView *view = nullptr;
250
        // We need to rebuild the effect view
251
252
253
        if (effectModel->getAssetId() == QLatin1String("lift_gamma_gain")) {
            hasLift = true;
        }
254
255
256
257
        QImage effectIcon = m_thumbnailer->requestImage(effectModel->getAssetId(), &size, QSize(QStyle::PM_SmallIconSize, QStyle::PM_SmallIconSize));
        view = new CollapsibleEffectView(effectModel, m_sourceFrameSize, effectIcon, this);
        connect(view, &CollapsibleEffectView::deleteEffect, m_model.get(), &EffectStackModel::removeEffect);
        connect(view, &CollapsibleEffectView::moveEffect, m_model.get(), &EffectStackModel::moveEffect);
258
        connect(view, &CollapsibleEffectView::reloadEffect, this, &EffectStackView::reloadEffect);
259
260
        connect(view, &CollapsibleEffectView::switchHeight, this, &EffectStackView::slotAdjustDelegate, Qt::DirectConnection);
        connect(view, &CollapsibleEffectView::startDrag, this, &EffectStackView::slotStartDrag);
261
        connect(view, &CollapsibleEffectView::saveStack, this, &EffectStackView::slotSaveStack);
262
263
        connect(view, &CollapsibleEffectView::createGroup, m_model.get(), &EffectStackModel::slotCreateGroup);
        connect(view, &CollapsibleEffectView::activateEffect, this, &EffectStackView::slotActivateEffect);
264
        connect(this, &EffectStackView::blockWheenEvent, view, &CollapsibleEffectView::blockWheenEvent);
Vincent Pinon's avatar
Vincent Pinon committed
265
        connect(view, &CollapsibleEffectView::seekToPos, this, [this](int pos) {
266
267
268
269
            // at this point, the effects returns a pos relative to the clip. We need to convert it to a global time
            int clipIn = pCore->getItemPosition(m_model->getOwnerId());
            emit seekToPos(pos + clipIn);
        });
270
        connect(this, &EffectStackView::switchCollapsedView, view, &CollapsibleEffectView::switchCollapsed);
271
272
        QModelIndex ix = m_model->getIndexFromItem(effectModel);
        m_effectsTree->setIndexWidget(ix, view);
Nicolas Carion's avatar
Nicolas Carion committed
273
        auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegate(ix));
274
        del->setHeight(ix, view->height());
275
276
        view->buttonUp->setEnabled(i > 0);
        view->buttonDown->setEnabled(i < max - 1);
277
        if (i == active) {
278
            activeIndex = ix;
279
            m_effectsTree->setCurrentIndex(activeIndex);
280
        }
281
    }
282
283
284
    if (!hasLift) {
        updateTreeHeight();
    }
285
    if (activeIndex.isValid()) {
286
        doActivateEffect(active, activeIndex, true);
287
288
289
290
291
292
293
294
        if (active > 0) {
            if (hasLift) {
                // Some effects have a complex timed layout, so we need to wait a bit before getting the correct position for the effect
                QTimer::singleShot(100, this, &EffectStackView::slotFocusEffect);
            } else {
                slotFocusEffect();
            }
        }
295
    }
Nicolas Carion's avatar
Nicolas Carion committed
296
    qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! loadEffects";
297
298
}

299
300
301
void EffectStackView::updateTreeHeight()
{
    // For some reason, the treeview height does not update correctly, so enforce it
302
303
304
305
    QMutexLocker lk(&m_mutex);
    if (!m_model) {
        return;
    }
306
    int totalHeight = 0;
307
308
309
310
311
    for (int j = 0; j < m_model->rowCount(); j++) {
        std::shared_ptr<AbstractEffectItem> item2 = m_model->getEffectStackRow(j);
        std::shared_ptr<EffectItemModel> eff = std::static_pointer_cast<EffectItemModel>(item2);
        QModelIndex idx = m_model->getIndexFromItem(eff);
        auto w = m_effectsTree->indexWidget(idx);
312
313
314
        if (w) {
            totalHeight += w->height();
        }
315
    }
316
317
318
319
    if (totalHeight != m_effectsTree->height()) {
        m_effectsTree->setFixedHeight(totalHeight);
        m_scrollTimer.start();
    }
320
321
}

Nicolas Carion's avatar
Nicolas Carion committed
322
void EffectStackView::slotActivateEffect(const std::shared_ptr<EffectItemModel> &effectModel)
323
{
324
    qDebug() << "MUTEX LOCK!!!!!!!!!!!! slotactivateeffect: " << effectModel->row();
Nicolas Carion's avatar
Nicolas Carion committed
325
    QMutexLocker lock(&m_mutex);
326
    QModelIndex activeIx = m_model->getIndexFromItem(effectModel);
327
    doActivateEffect(effectModel->row(), activeIx);
Nicolas Carion's avatar
Nicolas Carion committed
328
    qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! slotactivateeffect";
329
330
}

Nicolas Carion's avatar
Nicolas Carion committed
331
void EffectStackView::slotStartDrag(const QPixmap &pix, const std::shared_ptr<EffectItemModel> &effectModel)
332
333
334
335
{
    auto *drag = new QDrag(this);
    drag->setPixmap(pix);
    auto *mime = new QMimeData;
336
    mime->setData(QStringLiteral("kdenlive/effect"), effectModel->getAssetId().toUtf8());
337
    // TODO this will break if source effect is not on the stack of a timeline clip
338
    ObjectId source = effectModel->getOwnerId();
339
    QByteArray effectSource;
340
    effectSource += QString::number((int)source.first).toUtf8();
341
    effectSource += '-';
342
    effectSource += QString::number((int)source.second).toUtf8();
343
344
345
    effectSource += '-';
    effectSource += QString::number(effectModel->row()).toUtf8();
    mime->setData(QStringLiteral("kdenlive/effectsource"), effectSource);
Nicolas Carion's avatar
Nicolas Carion committed
346
    // mime->setData(QStringLiteral("kdenlive/effectrow"), QString::number(effectModel->row()).toUtf8());
347
348
349
350
351
352
353

    // Assign ownership of the QMimeData object to the QDrag object.
    drag->setMimeData(mime);
    // Start the drag and drop operation
    drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
}

354
void EffectStackView::slotAdjustDelegate(const std::shared_ptr<EffectItemModel> &effectModel, int newHeight)
355
{
356
357
358
    if (!m_model) {
        return;
    }
359
    QModelIndex ix = m_model->getIndexFromItem(effectModel);
360
361
    if (ix.isValid()) {
        auto *del = static_cast<WidgetDelegate *>(m_effectsTree->itemDelegate(ix));
362
        if (del) {
363
            del->setHeight(ix, newHeight);
364
            m_timerHeight.start();
365
        }
366
    }
367
368
}

369
370
371
372
373
374
375
void EffectStackView::resizeEvent(QResizeEvent *event)
{
    QWidget::resizeEvent(event);
    m_scrollTimer.start();
}


376
377
void EffectStackView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
Vincent Pinon's avatar
Vincent Pinon committed
378
    Q_UNUSED(roles)
379
    if (!topLeft.isValid() || !bottomRight.isValid()) {
380
        loadEffects();
381
382
        return;
    }
Nicolas Carion's avatar
Nicolas Carion committed
383
384
    for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
        for (int j = topLeft.column(); j <= bottomRight.column(); ++j) {
385
386
            CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(m_model->index(i, j, topLeft.parent())));
            if (w) {
Vincent Pinon's avatar
Vincent Pinon committed
387
                emit w->refresh();
388
389
390
            }
        }
    }
391
392
393
}

void EffectStackView::unsetModel(bool reset)
394
395
{
    // Release ownership of smart pointer
396
    Kdenlive::MonitorId id = Kdenlive::NoMonitor;
397
    if (m_model) {
398
399
        ObjectId item = m_model->getOwnerId();
        id = item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor;
400
        disconnect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh);
401
        disconnect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::changeEnabledState);
402
        disconnect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect);
403
        disconnect(&m_timerHeight, &QTimer::timeout, this, &EffectStackView::updateTreeHeight);
404
    }
405
    if (reset) {
406
407
        QMutexLocker lock(&m_mutex);
        m_effectsTree->setModel(nullptr);
408
        m_model.reset();
409
410
411
    }
    if (id != Kdenlive::NoMonitor) {
        pCore->getMonitor(id)->slotShowEffectScene(MonitorSceneDefault);
412
    }
413
414
}

415
ObjectId EffectStackView::stackOwner() const
416
417
{
    if (m_model) {
418
        return m_model->getOwnerId();
419
    }
420
421
422
    return ObjectId(ObjectType::NoItem, -1);
}

423
bool EffectStackView::addEffect(const QString &effectId)
424
425
{
    if (m_model) {
426
        return m_model->appendEffect(effectId, true);
427
    }
428
    return false;
429
430
}

431
432
433
434
435
bool EffectStackView::isEmpty() const
{
    return m_model == nullptr ? true : m_model->rowCount() == 0;
}

436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
void EffectStackView::enableStack(bool enable)
{
    if (m_model) {
        m_model->setEffectStackEnabled(enable);
    }
}

bool EffectStackView::isStackEnabled() const
{
    if (m_model) {
        return m_model->isStackEnabled();
    }
    return false;
}

451
452
453
454
455
456
457
458
459
void EffectStackView::switchCollapsed()
{
    if (m_model) {
        int max = m_model->rowCount();
        int active = qBound(0, m_model->getActiveEffect(), max - 1);
        emit switchCollapsedView(active);
    }
}

460
void EffectStackView::doActivateEffect(int row, QModelIndex activeIx, bool force)
461
{
462
    int currentActive = m_model->getActiveEffect();
463
    if (row == currentActive && !force) {
464
465
466
        // Effect is already active
        return;
    }
467
    if (row != currentActive && currentActive > -1 && currentActive < m_model->rowCount()) {
468
        auto item = m_model->getEffectStackRow(currentActive);
469
470
471
472
473
474
        if (item) {
            QModelIndex ix = m_model->getIndexFromItem(item);
            CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(ix));
            if (w) {
                w->slotActivateEffect(false);
            }
475
476
        }
    }
477
    m_effectsTree->setCurrentIndex(activeIx);
478
    m_model->setActiveEffect(row);
479
480
    CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(activeIx));
    if (w) {
481
        w->slotActivateEffect(true);
482
    }
483
484
485
486
487
}

void EffectStackView::slotFocusEffect()
{
    emit scrollView(m_effectsTree->visualRect(m_effectsTree->currentIndex()));
488
489
}

490
491
492
void EffectStackView::slotSaveStack()
{
    QString name = QInputDialog::getText(this, i18n("Save Effect Stack"), i18n("Name for saved stack: "));
493
    if (name.trimmed().isEmpty() || m_model->rowCount() <= 0) {
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
        return;
    }
    QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/"));
    if (!dir.exists()) {
        dir.mkpath(QStringLiteral("."));
    }

    if (dir.exists(name + QStringLiteral(".xml"))) {
        if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", name + QStringLiteral(".xml"))) == KMessageBox::No) {
            return;
        }
    }

    QDomDocument doc;
    QDomElement effect = doc.createElement(QStringLiteral("effectgroup"));
    effect.setAttribute(QStringLiteral("id"), name);
510
511
512
513
    auto item = m_model->getEffectStackRow(0);
    if (item->isAudio()) {
        effect.setAttribute(QStringLiteral("type"), QStringLiteral("customAudio"));
    }
514
515
516
517
518
519
520
521
522
523
524
525
526
527
    effect.setAttribute(QStringLiteral("parentIn"), pCore->getItemIn(m_model->getOwnerId()));
    doc.appendChild(effect);
    for (int i = 0; i <= m_model->rowCount(); ++i) {
        CollapsibleEffectView *w = static_cast<CollapsibleEffectView *>(m_effectsTree->indexWidget(m_model->index(i, 0, QModelIndex())));
        if (w) {
            effect.appendChild(doc.importNode(w->toXml().documentElement(), true));
        }
    }
    QFile file(dir.absoluteFilePath(name + QStringLiteral(".xml")));
    if (file.open(QFile::WriteOnly | QFile::Truncate)) {
        QTextStream out(&file);
        out << doc.toString();
    }
    file.close();
Vincent Pinon's avatar
Vincent Pinon committed
528
    emit reloadEffect(dir.absoluteFilePath(name + QStringLiteral(".xml")));
529
530
}

531
/*
532
533
534
void EffectStackView::switchBuiltStack(bool show)
{
    m_builtStack->setVisible(show);
535
    m_effectsTree->setVisible(!show);
536
    KdenliveSettings::setShowbuiltstack(show);
537
}
538
*/