diff --git a/src/assets/assetlist/view/qmltypes/asseticonprovider.cpp b/src/assets/assetlist/view/qmltypes/asseticonprovider.cpp
index bc436320e..c3e8b687f 100644
--- a/src/assets/assetlist/view/qmltypes/asseticonprovider.cpp
+++ b/src/assets/assetlist/view/qmltypes/asseticonprovider.cpp
@@ -1,108 +1,109 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "asseticonprovider.hpp"
#include "effects/effectsrepository.hpp"
#include "transitions/transitionsrepository.hpp"
#include
#include
#include
AssetIconProvider::AssetIconProvider(bool effect)
: QQuickImageProvider(QQmlImageProviderBase::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading)
{
m_effect = effect;
}
QImage AssetIconProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
{
QImage result;
if (id == QStringLiteral("root") || id.isEmpty()) {
QPixmap pix(30, 30);
return pix.toImage();
}
if (m_effect && EffectsRepository::get()->exists(id)) {
QString name = EffectsRepository::get()->getName(id);
result = makeIcon(id, name, requestedSize);
if (size) {
*size = result.size();
}
} else if (!m_effect && TransitionsRepository::get()->exists(id)) {
QString name = TransitionsRepository::get()->getName(id);
result = makeIcon(id, name, requestedSize);
if (size) {
*size = result.size();
}
} else {
qDebug() << "Asset not found " << id;
}
return result;
}
QImage AssetIconProvider::makeIcon(const QString &effectId, const QString &effectName, const QSize &size)
{
Q_UNUSED(size);
QPixmap pix(30, 30);
if (effectName.isEmpty()) {
pix.fill(Qt::red);
return pix.toImage();
}
QFont ft = QFont();
ft.setBold(true);
uint hex = qHash(effectName);
QString t = QStringLiteral("#") + QString::number(hex, 16).toUpper().left(6);
QColor col(t);
bool isAudio = false;
bool isCustom = false;
if (m_effect) {
- isAudio = EffectsRepository::get()->getType(effectId) == EffectType::Audio;
- isCustom = EffectsRepository::get()->getType(effectId) == EffectType::Custom;
+ EffectType type = EffectsRepository::get()->getType(effectId);
+ isAudio = type == EffectType::Audio || type == EffectType::CustomAudio;
+ isCustom = type == EffectType::CustomAudio || type == EffectType::Custom;
} else {
auto type = TransitionsRepository::get()->getType(effectId);
isAudio = (type == TransitionType::AudioComposition) || (type == TransitionType::AudioTransition);
}
QPainter p;
if (isCustom) {
pix.fill(Qt::transparent);
p.begin(&pix);
p.setPen(Qt::NoPen);
p.setBrush(Qt::red);
p.drawRoundedRect(pix.rect(), 4, 4);
p.setPen(QPen());
} else if (isAudio) {
pix.fill(Qt::transparent);
p.begin(&pix);
p.setPen(Qt::NoPen);
p.setBrush(col);
p.drawEllipse(pix.rect());
p.setPen(QPen());
} else {
pix.fill(col);
p.begin(&pix);
}
p.setFont(ft);
p.drawText(pix.rect(), Qt::AlignCenter, effectName.at(0));
p.end();
return pix.toImage();
}
diff --git a/src/effects/effectlist/model/effecttreemodel.cpp b/src/effects/effectlist/model/effecttreemodel.cpp
index 5a219bf55..7d28d8567 100644
--- a/src/effects/effectlist/model/effecttreemodel.cpp
+++ b/src/effects/effectlist/model/effecttreemodel.cpp
@@ -1,181 +1,181 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "effecttreemodel.hpp"
#include "abstractmodel/treeitem.hpp"
#include "effects/effectsrepository.hpp"
#include "kdenlivesettings.h"
#include
#include
#include
#include
#include
#include
#include
#include
EffectTreeModel::EffectTreeModel(QObject *parent)
: AssetTreeModel(parent)
, m_customCategory(nullptr)
{
}
std::shared_ptr EffectTreeModel::construct(const QString &categoryFile, QObject *parent)
{
std::shared_ptr self(new EffectTreeModel(parent));
QList rootData {"Name", "ID", "Type", "isFav"};
self->rootItem = TreeItem::construct(rootData, self, true);
QHash> effectCategory; // category in which each effect should land.
std::shared_ptr miscCategory = nullptr;
std::shared_ptr audioCategory = nullptr;
// We parse category file
if (!categoryFile.isEmpty()) {
QDomDocument doc;
QFile file(categoryFile);
doc.setContent(&file, false);
file.close();
QDomNodeList groups = doc.documentElement().elementsByTagName(QStringLiteral("group"));
// Create favorite group
auto groupFav = self->rootItem->appendChild(QList{i18n("Favorites"), QStringLiteral("root"), -1});
effectCategory[QStringLiteral("kdenlive:favorites")] = groupFav;
auto groupLegacy = self->rootItem->appendChild(QList{i18n("Legacy"), QStringLiteral("root")});
for (int i = 0; i < groups.count(); i++) {
QString groupName = i18n(groups.at(i).firstChild().firstChild().nodeValue().toUtf8().constData());
if (!KdenliveSettings::gpu_accel() && groupName == i18n("GPU effects")) {
continue;
}
QStringList list = groups.at(i).toElement().attribute(QStringLiteral("list")).split(QLatin1Char(','), QString::SkipEmptyParts);
auto groupItem = self->rootItem->appendChild(QList{groupName, QStringLiteral("root")});
for (const QString &effect : list) {
effectCategory[effect] = groupItem;
}
}
// We also create "Misc", "Audio" and "Custom" categories
miscCategory = self->rootItem->appendChild(QList{i18n("Misc"), QStringLiteral("root")});
audioCategory = self->rootItem->appendChild(QList{i18n("Audio"), QStringLiteral("root")});
self->m_customCategory = self->rootItem->appendChild(QList{i18n("Custom"), QStringLiteral("root")});
} else {
// Flat view
miscCategory = self->rootItem;
audioCategory = self->rootItem;
self->m_customCategory = self->rootItem;
}
// We parse effects
auto allEffects = EffectsRepository::get()->getNames();
QString favCategory = QStringLiteral("kdenlive:favorites");
for (const auto &effect : allEffects) {
if (!KdenliveSettings::gpu_accel() && effect.first.contains(QLatin1String("movit."))) {
continue;
}
auto targetCategory = miscCategory;
EffectType type = EffectsRepository::get()->getType(effect.first);
if (effectCategory.contains(effect.first)) {
targetCategory = effectCategory[effect.first];
} else if (type == EffectType::Audio) {
targetCategory = audioCategory;
}
- if (type == EffectType::Custom) {
+ if (type == EffectType::Custom || type == EffectType::CustomAudio) {
targetCategory = self->m_customCategory;
}
// we create the data list corresponding to this profile
bool isFav = KdenliveSettings::favorite_effects().contains(effect.first);
bool isPreferred = EffectsRepository::get()->isPreferred(effect.first);
//qDebug() << effect.second << effect.first << "in " << targetCategory->dataColumn(0).toString();
QList data {effect.second, effect.first, QVariant::fromValue(type), isFav, targetCategory->row(), isPreferred};
if (KdenliveSettings::favorite_effects().contains(effect.first) && effectCategory.contains(favCategory)) {
targetCategory = effectCategory[favCategory];
}
targetCategory->appendChild(data);
}
return self;
}
void EffectTreeModel::reloadEffect(const QString &path)
{
QPair asset = EffectsRepository::get()->reloadCustom(path);
if (asset.first.isEmpty() || m_customCategory == nullptr) {
return;
}
bool isFav = KdenliveSettings::favorite_effects().contains(asset.first);
QList data {asset.first, asset.first, QVariant::fromValue(EffectType::Custom), isFav};
m_customCategory->appendChild(data);
}
void EffectTreeModel::reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions)
{
for (int i = 0; i < rowCount(); i++) {
std::shared_ptr item = rootItem->child(i);
if (item->childCount() > 0) {
QMenu *catMenu = new QMenu(item->dataColumn(nameCol).toString(), effectsMenu);
effectsMenu->addMenu(catMenu);
for (int j = 0; j < item->childCount(); j++) {
std::shared_ptr child = item->child(j);
QAction *a = new QAction(i18n(child->dataColumn(nameCol).toString().toUtf8().data()), catMenu);
const QString id = child->dataColumn(idCol).toString();
a->setData(id);
catMenu->addAction(a);
effectActions->addAction("transition_" + id, a);
}
}
}
}
void EffectTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool isEffect)
{
if (!index.isValid()) {
return;
}
std::shared_ptr item = getItemById((int)index.internalId());
if (isEffect && item->depth() == 1) {
return;
}
item->setData(AssetTreeModel::favCol, favorite);
auto id = item->dataColumn(AssetTreeModel::idCol).toString();
if (!EffectsRepository::get()->exists(id)) {
qDebug()<<"Trying to reparent unknown asset: "<childCount();
QStringList favs = KdenliveSettings::favorite_effects();
if (!favorite) {
int ix = item->dataColumn(4).toInt();
item->changeParent(rootItem->child(ix));
favs.removeAll(id);
} else {
for (int i = 0; i < max; i++) {
if (rootItem->child(i)->dataColumn(2).toInt() == -1) {
item->changeParent(rootItem->child(i));
break;
}
}
favs << id;
}
KdenliveSettings::setFavorite_effects(favs);
}
diff --git a/src/effects/effectlist/view/effectlistwidget.cpp b/src/effects/effectlist/view/effectlistwidget.cpp
index c734e1539..485cccc54 100644
--- a/src/effects/effectlist/view/effectlistwidget.cpp
+++ b/src/effects/effectlist/view/effectlistwidget.cpp
@@ -1,96 +1,98 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "effectlistwidget.hpp"
#include "../model/effectfilter.hpp"
#include "../model/effecttreemodel.hpp"
#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp"
#include
#include
#include
#include
#include
EffectListWidget::EffectListWidget(QWidget *parent)
: AssetListWidget(parent)
{
QString effectCategory = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("kdenliveeffectscategory.rc"));
m_model = EffectTreeModel::construct(effectCategory, this);
m_proxyModel = std::make_unique(this);
m_proxyModel->setSourceModel(m_model.get());
m_proxyModel->setSortRole(EffectTreeModel::NameRole);
m_proxyModel->sort(0, Qt::AscendingOrder);
m_proxy = new EffectListWidgetProxy(this);
rootContext()->setContextProperty("assetlist", m_proxy);
rootContext()->setContextProperty("assetListModel", m_proxyModel.get());
rootContext()->setContextProperty("isEffectList", true);
m_assetIconProvider = new AssetIconProvider(true);
setup();
// Activate "Main effects" filter
setFilterType("");
}
void EffectListWidget::updateFavorite(const QModelIndex &index)
{
m_proxyModel->dataChanged(index, index, QVector());
m_proxyModel->reloadFilterOnFavorite();
emit reloadFavorites();
}
EffectListWidget::~EffectListWidget()
{
delete m_proxy;
qDebug() << " - - -Deleting effect list widget";
}
void EffectListWidget::setFilterType(const QString &type)
{
if (type == "video") {
static_cast(m_proxyModel.get())->setFilterType(true, EffectType::Video);
} else if (type == "audio") {
static_cast(m_proxyModel.get())->setFilterType(true, EffectType::Audio);
+ } else if (type == "customAudio") {
+ static_cast(m_proxyModel.get())->setFilterType(true, EffectType::CustomAudio);
} else if (type == "custom") {
static_cast(m_proxyModel.get())->setFilterType(true, EffectType::Custom);
} else if (type == "favorites") {
static_cast(m_proxyModel.get())->setFilterType(true, EffectType::Favorites);
} else {
static_cast(m_proxyModel.get())->setFilterType(true, EffectType::Preferred);
}
}
QString EffectListWidget::getMimeType(const QString &assetId) const
{
Q_UNUSED(assetId);
return QStringLiteral("kdenlive/effect");
}
void EffectListWidget::reloadCustomEffect(const QString &path)
{
static_cast(m_model.get())->reloadEffect(path);
}
void EffectListWidget::reloadEffectMenu(QMenu *effectsMenu, KActionCategory *effectActions)
{
m_model->reloadAssetMenu(effectsMenu, effectActions);
}
diff --git a/src/effects/effectsrepository.cpp b/src/effects/effectsrepository.cpp
index 89cfc94bb..b0b492b62 100644
--- a/src/effects/effectsrepository.cpp
+++ b/src/effects/effectsrepository.cpp
@@ -1,335 +1,337 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "effectsrepository.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "profiles/profilemodel.hpp"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include
#include
std::unique_ptr EffectsRepository::instance;
std::once_flag EffectsRepository::m_onceFlag;
EffectsRepository::EffectsRepository()
: AbstractAssetsRepository()
{
init();
// Check that our favorite effects are valid
QStringList invalidEffect;
for (const QString &effect : KdenliveSettings::favorite_effects()) {
if (!exists(effect)) {
invalidEffect << effect;
}
}
if (!invalidEffect.isEmpty()) {
pCore->displayMessage(i18n("Some of your favorite effects are invalid and were removed: %1", invalidEffect.join(QLatin1Char(','))), ErrorMessage);
QStringList newFavorites = KdenliveSettings::favorite_effects();
for (const QString &effect : invalidEffect) {
newFavorites.removeAll(effect);
}
KdenliveSettings::setFavorite_effects(newFavorites);
}
}
Mlt::Properties *EffectsRepository::retrieveListFromMlt() const
{
return pCore->getMltRepository()->filters();
}
Mlt::Properties *EffectsRepository::getMetadata(const QString &effectId)
{
return pCore->getMltRepository()->metadata(filter_type, effectId.toLatin1().data());
}
void EffectsRepository::parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const
{
QFile file(file_name);
QDomDocument doc;
doc.setContent(&file, false);
file.close();
QDomElement base = doc.documentElement();
if (base.tagName() == QLatin1String("effectgroup")) {
// in that case we have a custom effect
Info info;
info.xml = base;
info.type = EffectType::Custom;
QString tag = base.attribute(QStringLiteral("tag"), QString());
QString id = base.hasAttribute(QStringLiteral("id")) ? base.attribute(QStringLiteral("id")) : tag;
QString name = base.attribute(QStringLiteral("name"), QString());
info.name = name;
info.id = id;
info.mltId = tag;
if (customAssets.count(id) > 0) {
qDebug() << "Error: conflicting effect name" << id;
} else {
customAssets[id] = info;
}
return;
}
QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect"));
int nbr_effect = effects.count();
if (nbr_effect == 0) {
qDebug() << "+++++++++++++\nEffect broken: " << file_name << "\n+++++++++++";
return;
}
for (int i = 0; i < nbr_effect; ++i) {
QDomNode currentNode = effects.item(i);
if (currentNode.isNull()) {
continue;
}
QDomElement currentEffect = currentNode.toElement();
Info result;
bool ok = parseInfoFromXml(currentEffect, result);
if (!ok) {
continue;
}
if (customAssets.count(result.id) > 0) {
qDebug() << "Warning: duplicate custom definition of effect" << result.id << "found. Only last one will be considered. Duplicate found in"
<< file_name;
}
result.xml = currentEffect;
// Parse type information.
QString type = currentEffect.attribute(QStringLiteral("type"), QString());
if (type == QLatin1String("audio")) {
result.type = EffectType::Audio;
} else if (type == QLatin1String("custom")) {
result.type = EffectType::Custom;
+ } else if (type == QLatin1String("customAudio")) {
+ result.type = EffectType::CustomAudio;
} else if (type == QLatin1String("hidden")) {
result.type = EffectType::Hidden;
} else {
result.type = EffectType::Video;
}
customAssets[result.id] = result;
}
}
std::unique_ptr &EffectsRepository::get()
{
std::call_once(m_onceFlag, [] { instance.reset(new EffectsRepository()); });
return instance;
}
QStringList EffectsRepository::assetDirs() const
{
return QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory);
}
void EffectsRepository::parseType(QScopedPointer &metadata, Info &res)
{
res.type = EffectType::Video;
Mlt::Properties tags((mlt_properties)metadata->get_data("tags"));
if (QString(tags.get(0)) == QLatin1String("Audio")) {
res.type = EffectType::Audio;
}
}
QString EffectsRepository::assetBlackListPath() const
{
return QStringLiteral(":data/blacklisted_effects.txt");
}
QString EffectsRepository::assetPreferredListPath() const
{
return QStringLiteral(":data/preferred_effects.txt");
}
bool EffectsRepository::isPreferred(const QString &effectId) const
{
return m_preferred_list.contains(effectId);
}
std::unique_ptr EffectsRepository::getEffect(const QString &effectId) const
{
Q_ASSERT(exists(effectId));
QString service_name = m_assets.at(effectId).mltId;
// We create the Mlt element from its name
auto filter = std::make_unique(pCore->getCurrentProfile()->profile(), service_name.toLatin1().constData(), nullptr);
return filter;
}
bool EffectsRepository::hasInternalEffect(const QString &effectId) const
{
// Retrieve the list of MLT's available assets.
QScopedPointer assets(retrieveListFromMlt());
int max = assets->count();
for (int i = 0; i < max; ++i) {
if (assets->get_name(i) == effectId) {
return true;
}
}
return false;
}
QPair EffectsRepository::reloadCustom(const QString &path)
{
std::unordered_map customAssets;
parseCustomAssetFile(path, customAssets);
QPair result;
// TODO: handle files with several effects
for (const auto &custom : customAssets) {
// Custom assets should override default ones
m_assets[custom.first] = custom.second;
result.first = custom.first;
result.second = custom.second.mltId;
}
return result;
}
QPair EffectsRepository::fixDeprecatedEffects()
{
QString customAssetDir = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory);
QPair results;
QDir current_dir(customAssetDir);
QStringList filter;
filter << QStringLiteral("*.xml");
QStringList fileList = current_dir.entryList(filter, QDir::Files);
QStringList failed;
for (const auto &file : fileList) {
QString path = current_dir.absoluteFilePath(file);
QPair fixResult = fixCustomAssetFile(path);
if (!fixResult.first.isEmpty()) {
results.first << fixResult.first;
} else if (!fixResult.second.isEmpty()) {
results.second << fixResult.second;
}
}
return results;
}
QPair EffectsRepository::fixCustomAssetFile(const QString &path)
{
QPair results;
QFile file(path);
QDomDocument doc;
doc.setContent(&file, false);
file.close();
QDomElement base = doc.documentElement();
if (base.tagName() == QLatin1String("effectgroup")) {
// Groups not implemented
return results;
}
QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect"));
int nbr_effect = effects.count();
if (nbr_effect == 0) {
qDebug() << "+++++++++++++\nEffect broken: " << path << "\n+++++++++++";
results.second = path;
return results;
}
bool effectAdjusted = false;
for (int i = 0; i < nbr_effect; ++i) {
QDomNode currentNode = effects.item(i);
if (currentNode.isNull()) {
continue;
}
QDomElement currentEffect = currentNode.toElement();
Info result;
bool ok = parseInfoFromXml(currentEffect, result);
if (!ok) {
continue;
}
if (currentEffect.hasAttribute(QLatin1String("kdenlive_info"))) {
// This is a pre 19.x custom effect, adjust param values
// First backup effect in legacy folder
QDir dir(QFileInfo(path).absoluteDir());
if (!dir.mkpath(QStringLiteral("legacy"))) {
// Cannot create the legacy folder, abort
qDebug()<<" = = = Could not create legacy folder in : "<. *
***************************************************************************/
#ifndef EFFECTSREPOSITORY_H
#define EFFECTSREPOSITORY_H
#include "assets/abstractassetsrepository.hpp"
#include "definitions.h"
#include
#include
#include
/** @brief This class stores all the effects that can be added by the user.
* You can query any effect based on its name.
* Note that this class is a Singleton
*/
-enum class EffectType { Preferred, Video, Audio, Custom, Favorites, Hidden };
+enum class EffectType { Preferred, Video, Audio, Custom, CustomAudio, Favorites, Hidden };
Q_DECLARE_METATYPE(EffectType)
class EffectsRepository : public AbstractAssetsRepository
{
public:
// Returns the instance of the Singleton
static std::unique_ptr &get();
/* @brief returns a fresh instance of the given effect */
std::unique_ptr getEffect(const QString &effectId) const;
/* @brief returns true if an effect exists in MLT (bypasses the blacklist/metadata parsing) */
bool hasInternalEffect(const QString &effectId) const;
QPair reloadCustom(const QString &path);
/* @brief Returns whether this belongs to main effects */
bool isPreferred(const QString &effectId) const;
/* @brief Check custom effects (older custom effects need an update to default and current values
* returns a list of effects that were incorrectly converted */
QPair fixDeprecatedEffects();
protected:
// Constructor is protected because class is a Singleton
EffectsRepository();
/* Retrieves the list of all available effects from Mlt*/
Mlt::Properties *retrieveListFromMlt() const override;
/* @brief Retrieves additional info about effects from a custom XML file
The resulting assets are stored in customAssets
*/
void parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const override;
/* @brief Returns the path to the effects' blacklist*/
QString assetBlackListPath() const override;
/* @brief Returns the path to the effects' preferred list*/
QString assetPreferredListPath() const override;
QStringList assetDirs() const override;
void parseType(QScopedPointer &metadata, Info &res) override;
/* @brief Returns the metadata associated with the given asset*/
Mlt::Properties *getMetadata(const QString &assetId) override;
QPair fixCustomAssetFile(const QString &path);
static std::unique_ptr instance;
static std::once_flag m_onceFlag; // flag to create the repository only once;
};
#endif
diff --git a/src/effects/effectstack/model/effectitemmodel.cpp b/src/effects/effectstack/model/effectitemmodel.cpp
index d8b0656f6..456eb1615 100644
--- a/src/effects/effectstack/model/effectitemmodel.cpp
+++ b/src/effects/effectstack/model/effectitemmodel.cpp
@@ -1,220 +1,221 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "effectitemmodel.hpp"
#include "core.h"
#include "effects/effectsrepository.hpp"
#include "effectstackmodel.hpp"
#include
EffectItemModel::EffectItemModel(const QList &effectData, std::unique_ptr effect, const QDomElement &xml, const QString &effectId,
const std::shared_ptr &stack, bool isEnabled)
: AbstractEffectItem(EffectItemType::Effect, effectData, stack, false, isEnabled)
, AssetParameterModel(std::move(effect), xml, effectId, std::static_pointer_cast(stack)->getOwnerId())
, m_childId(0)
{
connect(this, &AssetParameterModel::updateChildren, [&](const QString &name) {
if (m_childEffects.size() == 0) {
return;
}
qDebug() << "* * *SETTING EFFECT PARAM: " << name << " = " << m_asset->get(name.toUtf8().constData());
QMapIterator> i(m_childEffects);
while (i.hasNext()) {
i.next();
i.value()->filter().set(name.toUtf8().constData(), m_asset->get(name.toUtf8().constData()));
}
});
}
// static
std::shared_ptr EffectItemModel::construct(const QString &effectId, std::shared_ptr stack, bool effectEnabled)
{
Q_ASSERT(EffectsRepository::get()->exists(effectId));
QDomElement xml = EffectsRepository::get()->getXml(effectId);
std::unique_ptr effect = EffectsRepository::get()->getEffect(effectId);
effect->set("kdenlive_id", effectId.toUtf8().constData());
QList data;
data << EffectsRepository::get()->getName(effectId) << effectId;
std::shared_ptr self(new EffectItemModel(data, std::move(effect), xml, effectId, stack, effectEnabled));
baseFinishConstruct(self);
return self;
}
// static
std::shared_ptr EffectItemModel::construct(std::unique_ptr effect, std::shared_ptr stack)
{
QString effectId = effect->get("kdenlive_id");
if (effectId.isEmpty()) {
effectId = effect->get("mlt_service");
}
Q_ASSERT(EffectsRepository::get()->exists(effectId));
QDomElement xml = EffectsRepository::get()->getXml(effectId);
QDomNodeList params = xml.elementsByTagName(QStringLiteral("parameter"));
for (int i = 0; i < params.count(); ++i) {
QDomElement currentParameter = params.item(i).toElement();
QString paramName = currentParameter.attribute(QStringLiteral("name"));
QString paramValue = effect->get(paramName.toUtf8().constData());
currentParameter.setAttribute(QStringLiteral("value"), paramValue);
}
QList data;
data << EffectsRepository::get()->getName(effectId) << effectId;
bool disable = effect->get_int("disable") == 0;
std::shared_ptr self(new EffectItemModel(data, std::move(effect), xml, effectId, stack, disable));
baseFinishConstruct(self);
return self;
}
void EffectItemModel::plant(const std::weak_ptr &service)
{
if (auto ptr = service.lock()) {
int ret = ptr->attach(filter());
Q_ASSERT(ret == 0);
} else {
qDebug() << "Error : Cannot plant effect because parent service is not available anymore";
Q_ASSERT(false);
}
}
void EffectItemModel::loadClone(const std::weak_ptr &service)
{
if (auto ptr = service.lock()) {
const QString effectId = getAssetId();
std::shared_ptr effect = nullptr;
for (int i = 0; i < ptr->filter_count(); i++) {
std::unique_ptr filt(ptr->filter(i));
QString effName = filt->get("kdenlive_id");
if (effName == effectId && filt->get_int("_kdenlive_processed") == 0) {
if (auto ptr2 = m_model.lock()) {
effect = EffectItemModel::construct(std::move(filt), ptr2);
int childId = ptr->get_int("_childid");
if (childId == 0) {
childId = m_childId++;
ptr->set("_childid", childId);
}
m_childEffects.insert(childId, effect);
}
break;
}
filt->set("_kdenlive_processed", 1);
}
return;
}
qDebug() << "Error : Cannot plant effect because parent service is not available anymore";
Q_ASSERT(false);
}
void EffectItemModel::plantClone(const std::weak_ptr &service)
{
if (auto ptr = service.lock()) {
const QString effectId = getAssetId();
std::shared_ptr effect = nullptr;
if (auto ptr2 = m_model.lock()) {
effect = EffectItemModel::construct(effectId, ptr2);
effect->setParameters(getAllParameters());
int childId = ptr->get_int("_childid");
if (childId == 0) {
childId = m_childId++;
ptr->set("_childid", childId);
}
m_childEffects.insert(childId, effect);
int ret = ptr->attach(effect->filter());
Q_ASSERT(ret == 0);
return;
}
}
qDebug() << "Error : Cannot plant effect because parent service is not available anymore";
Q_ASSERT(false);
}
void EffectItemModel::unplant(const std::weak_ptr &service)
{
if (auto ptr = service.lock()) {
int ret = ptr->detach(filter());
Q_ASSERT(ret == 0);
} else {
qDebug() << "Error : Cannot plant effect because parent service is not available anymore";
Q_ASSERT(false);
}
}
void EffectItemModel::unplantClone(const std::weak_ptr &service)
{
if (m_childEffects.size() == 0) {
return;
}
if (auto ptr = service.lock()) {
int ret = ptr->detach(filter());
Q_ASSERT(ret == 0);
int childId = ptr->get_int("_childid");
auto effect = m_childEffects.take(childId);
if (effect && effect->isValid()) {
ptr->detach(effect->filter());
effect.reset();
}
} else {
qDebug() << "Error : Cannot plant effect because parent service is not available anymore";
Q_ASSERT(false);
}
}
Mlt::Filter &EffectItemModel::filter() const
{
return *static_cast(m_asset.get());
}
bool EffectItemModel::isValid() const
{
return m_asset && m_asset->is_valid();
}
void EffectItemModel::updateEnable()
{
filter().set("disable", isEnabled() ? 0 : 1);
pCore->refreshProjectItem(m_ownerId);
const QModelIndex start = AssetParameterModel::index(0, 0);
const QModelIndex end = AssetParameterModel::index(rowCount() - 1, 0);
emit dataChanged(start, end, QVector());
emit enabledChange(!isEnabled());
// Update timeline child producers
AssetParameterModel::updateChildren(QStringLiteral("disable"));
}
void EffectItemModel::setCollapsed(bool collapsed)
{
filter().set("kdenlive:collapsed", collapsed ? 1 : 0);
}
bool EffectItemModel::isCollapsed()
{
return filter().get_int("kdenlive:collapsed") == 1;
}
bool EffectItemModel::isAudio() const
{
- return EffectsRepository::get()->getType(getAssetId()) == EffectType::Audio;
+ EffectType type = EffectsRepository::get()->getType(getAssetId());
+ return type == EffectType::Audio || type == EffectType::CustomAudio;
}
diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp
index 45528d3e1..a51a0b106 100644
--- a/src/effects/effectstack/model/effectstackmodel.cpp
+++ b/src/effects/effectstack/model/effectstackmodel.cpp
@@ -1,1198 +1,1199 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "effectstackmodel.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "doc/docundostack.hpp"
#include "effectgroupmodel.hpp"
#include "effectitemmodel.hpp"
#include "effects/effectsrepository.hpp"
#include "macros.hpp"
#include "timeline2/model/timelinemodel.hpp"
#include
#include
#include
#include
EffectStackModel::EffectStackModel(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack)
: AbstractTreeModel()
, m_effectStackEnabled(true)
, m_ownerId(std::move(ownerId))
, m_undoStack(std::move(undo_stack))
, m_lock(QReadWriteLock::Recursive)
, m_loadingExisting(false)
{
m_masterService = std::move(service);
}
std::shared_ptr EffectStackModel::construct(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack)
{
std::shared_ptr self(new EffectStackModel(std::move(service), ownerId, std::move(undo_stack)));
self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true);
return self;
}
void EffectStackModel::resetService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_masterService = std::move(service);
m_childServices.clear();
// replant all effects in new service
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->plant(m_masterService);
}
}
void EffectStackModel::addService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_childServices.emplace_back(std::move(service));
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->plantClone(m_childServices.back());
}
}
void EffectStackModel::loadService(std::weak_ptr service)
{
QWriteLocker locker(&m_lock);
m_childServices.emplace_back(std::move(service));
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->loadClone(m_childServices.back());
}
}
void EffectStackModel::removeService(const std::shared_ptr &service)
{
QWriteLocker locker(&m_lock);
std::vector to_delete;
for (int i = int(m_childServices.size()) - 1; i >= 0; --i) {
auto ptr = m_childServices[uint(i)].lock();
if (service->get_int("_childid") == ptr->get_int("_childid")) {
for (int j = 0; j < rootItem->childCount(); ++j) {
std::static_pointer_cast(rootItem->child(j))->unplantClone(ptr);
}
to_delete.push_back(i);
}
}
for (int i : to_delete) {
m_childServices.erase(m_childServices.begin() + i);
}
}
void EffectStackModel::removeCurrentEffect()
{
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0) {
return;
}
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(ix));
if (effect) {
removeEffect(effect);
}
}
void EffectStackModel::removeEffect(const std::shared_ptr &effect)
{
qDebug() << "* * ** REMOVING EFFECT FROM STACK!!!\n!!!!!!!!!";
QWriteLocker locker(&m_lock);
Q_ASSERT(m_allItems.count(effect->getId()) > 0);
int parentId = -1;
if (auto ptr = effect->parentItem().lock()) parentId = ptr->getId();
int current = 0;
if (auto srv = m_masterService.lock()) {
current = srv->get_int("kdenlive:activeeffect");
if (current >= rootItem->childCount() - 1) {
srv->set("kdenlive:activeeffect", --current);
}
}
int currentRow = effect->row();
Fun undo = addItem_lambda(effect, parentId);
if (currentRow != rowCount() - 1) {
Fun move = moveItem_lambda(effect->getId(), currentRow, true);
PUSH_LAMBDA(move, undo);
}
Fun redo = removeItem_lambda(effect->getId());
bool res = redo();
if (res) {
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;
QString effectName = EffectsRepository::get()->getName(effect->getAssetId());
Fun update = [this, current, inFades, outFades]() {
// Required to build the effect view
if (current < 0 || rowCount() == 0) {
// Stack is now empty
emit dataChanged(QModelIndex(), QModelIndex(), {});
} else {
QVector roles = {TimelineModel::EffectNamesRole};
if (inFades < 0) {
roles << TimelineModel::FadeInRole;
}
if (outFades < 0) {
roles << TimelineModel::FadeOutRole;
}
qDebug() << "// EMITTING UNDO DATA CHANGE: " << roles;
emit dataChanged(QModelIndex(), QModelIndex(), roles);
}
// TODO: only update if effect is fade or keyframe
/*if (inFades < 0) {
pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
} else if (outFades < 0) {
pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
}*/
pCore->updateItemKeyframes(m_ownerId);
return true;
};
Fun update2 = [this, inFades, outFades]() {
// Required to build the effect view
QVector roles = {TimelineModel::EffectNamesRole};
// TODO: only update if effect is fade or keyframe
if (inFades < 0) {
roles << TimelineModel::FadeInRole;
} else if (outFades < 0) {
roles << TimelineModel::FadeOutRole;
}
qDebug() << "// EMITTING REDO DATA CHANGE: " << roles;
emit dataChanged(QModelIndex(), QModelIndex(), roles);
pCore->updateItemKeyframes(m_ownerId);
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update2, undo);
PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName));
} else {
qDebug() << "..........FAILED EFFECT DELETION";
}
}
bool EffectStackModel::copyXmlEffect(QDomElement effect)
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
bool result = fromXml(effect, undo, redo);
if (result) {
PUSH_UNDO(undo, redo, i18n("Copy effect"));
}
return result;
}
QDomElement EffectStackModel::toXml(QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("effects"));
int currentIn = pCore->getItemIn(m_ownerId);
container.setAttribute(QStringLiteral("parentIn"), currentIn);
for (int i = 0; i < rootItem->childCount(); ++i) {
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i));
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 ¶m : passProps) {
int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
if (paramVal > 0) {
Xml::setXmlProperty(sub, param, QString::number(paramVal));
}
}
QVector> params = sourceEffect->getAllParameters();
QLocale locale;
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble()));
} else {
Xml::setXmlProperty(sub, param.first, param.second.toString());
}
}
container.appendChild(sub);
}
return container;
}
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 sourceEffect = std::static_pointer_cast(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 ¶m : passProps) {
int paramVal = sourceEffect->filter().get_int(param.toUtf8().constData());
if (paramVal > 0) {
Xml::setXmlProperty(sub, param, QString::number(paramVal));
}
}
QVector> params = sourceEffect->getAllParameters();
QLocale locale;
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
Xml::setXmlProperty(sub, param.first, locale.toString(param.second.toDouble()));
} else {
Xml::setXmlProperty(sub, param.first, param.second.toString());
}
}
container.appendChild(sub);
return container;
}
bool EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo)
{
QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect"));
int parentIn = effectsXml.attribute(QStringLiteral("parentIn")).toInt();
qDebug()<<"// GOT PREVIOUS PARENTIN: "<getItemIn(m_ownerId);
PlaylistState::ClipState state = pCore->getItemState(m_ownerId);
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement node = nodeList.item(i).toElement();
const QString effectId = node.attribute(QStringLiteral("id"));
- bool isAudioEffect = EffectsRepository::get()->getType(effectId) == EffectType::Audio;
+ EffectType type = EffectsRepository::get()->getType(effectId);
+ bool isAudioEffect = type == EffectType::Audio || type == EffectType::CustomAudio;
if (isAudioEffect) {
if (state != PlaylistState::AudioOnly) {
continue;
}
} else if (state != PlaylistState::VideoOnly) {
continue;
}
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);
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());
}
QStringList keyframeParams = effect->getKeyframableParameters();
QVector> parameters;
QDomNodeList params = node.elementsByTagName(QStringLiteral("property"));
for (int j = 0; j < params.count(); j++) {
QDomElement pnode = params.item(j).toElement();
const QString pName = pnode.attribute(QStringLiteral("name"));
if (pName == QLatin1String("in") || pName == QLatin1String("out")) {
continue;
}
if (keyframeParams.contains(pName)) {
// This is a keyframable parameter, fix offset
QString pValue = KeyframeModel::getAnimationStringWithOffset(effect, pnode.text(), currentIn - parentIn);
parameters.append(QPair(pName, QVariant(pValue)));
} else {
parameters.append(QPair(pName, QVariant(pnode.text())));
}
}
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());
effect->prepareKeyframes();
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")) {
m_fadeIns.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
effect->filter().set("in", currentIn);
effect->filter().set("out", currentIn + duration);
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effect->getId());
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);
}
local_redo();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
if (true) {
Fun update = [this]() {
emit dataChanged(QModelIndex(), QModelIndex(), {});
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
}
return true;
}
bool EffectStackModel::copyEffect(const std::shared_ptr &sourceItem, PlaylistState::ClipState state)
{
QWriteLocker locker(&m_lock);
if (sourceItem->childCount() > 0) {
// TODO: group
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;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(sourceItem);
const QString effectId = sourceEffect->getAssetId();
bool enabled = sourceEffect->isEnabled();
auto effect = EffectItemModel::construct(effectId, shared_from_this(), enabled);
effect->setParameters(sourceEffect->getAllParameters());
if (!enabled) {
effect->filter().set("disable", 1);
}
effect->filter().set("in", sourceEffect->filter().get_int("in"));
effect->filter().set("out", sourceEffect->filter().get_int("out"));
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());
effect->prepareKeyframes();
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
QVector roles = {TimelineModel::EffectNamesRole};
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
m_fadeIns.insert(effect->getId());
int duration = effect->filter().get_length() - 1;
int in = pCore->getItemIn(m_ownerId);
effect->filter().set("in", in);
effect->filter().set("out", in + duration);
roles << TimelineModel::FadeInRole;
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effect->getId());
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);
roles << TimelineModel::FadeOutRole;
}
bool res = local_redo();
if (res) {
Fun update = [this, roles]() {
emit dataChanged(QModelIndex(), QModelIndex(), roles);
return true;
};
}
return res;
}
bool EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent)
{
QWriteLocker locker(&m_lock);
auto effect = EffectItemModel::construct(effectId, shared_from_this());
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;
}
Fun undo = removeItem_lambda(effect->getId());
// TODO the parent should probably not always be the root
Fun redo = addItem_lambda(effect, rootItem->getId());
effect->prepareKeyframes();
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
int currentActive = getActiveEffect();
if (makeCurrent) {
if (auto srvPtr = m_masterService.lock()) {
srvPtr->set("kdenlive:activeeffect", rowCount());
}
}
bool res = redo();
if (res) {
int inFades = 0;
int outFades = 0;
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
int duration = effect->filter().get_length() - 1;
int in = pCore->getItemIn(m_ownerId);
effect->filter().set("in", in);
effect->filter().set("out", in + duration);
inFades++;
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
/*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);*/
outFades++;
} else if (m_ownerId.first == ObjectType::TimelineTrack) {
effect->filter().set("out", pCore->getItemDuration(m_ownerId));
}
QString effectName = EffectsRepository::get()->getName(effectId);
Fun update = [this, inFades, outFades]() {
// TODO: only update if effect is fade or keyframe
QVector roles = {TimelineModel::EffectNamesRole};
if (inFades > 0) {
roles << TimelineModel::FadeInRole;
} else if (outFades > 0) {
roles << TimelineModel::FadeOutRole;
}
pCore->updateItemKeyframes(m_ownerId);
emit dataChanged(QModelIndex(), QModelIndex(), roles);
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
PUSH_UNDO(undo, redo, i18n("Add effect %1").arg(i18n(effectName.toUtf8().data())));
} else if (makeCurrent) {
if (auto srvPtr = m_masterService.lock()) {
srvPtr->set("kdenlive:activeeffect", currentActive);
}
}
return res;
}
bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, int offset, Fun &undo, Fun &redo,
bool logUndo)
{
QWriteLocker locker(&m_lock);
const int fadeInDuration = getFadePosition(true);
const int fadeOutDuration = getFadePosition(false);
int out = newIn + duration;
for (const auto &leaf : rootItem->getLeaves()) {
std::shared_ptr item = std::static_pointer_cast(leaf);
if (item->effectItemType() == EffectItemType::Group) {
// probably an empty group, ignore
continue;
}
std::shared_ptr effect = std::static_pointer_cast(leaf);
if (fadeInDuration > 0 && m_fadeIns.count(leaf->getId()) > 0) {
int oldEffectIn = qMax(0, effect->filter().get_in());
int oldEffectOut = effect->filter().get_out();
qDebug() << "--previous effect: " << oldEffectIn << "-" << oldEffectOut;
int effectDuration = qMin(effect->filter().get_length() - 1, duration);
if (!adjustFromEnd && (oldIn != newIn || duration != oldDuration)) {
// Clip start was resized, adjust effect in / out
Fun operation = [effect, newIn, effectDuration, logUndo]() {
effect->setParameter(QStringLiteral("in"), newIn, false);
effect->setParameter(QStringLiteral("out"), newIn + effectDuration, logUndo);
qDebug() << "--new effect: " << newIn << "-" << newIn + effectDuration;
return true;
};
bool res = operation();
if (!res) {
return false;
}
Fun reverse = [effect, oldEffectIn, oldEffectOut, logUndo]() {
effect->setParameter(QStringLiteral("in"), oldEffectIn, false);
effect->setParameter(QStringLiteral("out"), oldEffectOut, logUndo);
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);
}
Fun operation = [effect, oldEffectIn, effectDuration, logUndo]() {
effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo);
return true;
};
bool res = operation();
if (!res) {
return false;
}
if (logUndo) {
Fun reverse = [effect, referenceEffectOut]() {
effect->setParameter(QStringLiteral("out"), referenceEffectOut, true);
effect->filter().set("_refout", (char *)nullptr);
return true;
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
}
}
} else if (fadeOutDuration > 0 && m_fadeOuts.count(leaf->getId()) > 0) {
int effectDuration = qMin(fadeOutDuration, duration);
int newFadeIn = out - effectDuration;
int oldFadeIn = effect->filter().get_int("in");
int oldOut = effect->filter().get_int("out");
int referenceEffectIn = effect->filter().get_int("_refin");
if (referenceEffectIn <= 0) {
referenceEffectIn = oldFadeIn;
effect->filter().set("_refin", referenceEffectIn);
}
Fun operation = [effect, newFadeIn, out, logUndo]() {
effect->setParameter(QStringLiteral("in"), newFadeIn, false);
effect->setParameter(QStringLiteral("out"), out, logUndo);
return true;
};
bool res = operation();
if (!res) {
return false;
}
if (logUndo) {
Fun reverse = [effect, referenceEffectIn, oldOut]() {
effect->setParameter(QStringLiteral("in"), referenceEffectIn, false);
effect->setParameter(QStringLiteral("out"), oldOut, true);
effect->filter().set("_refin", (char *)nullptr);
return true;
};
PUSH_LAMBDA(operation, redo);
PUSH_LAMBDA(reverse, undo);
}
} else {
// Not a fade effect, check for keyframes
std::shared_ptr keyframes = effect->getKeyframeModel();
if (keyframes != nullptr) {
// Effect has keyframes, update these
keyframes->resizeKeyframes(oldIn, oldIn + oldDuration - 1, newIn, out - 1, offset, adjustFromEnd, undo, redo);
QModelIndex index = getIndexFromItem(effect);
Fun refresh = [effect, index]() {
effect->dataChanged(index, index, QVector());
return true;
};
refresh();
PUSH_LAMBDA(refresh, redo);
PUSH_LAMBDA(refresh, undo);
} else {
qDebug() << "// NULL Keyframes---------";
}
}
}
return true;
}
bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade, bool logUndo)
{
QWriteLocker locker(&m_lock);
if (fromStart) {
// Fade in
if (m_fadeIns.empty()) {
if (audioFade) {
appendEffect(QStringLiteral("fadein"));
}
if (videoFade) {
appendEffect(QStringLiteral("fade_from_black"));
}
}
QList indexes;
auto ptr = m_masterService.lock();
int in = 0;
if (ptr) {
in = ptr->get_int("in");
}
int oldDuration = -1;
for (int i = 0; i < rootItem->childCount(); ++i) {
if (m_fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) {
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i));
if (oldDuration == -1) {
oldDuration = effect->filter().get_length();
}
effect->filter().set("in", in);
duration = qMin(pCore->getItemDuration(m_ownerId), duration);
effect->filter().set("out", in + duration);
indexes << getIndexFromItem(effect);
}
}
if (!indexes.isEmpty()) {
emit dataChanged(indexes.first(), indexes.last(), QVector());
pCore->updateItemModel(m_ownerId, QStringLiteral("fadein"));
if (videoFade) {
int min = pCore->getItemPosition(m_ownerId);
QSize range(min, min + qMax(duration, oldDuration));
pCore->refreshProjectRange(range);
if (logUndo) {
pCore->invalidateRange(range);
}
}
}
} else {
// Fade out
if (m_fadeOuts.empty()) {
if (audioFade) {
appendEffect(QStringLiteral("fadeout"));
}
if (videoFade) {
appendEffect(QStringLiteral("fade_to_black"));
}
}
int in = 0;
auto ptr = m_masterService.lock();
if (ptr) {
in = ptr->get_int("in");
}
int itemDuration = pCore->getItemDuration(m_ownerId);
int out = in + itemDuration;
int oldDuration = -1;
QList indexes;
for (int i = 0; i < rootItem->childCount(); ++i) {
if (m_fadeOuts.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) {
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i));
if (oldDuration == -1) {
oldDuration = effect->filter().get_length();
}
effect->filter().set("out", out - 1);
duration = qMin(itemDuration, duration);
effect->filter().set("in", out - duration);
indexes << getIndexFromItem(effect);
}
}
if (!indexes.isEmpty()) {
emit dataChanged(indexes.first(), indexes.last(), QVector());
pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout"));
if (videoFade) {
int min = pCore->getItemPosition(m_ownerId);
QSize range(min + itemDuration - qMax(duration, oldDuration), min + itemDuration);
pCore->refreshProjectRange(range);
if (logUndo) {
pCore->invalidateRange(range);
}
}
}
}
return true;
}
int EffectStackModel::getFadePosition(bool fromStart)
{
QWriteLocker locker(&m_lock);
if (fromStart) {
if (m_fadeIns.empty()) {
return 0;
}
for (int i = 0; i < rootItem->childCount(); ++i) {
if (*(m_fadeIns.begin()) == std::static_pointer_cast(rootItem->child(i))->getId()) {
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i));
return effect->filter().get_length() - 1;
}
}
} else {
if (m_fadeOuts.empty()) {
return 0;
}
for (int i = 0; i < rootItem->childCount(); ++i) {
if (*(m_fadeOuts.begin()) == std::static_pointer_cast(rootItem->child(i))->getId()) {
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i));
return effect->filter().get_length() - 1;
}
}
}
return 0;
}
bool EffectStackModel::removeFade(bool fromStart)
{
QWriteLocker locker(&m_lock);
std::vector toRemove;
for (int i = 0; i < rootItem->childCount(); ++i) {
if ((fromStart && m_fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) ||
(!fromStart && m_fadeOuts.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0)) {
toRemove.push_back(i);
}
}
for (int i : toRemove) {
std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i));
removeEffect(effect);
}
return true;
}
void EffectStackModel::moveEffect(int destRow, const std::shared_ptr &item)
{
QWriteLocker locker(&m_lock);
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) {
Fun update = [this]() {
this->dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectNamesRole});
return true;
};
update();
UPDATE_UNDO_REDO(update, update, undo, redo);
auto effectId = std::static_pointer_cast(item)->getAssetId();
QString effectName = EffectsRepository::get()->getName(effectId);
PUSH_UNDO(undo, redo, i18n("Move effect %1").arg(i18n(effectName.toUtf8().data())));
}
}
void EffectStackModel::registerItem(const std::shared_ptr &item)
{
QWriteLocker locker(&m_lock);
// qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect";
QModelIndex ix;
if (!item->isRoot()) {
auto effectItem = std::static_pointer_cast(item);
if (!m_loadingExisting) {
// qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << m_childServices.size();
effectItem->plant(m_masterService);
for (const auto &service : m_childServices) {
// qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting CLONE effect in " << (void *)service.lock().get();
effectItem->plantClone(service);
}
}
effectItem->setEffectStackEnabled(m_effectStackEnabled);
const QString &effectId = effectItem->getAssetId();
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
m_fadeIns.insert(effectItem->getId());
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effectItem->getId());
}
ix = getIndexFromItem(effectItem);
if (!effectItem->isAudio() && !m_loadingExisting) {
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
}
AbstractTreeModel::registerItem(item);
}
void EffectStackModel::deregisterItem(int id, TreeItem *item)
{
QWriteLocker locker(&m_lock);
if (!item->isRoot()) {
auto effectItem = static_cast(item);
effectItem->unplant(m_masterService);
for (const auto &service : m_childServices) {
effectItem->unplantClone(service);
}
if (!effectItem->isAudio()) {
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
}
AbstractTreeModel::deregisterItem(id, item);
}
void EffectStackModel::setEffectStackEnabled(bool enabled)
{
QWriteLocker locker(&m_lock);
m_effectStackEnabled = enabled;
// Recursively updates children states
for (int i = 0; i < rootItem->childCount(); ++i) {
std::static_pointer_cast(rootItem->child(i))->setEffectStackEnabled(enabled);
}
emit dataChanged(QModelIndex(), QModelIndex(), {TimelineModel::EffectsEnabledRole});
emit enabledStateChanged();
}
std::shared_ptr EffectStackModel::getEffectStackRow(int row, const std::shared_ptr &parentItem)
{
return std::static_pointer_cast(parentItem ? parentItem->child(row) : rootItem->child(row));
}
bool EffectStackModel::importEffects(const std::shared_ptr &sourceStack, PlaylistState::ClipState state)
{
QWriteLocker locker(&m_lock);
// TODO: manage fades, keyframes if clips don't have same size / in point
bool found = false;
for (int i = 0; i < sourceStack->rowCount(); i++) {
auto item = sourceStack->getEffectStackRow(i);
// NO undo. this should only be used on project opening
if (copyEffect(item, state)) {
found = true;
}
}
if (found) {
modelChanged();
}
return found;
}
void EffectStackModel::importEffects(const std::weak_ptr &service, PlaylistState::ClipState state, bool alreadyExist)
{
QWriteLocker locker(&m_lock);
m_loadingExisting = alreadyExist;
if (auto ptr = service.lock()) {
for (int i = 0; i < ptr->filter_count(); i++) {
std::unique_ptr filter(ptr->filter(i));
if (filter->get("kdenlive_id") == nullptr) {
// don't consider internal MLT stuff
continue;
}
const QString effectId = qstrdup(filter->get("kdenlive_id"));
// The MLT filter already exists, use it directly to create the effect
std::shared_ptr effect;
if (alreadyExist) {
// effect is already plugged in the service
effect = EffectItemModel::construct(std::move(filter), shared_from_this());
} else {
// duplicate effect
std::unique_ptr asset = EffectsRepository::get()->getEffect(effectId);
asset->inherit(*(filter));
effect = EffectItemModel::construct(std::move(asset), shared_from_this());
}
if (effect->isAudio()) {
if (state == PlaylistState::VideoOnly) {
// Don't import effect
continue;
}
} else if (state == PlaylistState::AudioOnly) {
// Don't import effect
continue;
}
connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged);
connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection);
Fun redo = addItem_lambda(effect, rootItem->getId());
effect->prepareKeyframes();
if (redo()) {
if (effectId == QLatin1String("fadein") || effectId == QLatin1String("fade_from_black")) {
m_fadeIns.insert(effect->getId());
int clipIn = ptr->get_int("in");
if (effect->filter().get_int("in") != clipIn) {
// Broken fade, fix
int filterLength = effect->filter().get_length() - 1;
effect->filter().set("in", clipIn);
effect->filter().set("out", clipIn + filterLength);
}
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
m_fadeOuts.insert(effect->getId());
int clipOut = ptr->get_int("out");
if (effect->filter().get_int("out") != clipOut) {
// Broken fade, fix
int filterLength = effect->filter().get_length() - 1;
effect->filter().set("in", clipOut - filterLength);
effect->filter().set("out", clipOut);
}
}
}
}
}
m_loadingExisting = false;
modelChanged();
}
void EffectStackModel::setActiveEffect(int ix)
{
QWriteLocker locker(&m_lock);
if (auto ptr = m_masterService.lock()) {
ptr->set("kdenlive:activeeffect", ix);
}
pCore->updateItemKeyframes(m_ownerId);
}
int EffectStackModel::getActiveEffect() const
{
QWriteLocker locker(&m_lock);
if (auto ptr = m_masterService.lock()) {
return ptr->get_int("kdenlive:activeeffect");
}
return 0;
}
void EffectStackModel::slotCreateGroup(const std::shared_ptr &childEffect)
{
QWriteLocker locker(&m_lock);
auto groupItem = EffectGroupModel::construct(QStringLiteral("group"), shared_from_this());
rootItem->appendChild(groupItem);
groupItem->appendChild(childEffect);
}
ObjectId EffectStackModel::getOwnerId() const
{
return m_ownerId;
}
bool EffectStackModel::checkConsistency()
{
if (!AbstractTreeModel::checkConsistency()) {
return false;
}
std::vector> allFilters;
// We do a DFS on the tree to retrieve all the filters
std::stack> stck;
stck.push(std::static_pointer_cast(rootItem));
while (!stck.empty()) {
auto current = stck.top();
stck.pop();
if (current->effectItemType() == EffectItemType::Effect) {
if (current->childCount() > 0) {
qDebug() << "ERROR: Found an effect with children";
return false;
}
allFilters.push_back(std::static_pointer_cast(current));
continue;
}
for (int i = current->childCount() - 1; i >= 0; --i) {
stck.push(std::static_pointer_cast(current->child(i)));
}
}
for (const auto &service : m_childServices) {
auto ptr = service.lock();
if (!ptr) {
qDebug() << "ERROR: unavailable service";
return false;
}
// MLT inserts some default normalizer filters that are not managed by Kdenlive, which explains why the filter count is not equal
int kdenliveFilterCount = 0;
for (int i = 0; i < ptr->filter_count(); i++) {
std::shared_ptr filt(ptr->filter(i));
if (filt->get("kdenlive_id") != nullptr) {
kdenliveFilterCount++;
}
// qDebug() << "FILTER: "<filter(i)->get("mlt_service");
}
if (kdenliveFilterCount != (int)allFilters.size()) {
qDebug() << "ERROR: Wrong filter count: " << kdenliveFilterCount << " = " << allFilters.size();
return false;
}
int ct = 0;
for (uint i = 0; i < allFilters.size(); ++i) {
while (ptr->filter(ct)->get("kdenlive_id") == nullptr && ct < ptr->filter_count()) {
ct++;
}
auto mltFilter = ptr->filter(ct);
auto currentFilter = allFilters[i]->filter();
if (QString(currentFilter.get("mlt_service")) != QLatin1String(mltFilter->get("mlt_service"))) {
qDebug() << "ERROR: filter " << i << "differ: " << ct << ", " << currentFilter.get("mlt_service") << " = " << mltFilter->get("mlt_service");
return false;
}
QVector> params = allFilters[i]->getAllParameters();
for (const auto &val : params) {
// Check parameters values
if (val.second != QVariant(mltFilter->get(val.first.toUtf8().constData()))) {
qDebug() << "ERROR: filter " << i << "PARAMETER MISMATCH: " << val.first << " = " << val.second
<< " != " << mltFilter->get(val.first.toUtf8().constData());
return false;
}
}
ct++;
}
}
return true;
}
void EffectStackModel::adjust(const QString &effectId, const QString &effectName, double value)
{
QWriteLocker locker(&m_lock);
for (int i = 0; i < rootItem->childCount(); ++i) {
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i));
if (effectId == sourceEffect->getAssetId()) {
sourceEffect->setParameter(effectName, QString::number(value));
return;
}
}
}
std::shared_ptr EffectStackModel::getAssetModelById(const QString &effectId)
{
QWriteLocker locker(&m_lock);
for (int i = 0; i < rootItem->childCount(); ++i) {
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i));
if (effectId == sourceEffect->getAssetId()) {
return std::static_pointer_cast(sourceEffect);
}
}
return nullptr;
}
bool EffectStackModel::hasFilter(const QString &effectId) const
{
READ_LOCK();
return rootItem->accumulate_const(false, [effectId](bool b, std::shared_ptr it) {
if (b) return true;
auto item = std::static_pointer_cast(it);
if (item->effectItemType() == EffectItemType::Group) {
return false;
}
auto sourceEffect = std::static_pointer_cast(it);
return effectId == sourceEffect->getAssetId();
});
}
double EffectStackModel::getFilterParam(const QString &effectId, const QString ¶mName)
{
READ_LOCK();
for (int i = 0; i < rootItem->childCount(); ++i) {
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i));
if (effectId == sourceEffect->getAssetId()) {
return sourceEffect->filter().get_double(paramName.toUtf8().constData());
}
}
return 0.0;
}
KeyframeModel *EffectStackModel::getEffectKeyframeModel()
{
if (rootItem->childCount() == 0) return nullptr;
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0 || ix >= rootItem->childCount()) {
return nullptr;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix));
std::shared_ptr listModel = sourceEffect->getKeyframeModel();
if (listModel) {
return listModel->getKeyModel();
}
return nullptr;
}
void EffectStackModel::replugEffect(const std::shared_ptr &asset)
{
QWriteLocker locker(&m_lock);
auto effectItem = std::static_pointer_cast(asset);
int oldRow = effectItem->row();
int count = rowCount();
for (int ix = oldRow; ix < count; ix++) {
auto item = std::static_pointer_cast(rootItem->child(ix));
item->unplant(m_masterService);
for (const auto &service : m_childServices) {
item->unplantClone(service);
}
}
std::unique_ptr effect = EffectsRepository::get()->getEffect(effectItem->getAssetId());
effect->inherit(effectItem->filter());
effectItem->resetAsset(std::move(effect));
for (int ix = oldRow; ix < count; ix++) {
auto item = std::static_pointer_cast(rootItem->child(ix));
item->plant(m_masterService);
for (const auto &service : m_childServices) {
item->plantClone(service);
}
}
}
void EffectStackModel::cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
const auto &toDelete = outEffects ? m_fadeOuts : m_fadeIns;
for (int id : toDelete) {
auto effect = std::static_pointer_cast(getItemById(id));
Fun operation = removeItem_lambda(id);
if (operation()) {
Fun reverse = addItem_lambda(effect, rootItem->getId());
UPDATE_UNDO_REDO(operation, reverse, undo, redo);
}
}
if (!toDelete.empty()) {
Fun updateRedo = [this, toDelete, outEffects]() {
for (int id : toDelete) {
if (outEffects) {
m_fadeOuts.erase(id);
} else {
m_fadeIns.erase(id);
}
}
QVector roles = {TimelineModel::EffectNamesRole};
roles << (outEffects ? TimelineModel::FadeOutRole : TimelineModel::FadeInRole);
emit dataChanged(QModelIndex(), QModelIndex(), roles);
pCore->updateItemKeyframes(m_ownerId);
return true;
};
updateRedo();
PUSH_LAMBDA(updateRedo, redo);
}
}
const QString EffectStackModel::effectNames() const
{
QStringList effects;
for (int i = 0; i < rootItem->childCount(); ++i) {
effects.append(i18n(EffectsRepository::get()->getName(std::static_pointer_cast(rootItem->child(i))->getAssetId()).toUtf8().data()));
}
return effects.join(QLatin1Char('/'));
}
bool EffectStackModel::isStackEnabled() const
{
return m_effectStackEnabled;
}
bool EffectStackModel::addEffectKeyFrame(int frame, double normalisedVal)
{
if (rootItem->childCount() == 0) return false;
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0) {
return false;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix));
std::shared_ptr listModel = sourceEffect->getKeyframeModel();
if (m_ownerId.first == ObjectType::TimelineTrack) {
sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId));
}
return listModel->addKeyframe(frame, normalisedVal);
}
bool EffectStackModel::removeKeyFrame(int frame)
{
if (rootItem->childCount() == 0) return false;
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0) {
return false;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix));
std::shared_ptr listModel = sourceEffect->getKeyframeModel();
return listModel->removeKeyframe(GenTime(frame, pCore->getCurrentFps()));
}
bool EffectStackModel::updateKeyFrame(int oldFrame, int newFrame, QVariant normalisedVal)
{
if (rootItem->childCount() == 0) return false;
int ix = 0;
if (auto ptr = m_masterService.lock()) {
ix = ptr->get_int("kdenlive:activeeffect");
}
if (ix < 0) {
return false;
}
std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(ix));
std::shared_ptr listModel = sourceEffect->getKeyframeModel();
if (m_ownerId.first == ObjectType::TimelineTrack) {
sourceEffect->filter().set("out", pCore->getItemDuration(m_ownerId));
}
return listModel->updateKeyframe(GenTime(oldFrame, pCore->getCurrentFps()), GenTime(newFrame, pCore->getCurrentFps()), std::move(normalisedVal));
}
diff --git a/src/effects/effectstack/view/collapsibleeffectview.cpp b/src/effects/effectstack/view/collapsibleeffectview.cpp
index 3b93782cc..2252df612 100644
--- a/src/effects/effectstack/view/collapsibleeffectview.cpp
+++ b/src/effects/effectstack/view/collapsibleeffectview.cpp
@@ -1,808 +1,809 @@
/***************************************************************************
* 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 . *
***************************************************************************/
#include "collapsibleeffectview.hpp"
#include "assets/view/assetparameterview.hpp"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "effects/effectsrepository.hpp"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "kdenlivesettings.h"
#include "monitor/monitor.h"
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
CollapsibleEffectView::CollapsibleEffectView(const std::shared_ptr &effectModel, QSize frameSize, const QImage &icon, QWidget *parent)
: AbstractCollapsibleWidget(parent)
, m_view(nullptr)
, m_model(effectModel)
, m_regionEffect(false)
{
QString effectId = effectModel->getAssetId();
QString effectName = i18n(EffectsRepository::get()->getName(effectId).toUtf8().data());
if (effectId == QLatin1String("region")) {
m_regionEffect = true;
decoframe->setObjectName(QStringLiteral("decoframegroup"));
}
filterWheelEvent = true;
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
// decoframe->setProperty("active", true);
// m_info.fromString(effect.attribute(QStringLiteral("kdenlive_info")));
// setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
buttonUp->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-up")));
buttonUp->setToolTip(i18n("Move effect up"));
buttonDown->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-down")));
buttonDown->setToolTip(i18n("Move effect down"));
buttonDel->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-deleffect")));
buttonDel->setToolTip(i18n("Delete effect"));
// buttonUp->setEnabled(canMoveUp);
// buttonDown->setEnabled(!lastEffect);
if (effectId == QLatin1String("speed")) {
// Speed effect is a "pseudo" effect, cannot be moved
buttonUp->setVisible(false);
buttonDown->setVisible(false);
m_isMovable = false;
setAcceptDrops(false);
} else {
setAcceptDrops(true);
}
// checkAll->setToolTip(i18n("Enable/Disable all effects"));
// buttonShowComments->setIcon(QIcon::fromTheme("help-about"));
// buttonShowComments->setToolTip(i18n("Show additional information for the parameters"));
m_collapse = new KDualAction(i18n("Collapse Effect"), i18n("Expand Effect"), this);
m_collapse->setActiveIcon(QIcon::fromTheme(QStringLiteral("arrow-right")));
collapseButton->setDefaultAction(m_collapse);
connect(m_collapse, &KDualAction::activeChanged, this, &CollapsibleEffectView::slotSwitch);
if (effectModel->rowCount() == 0) {
// Effect has no paramerter
m_collapse->setInactiveIcon(QIcon::fromTheme(QStringLiteral("tools-wizard")));
collapseButton->setEnabled(false);
} else {
m_collapse->setInactiveIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
}
auto *l = static_cast(frame->layout());
m_colorIcon = new QLabel(this);
l->insertWidget(0, m_colorIcon);
m_colorIcon->setFixedSize(icon.size());
title = new QLabel(this);
l->insertWidget(2, title);
m_keyframesButton = new QToolButton(this);
m_keyframesButton->setIcon(QIcon::fromTheme(QStringLiteral("adjustcurves")));
m_keyframesButton->setAutoRaise(true);
m_keyframesButton->setCheckable(true);
m_keyframesButton->setToolTip(i18n("Enable Keyframes"));
l->insertWidget(3, m_keyframesButton);
// Enable button
m_enabledButton = new KDualAction(i18n("Disable Effect"), i18n("Enable Effect"), this);
m_enabledButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("hint")));
m_enabledButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("visibility")));
enabledButton->setDefaultAction(m_enabledButton);
connect(m_model.get(), &AssetParameterModel::enabledChange, this, &CollapsibleEffectView::enableView);
m_groupAction = new QAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Create Group"), this);
connect(m_groupAction, &QAction::triggered, this, &CollapsibleEffectView::slotCreateGroup);
if (m_regionEffect) {
effectName.append(':' + QUrl(Xml::getXmlParameter(m_effect, QStringLiteral("resource"))).fileName());
}
// Color thumb
m_colorIcon->setPixmap(QPixmap::fromImage(icon));
title->setText(effectName);
m_view = new AssetParameterView(this);
const std::shared_ptr effectParamModel = std::static_pointer_cast(effectModel);
m_view->setModel(effectParamModel, frameSize);
connect(m_view, &AssetParameterView::seekToPos, this, &AbstractCollapsibleWidget::seekToPos);
connect(this, &CollapsibleEffectView::refresh, m_view, &AssetParameterView::slotRefresh);
m_keyframesButton->setVisible(m_view->keyframesAllowed());
auto *lay = new QVBoxLayout(widgetFrame);
lay->setContentsMargins(0, 0, 0, 2);
lay->setSpacing(0);
connect(m_keyframesButton, &QToolButton::toggled, [this](bool toggle) {
m_view->toggleKeyframes(toggle);
// We need to switch twice to get a correct resize
slotSwitch(!m_model->isCollapsed());
slotSwitch(!m_model->isCollapsed());
});
lay->addWidget(m_view);
if (!effectParamModel->hasMoreThanOneKeyframe()) {
// No keyframe or only one, allow hiding
bool hideByDefault = effectParamModel->data(effectParamModel->index(0, 0), AssetParameterModel::HideKeyframesFirstRole).toBool();
if (hideByDefault) {
m_view->toggleKeyframes(false);
} else {
m_keyframesButton->setChecked(true);
}
} else {
m_keyframesButton->setChecked(true);
}
// Presets
presetButton->setIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
presetButton->setMenu(m_view->presetMenu());
presetButton->setToolTip(i18n("Presets"));
// Main menu
m_menu = new QMenu(this);
if (effectModel->rowCount() == 0) {
collapseButton->setEnabled(false);
m_view->setVisible(false);
}
m_menu->addAction(QIcon::fromTheme(QStringLiteral("document-save")), i18n("Save Effect"), this, SLOT(slotSaveEffect()));
if (!m_regionEffect) {
/*if (m_info.groupIndex == -1) {
m_menu->addAction(m_groupAction);
}*/
m_menu->addAction(QIcon::fromTheme(QStringLiteral("folder-new")), i18n("Create Region"), this, SLOT(slotCreateRegion()));
}
// setupWidget(info, metaInfo);
menuButton->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
menuButton->setMenu(m_menu);
if (!effectModel->isEnabled()) {
title->setEnabled(false);
m_colorIcon->setEnabled(false);
if (KdenliveSettings::disable_effect_parameters()) {
widgetFrame->setEnabled(false);
}
m_enabledButton->setActive(true);
} else {
m_enabledButton->setActive(false);
}
connect(m_enabledButton, &KDualAction::activeChangedByUser, this, &CollapsibleEffectView::slotDisable);
connect(buttonUp, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectUp);
connect(buttonDown, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotEffectDown);
connect(buttonDel, &QAbstractButton::clicked, this, &CollapsibleEffectView::slotDeleteEffect);
Q_FOREACH (QSpinBox *sp, findChildren()) {
sp->installEventFilter(this);
sp->setFocusPolicy(Qt::StrongFocus);
}
Q_FOREACH (KComboBox *cb, findChildren()) {
cb->installEventFilter(this);
cb->setFocusPolicy(Qt::StrongFocus);
}
Q_FOREACH (QProgressBar *cb, findChildren()) {
cb->installEventFilter(this);
cb->setFocusPolicy(Qt::StrongFocus);
}
m_collapse->setActive(m_model->isCollapsed());
slotSwitch(m_model->isCollapsed());
}
CollapsibleEffectView::~CollapsibleEffectView()
{
qDebug() << "deleting collapsibleeffectview";
}
void CollapsibleEffectView::setWidgetHeight(qreal value)
{
widgetFrame->setFixedHeight(m_view->contentHeight() * value);
}
void CollapsibleEffectView::slotCreateGroup()
{
emit createGroup(m_model);
}
void CollapsibleEffectView::slotCreateRegion()
{
QString allExtensions = ClipCreationDialog::getExtensions().join(QLatin1Char(' '));
const QString dialogFilter =
allExtensions + QLatin1Char(' ') + QLatin1Char('|') + i18n("All Supported Files") + QStringLiteral("\n* ") + QLatin1Char('|') + i18n("All Files");
QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder"));
if (clipFolder.isEmpty()) {
clipFolder = QDir::homePath();
}
QPointer d = new QFileDialog(QApplication::activeWindow(), QString(), clipFolder, dialogFilter);
d->setFileMode(QFileDialog::ExistingFile);
if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) {
KRecentDirs::add(QStringLiteral(":KdenliveClipFolder"), d->selectedUrls().first().adjusted(QUrl::RemoveFilename).toLocalFile());
emit createRegion(effectIndex(), d->selectedUrls().first());
}
delete d;
}
void CollapsibleEffectView::slotUnGroup()
{
emit unGroup(this);
}
bool CollapsibleEffectView::eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::Enter) {
frame->setProperty("mouseover", true);
frame->setStyleSheet(frame->styleSheet());
return QWidget::eventFilter(o, e);
}
if (e->type() == QEvent::Wheel) {
auto *we = static_cast(e);
if (!filterWheelEvent || we->modifiers() != Qt::NoModifier) {
e->accept();
return false;
}
if (qobject_cast(o)) {
// if (qobject_cast(o)->focusPolicy() == Qt::WheelFocus) {
e->accept();
return false;
}
if (qobject_cast