diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp index af2bf27c6..db4a48bd0 100644 --- a/src/effects/effectstack/model/effectstackmodel.cpp +++ b/src/effects/effectstack/model/effectstackmodel.cpp @@ -1,750 +1,754 @@ /*************************************************************************** * 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 #include #include EffectStackModel::EffectStackModel(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack) : AbstractTreeModel() , m_effectStackEnabled(true) , m_ownerId(ownerId) , m_undoStack(undo_stack) , m_loadingExisting(false) , m_lock(QReadWriteLock::Recursive) { m_services.emplace_back(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, undo_stack)); self->rootItem = EffectGroupModel::construct(QStringLiteral("root"), self, true); return self; } void EffectStackModel::resetService(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_services.clear(); m_services.emplace_back(std::move(service)); // replant all effects in new service for (int i = 0; i < rootItem->childCount(); ++i) { for (const auto &s : m_services) { std::static_pointer_cast(rootItem->child(i))->plant(s); } } } void EffectStackModel::addService(std::weak_ptr service) { QWriteLocker locker(&m_lock); m_services.emplace_back(std::move(service)); for (int i = 0; i < rootItem->childCount(); ++i) { std::static_pointer_cast(rootItem->child(i))->plant(m_services.back()); } } void EffectStackModel::removeService(std::shared_ptr service) { QWriteLocker locker(&m_lock); std::vector to_delete; for (int i = int(m_services.size()) - 1; i >= 0; --i) { if (service.get() == m_services[uint(i)].lock().get()) { to_delete.push_back(i); } } for (int i : to_delete) { m_services.erase(m_services.begin() + i); } } void EffectStackModel::removeEffect(std::shared_ptr effect) { 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; bool currentChanged = false; for (const auto &service : m_services) { if (auto srv = service.lock()) { current = srv->get_int("kdenlive:activeeffect"); if (current >= rootItem->childCount() - 1) { currentChanged = true; 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(fadeIns.size()); int outFades = int(fadeOuts.size()); fadeIns.erase(effect->getId()); fadeOuts.erase(effect->getId()); inFades = int(fadeIns.size()) - inFades; outFades = int(fadeOuts.size()) - outFades; QString effectName = EffectsRepository::get()->getName(effect->getAssetId()); Fun update = [this, current, currentChanged, inFades, outFades]() { // Required to build the effect view if (currentChanged) { if (current < 0 || rowCount() == 0) { // Stack is now empty emit dataChanged(QModelIndex(), QModelIndex(), QVector()); } else { std::shared_ptr effectItem = std::static_pointer_cast(rootItem->child(current)); QModelIndex ix = getIndexFromItem(effectItem); emit dataChanged(ix, ix, QVector()); } } // 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; }; update(); PUSH_LAMBDA(update, redo); PUSH_LAMBDA(update, undo); PUSH_UNDO(undo, redo, i18n("Delete effect %1", effectName)); } } void EffectStackModel::copyEffect(std::shared_ptr sourceItem, bool logUndo) { QWriteLocker locker(&m_lock); if (sourceItem->childCount() > 0) { // TODO: group return; } std::shared_ptr sourceEffect = std::static_pointer_cast(sourceItem); const QString effectId = sourceEffect->getAssetId(); auto effect = EffectItemModel::construct(effectId, shared_from_this()); effect->setParameters(sourceEffect->getAllParameters()); effect->filter().set("in", sourceEffect->filter().get_int("in")); effect->filter().set("out", sourceEffect->filter().get_int("out")); Fun undo = removeItem_lambda(effect->getId()); // TODO the parent should probably not always be the root Fun redo = addItem_lambda(effect, rootItem->getId()); 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")) { fadeIns.insert(effect->getId()); } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { fadeOuts.insert(effect->getId()); } bool res = redo(); if (res && logUndo) { QString effectName = EffectsRepository::get()->getName(effectId); PUSH_UNDO(undo, redo, i18n("copy effect %1", effectName)); } } void EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent) { QWriteLocker locker(&m_lock); auto effect = EffectItemModel::construct(effectId, shared_from_this()); Fun undo = removeItem_lambda(effect->getId()); // TODO the parent should probably not always be the root Fun redo = addItem_lambda(effect, rootItem->getId()); connect(effect.get(), &AssetParameterModel::modelChanged, this, &EffectStackModel::modelChanged); connect(effect.get(), &AssetParameterModel::replugEffect, this, &EffectStackModel::replugEffect, Qt::DirectConnection); int currentActive = getActiveEffect(); if (makeCurrent) { for (const auto &service : m_services) { auto srvPtr = service.lock(); if (srvPtr) { 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")) { fadeIns.insert(effect->getId()); inFades++; } else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) { fadeOuts.insert(effect->getId()); outFades++; } QString effectName = EffectsRepository::get()->getName(effectId); Fun update = [this, inFades, outFades]() { // 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); emit dataChanged(QModelIndex(), QModelIndex(), QVector()); return true; }; update(); PUSH_LAMBDA(update, redo); PUSH_LAMBDA(update, undo); PUSH_UNDO(undo, redo, i18n("Add effect %1", effectName)); } else if (makeCurrent) { for (const auto &service : m_services) { auto srvPtr = service.lock(); if (srvPtr) { srvPtr->set("kdenlive:activeeffect", currentActive); } } } } bool EffectStackModel::adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, 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 && 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 = [this, 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 = [this, 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 = [this, effect, oldEffectIn, effectDuration, logUndo]() { effect->setParameter(QStringLiteral("out"), oldEffectIn + effectDuration, logUndo); return true; }; bool res = operation(); if (!res) { return false; } if (logUndo) { Fun reverse = [this, 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 && 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 = [this, 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 = [this, 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, undo, redo); QModelIndex index = getIndexFromItem(effect); Fun refresh = [this, effect, index]() { effect->dataChanged(index, index, QVector()); return true; }; refresh(); PUSH_LAMBDA(refresh, redo); PUSH_LAMBDA(refresh, undo); } } } return true; } bool EffectStackModel::adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade) { QWriteLocker locker(&m_lock); if (fromStart) { // Fade in if (fadeIns.empty()) { if (audioFade) { appendEffect(QStringLiteral("fadein")); } if (videoFade) { appendEffect(QStringLiteral("fade_from_black")); } } QList indexes; auto ptr = m_services.front().lock(); int in = 0; if (ptr) { in = ptr->get_int("in"); } qDebug() << "//// SETTING CLIP FADIN: " << duration; for (int i = 0; i < rootItem->childCount(); ++i) { if (fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); 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")); } } else { // Fade out if (fadeOuts.empty()) { if (audioFade) { appendEffect(QStringLiteral("fadeout")); } if (videoFade) { appendEffect(QStringLiteral("fade_to_black")); } } int in = 0; auto ptr = m_services.front().lock(); if (ptr) { in = ptr->get_int("in"); } int out = in + pCore->getItemDuration(m_ownerId); QList indexes; for (int i = 0; i < rootItem->childCount(); ++i) { if (fadeOuts.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) { std::shared_ptr effect = std::static_pointer_cast(rootItem->child(i)); effect->filter().set("out", out); duration = qMin(pCore->getItemDuration(m_ownerId), duration); effect->filter().set("in", out - duration); indexes << getIndexFromItem(effect); } } if (!indexes.isEmpty()) { qDebug() << "// UPDATING DATA INDEXES 2!!!"; emit dataChanged(indexes.first(), indexes.last(), QVector()); pCore->updateItemModel(m_ownerId, QStringLiteral("fadeout")); } } return true; } int EffectStackModel::getFadePosition(bool fromStart) { QWriteLocker locker(&m_lock); if (fromStart) { if (fadeIns.empty()) { return 0; } for (int i = 0; i < rootItem->childCount(); ++i) { if (*(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(); } } } else { if (fadeOuts.empty()) { return 0; } for (int i = 0; i < rootItem->childCount(); ++i) { if (*(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(); } } } return 0; } bool EffectStackModel::removeFade(bool fromStart) { QWriteLocker locker(&m_lock); std::vector toRemove; for (int i = 0; i < rootItem->childCount(); ++i) { if ((fromStart && fadeIns.count(std::static_pointer_cast(rootItem->child(i))->getId()) > 0) || (!fromStart && 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, 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(), {}); 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", effectName)); } } 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_services.size(); for (const auto &service : m_services) { qDebug() << "$$$$$$$$$$$$$$$$$$$$$ Planting effect in " << (void *)service.lock().get(); effectItem->plant(service); } } effectItem->setEffectStackEnabled(m_effectStackEnabled); ix = getIndexFromItem(effectItem); if (!effectItem->isAudio() && !m_loadingExisting) { pCore->refreshProjectItem(m_ownerId); pCore->invalidateItem(m_ownerId); } } AbstractTreeModel::registerItem(item); if (ix.isValid()) { // Required to build the effect view emit dataChanged(ix, ix, QVector()); } } void EffectStackModel::deregisterItem(int id, TreeItem *item) { QWriteLocker locker(&m_lock); if (!item->isRoot()) { auto effectItem = static_cast(item); for (const auto &service : m_services) { effectItem->unplant(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); } } std::shared_ptr EffectStackModel::getEffectStackRow(int row, std::shared_ptr parentItem) { return std::static_pointer_cast(parentItem ? rootItem->child(row) : rootItem->child(row)); } void EffectStackModel::importEffects(std::shared_ptr sourceStack) { QWriteLocker locker(&m_lock); // TODO: manage fades, keyframes if clips don't have same size / in point for (int i = 0; i < sourceStack->rowCount(); i++) { auto item = sourceStack->getEffectStackRow(i); copyEffect(item, false); } modelChanged(); } void EffectStackModel::importEffects(std::weak_ptr service, bool alreadyExist) { QWriteLocker locker(&m_lock); m_loadingExisting = alreadyExist; if (auto ptr = service.lock()) { for (int i = 0; i < ptr->filter_count(); i++) { if (ptr->filter(i)->get("kdenlive_id") == nullptr) { // don't consider internal MLT stuff continue; } QString effectId = ptr->filter(i)->get("kdenlive_id"); auto effect = EffectItemModel::construct(ptr->filter(i), shared_from_this()); copyEffect(effect, false); } } m_loadingExisting = false; modelChanged(); } void EffectStackModel::setActiveEffect(int ix) { QWriteLocker locker(&m_lock); for (const auto &service : m_services) { auto ptr = service.lock(); if (ptr) { ptr->set("kdenlive:activeeffect", ix); } } pCore->updateItemKeyframes(m_ownerId); } int EffectStackModel::getActiveEffect() const { QWriteLocker locker(&m_lock); auto ptr = m_services.front().lock(); if (ptr) { return ptr->get_int("kdenlive:activeeffect"); } return 0; } void EffectStackModel::slotCreateGroup(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_services) { auto ptr = service.lock(); if (!ptr) { qDebug() << "ERROR: unavailable service"; return false; } if (ptr->filter_count() != (int)allFilters.size()) { qDebug() << "ERROR: Wrong filter count"; return false; } for (uint i = 0; i < allFilters.size(); ++i) { auto mltFilter = ptr->filter((int)i)->get_filter(); auto currentFilter = allFilters[i]->filter().get_filter(); if (mltFilter != currentFilter) { qDebug() << "ERROR: filter " << i << "differ"; return false; } } } 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; } } } -bool EffectStackModel::hasFilter(const QString &effectId) +bool EffectStackModel::hasFilter(const QString &effectId) const { - for (int i = 0; i < rootItem->childCount(); ++i) { - std::shared_ptr sourceEffect = std::static_pointer_cast(rootItem->child(i)); - if (effectId == sourceEffect->getAssetId()) { - return true; + 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; } - } - return false; + auto sourceEffect = std::static_pointer_cast(it); + return effectId == sourceEffect->getAssetId(); + }); } -double EffectStackModel::getFilter(const QString &effectId, const QString ¶mName) +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; auto ptr = m_services.front().lock(); if (ptr) { ix = ptr->get_int("kdenlive:activeeffect"); } if (ix < 0) { 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(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)); for (const auto &service : m_services) { item->unplant(service); } } Mlt::Properties *effect = EffectsRepository::get()->getEffect(effectItem->getAssetId()); effect->inherit(effectItem->filter()); effectItem->resetAsset(effect); for (int ix = oldRow; ix < count; ix++) { auto item = std::static_pointer_cast(rootItem->child(ix)); for (const auto &service : m_services) { item->plant(service); } } } void EffectStackModel::cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo) { QWriteLocker locker(&m_lock); const auto &toDelete = outEffects ? fadeOuts : 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 update = [this]() { // TODO: only update if effect is fade or keyframe pCore->updateItemKeyframes(m_ownerId); return true; }; update(); PUSH_LAMBDA(update, redo); } } diff --git a/src/effects/effectstack/model/effectstackmodel.hpp b/src/effects/effectstack/model/effectstackmodel.hpp index 569fcd4e5..4e927dd84 100644 --- a/src/effects/effectstack/model/effectstackmodel.hpp +++ b/src/effects/effectstack/model/effectstackmodel.hpp @@ -1,158 +1,161 @@ /*************************************************************************** * 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 . * ***************************************************************************/ #ifndef EFFECTSTACKMODEL_H #define EFFECTSTACKMODEL_H #include "abstractmodel/abstracttreemodel.hpp" #include "definitions.h" #include "undohelper.hpp" #include #include #include #include /* @brief This class an effect stack as viewed by the back-end. It is responsible for planting and managing effects into the list of producer it holds a pointer to. It can contains more than one producer for example if it represents the effect stack of a projectClip: this clips contains several producers (audio, video, ...) */ class AbstractEffectItem; class AssetParameterModel; class DocUndoStack; class EffectItemModel; class TreeItem; class KeyframeModel; class EffectStackModel : public AbstractTreeModel { Q_OBJECT public: /* @brief Constructs an effect stack and returns a shared ptr to the constucted object @param service is the mlt object on which we will plant the effects @param ownerId is some information about the actual object to which the effects are applied */ static std::shared_ptr construct(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack); protected: EffectStackModel(std::weak_ptr service, ObjectId ownerId, std::weak_ptr undo_stack); public: /* @brief Add an effect at the bottom of the stack */ void appendEffect(const QString &effectId, bool makeCurrent = false); /* @brief Copy an existing effect and append it at the bottom of the stack @param logUndo: if true, an undo/redo is created */ void copyEffect(std::shared_ptr sourceItem, bool logUndo = true); /* @brief Import all effects from the given effect stack */ void importEffects(std::shared_ptr sourceStack); /* @brief Import all effects attached to a given service @param alreadyExist: if true, the effect should be already attached to the service owned by this effectstack (it means we are in the process of loading). In that case, we need to build the stack but not replant the effects */ void importEffects(std::weak_ptr service, bool alreadyExist = false); bool removeFade(bool fromStart); /* @brief This function change the global (timeline-wise) enabled state of the effects */ void setEffectStackEnabled(bool enabled); /* @brief Returns an effect or group from the stack (at the given row) */ std::shared_ptr getEffectStackRow(int row, std::shared_ptr parentItem = nullptr); /* @brief Move an effect in the stack */ void moveEffect(int destRow, std::shared_ptr item); /* @brief Set effect in row as current one */ void setActiveEffect(int ix); /* @brief Get currently active effect row */ int getActiveEffect() const; /* @brief Adjust an effect duration (useful for fades) */ bool adjustFadeLength(int duration, bool fromStart, bool audioFade, bool videoFade); bool adjustStackLength(bool adjustFromEnd, int oldIn, int oldDuration, int newIn, int duration, Fun &undo, Fun &redo, bool logUndo); void slotCreateGroup(std::shared_ptr childEffect); /* @brief Returns the id of the owner of the stack */ ObjectId getOwnerId() const; int getFadePosition(bool fromStart); Q_INVOKABLE void adjust(const QString &effectId, const QString &effectName, double value); - Q_INVOKABLE bool hasFilter(const QString &effectId); - Q_INVOKABLE double getFilter(const QString &effectId, const QString ¶mName); + + /* @brief Returns true if the stack contains an effect with the given Id */ + Q_INVOKABLE bool hasFilter(const QString &effectId) const; + // TODO: this break the encapsulation, remove + Q_INVOKABLE double getFilterParam(const QString &effectId, const QString ¶mName); /** get the active effect's keyframe model */ Q_INVOKABLE KeyframeModel *getEffectKeyframeModel(); /** Remove unwanted fade effects, mostly after a cut operation */ void cleanFadeEffects(bool outEffects, Fun &undo, Fun &redo); /* Remove all the services associated with this stack and replace them with the given one */ void resetService(std::weak_ptr service); /* @brief Append a new service to be managed by this stack */ void addService(std::weak_ptr service); /* @brief Remove a service from those managed by this stack */ void removeService(std::shared_ptr service); public slots: /* @brief Delete an effect from the stack */ void removeEffect(std::shared_ptr effect); protected: /* @brief Register the existence of a new element */ void registerItem(const std::shared_ptr &item) override; /* @brief Deregister the existence of a new element*/ void deregisterItem(int id, TreeItem *item) override; /* @brief This is a convenience function that helps check if the tree is in a valid state */ bool checkConsistency() override; std::vector> m_services; bool m_effectStackEnabled; ObjectId m_ownerId; std::weak_ptr m_undoStack; private: mutable QReadWriteLock m_lock; std::unordered_set fadeIns; std::unordered_set fadeOuts; /** @brief: When loading a project, we load filters/effects that are already planted * in the producer, so we shouldn't plant them again. Setting this value to * true will prevent planting in the producer */ bool m_loadingExisting; private slots: /** @brief: Some effects do not support dynamic changes like sox, and need to be unplugged / replugged on each param change */ void replugEffect(std::shared_ptr asset); signals: /** @brief: This signal is connected to the project clip for bin clips and activates the reload of effects on child (timeline) producers */ void modelChanged(); }; #endif diff --git a/src/effects/effectstack/view/qml/LiftGammaGain.qml b/src/effects/effectstack/view/qml/LiftGammaGain.qml index 20b8cbc67..3bdfbd0e3 100644 --- a/src/effects/effectstack/view/qml/LiftGammaGain.qml +++ b/src/effects/effectstack/view/qml/LiftGammaGain.qml @@ -1,95 +1,95 @@ import QtQuick 2.6 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import Kdenlive.Controls 1.0 import QtQuick.Layouts 1.3 Item { id: liftgammagain property string effectName: 'lift_gamma_gain' property double liftFactor : 2.0 property double gammaFactor: 2.0 property double gainFactor: 4.0 Layout.fillWidth: true function loadWheels() { if (!effectstackmodel.hasFilter(liftgammagain.effectName)) { // Set default parameter values liftwheel.setColor(0. / liftFactor, 0. / liftFactor, 0. / liftFactor, 1.0); gammawheel.setColor(1.0 / gammaFactor, 1.0 / gammaFactor, 1.0 / gammaFactor, 1.0); gainwheel.setColor(1.0 / gainFactor, 1.0 / gainFactor, 1.0 / gainFactor, 1.0); } else { - liftwheel.setColor(effectstackmodel.getFilter(liftgammagain.effectName, 'lift_r') / liftFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'lift_g') / liftFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'lift_b') / liftFactor, + liftwheel.setColor(effectstackmodel.getFilterParam(liftgammagain.effectName, 'lift_r') / liftFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'lift_g') / liftFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'lift_b') / liftFactor, 1.0 ) - gammawheel.setColor(effectstackmodel.getFilter(liftgammagain.effectName, 'gamma_r') / gammaFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'gamma_g') / gammaFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'gamma_b') / gammaFactor, + gammawheel.setColor(effectstackmodel.getFilterParam(liftgammagain.effectName, 'gamma_r') / gammaFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'gamma_g') / gammaFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'gamma_b') / gammaFactor, 1.0 ) - gainwheel.setColor(effectstackmodel.getFilter(liftgammagain.effectName, 'gain_r') / gainFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'gain_g') / gainFactor, - effectstackmodel.getFilter(liftgammagain.effectName, 'gain_b') / gainFactor, + gainwheel.setColor(effectstackmodel.getFilterParam(liftgammagain.effectName, 'gain_r') / gainFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'gain_g') / gainFactor, + effectstackmodel.getFilterParam(liftgammagain.effectName, 'gain_b') / gainFactor, 1.0 ) } } RowLayout { spacing: 0 Layout.fillWidth: true Column { height: parent.height width: liftgammagain.width / 3 Label { text: 'Lift' } ColorWheelItem { id: liftwheel width: liftgammagain.width / 3 height: width onColorChanged: { effectstackmodel.adjust(liftgammagain.effectName, 'lift_r', liftwheel.red ); effectstackmodel.adjust(liftgammagain.effectName, 'lift_g', liftwheel.green ); effectstackmodel.adjust(liftgammagain.effectName, 'lift_b', liftwheel.blue ); } } } Column { height: parent.height width: liftgammagain.width / 3 Label { text: 'Gamma' } ColorWheelItem { id: gammawheel width: liftgammagain.width / 3 height: width onColorChanged: { effectstackmodel.adjust(liftgammagain.effectName, 'gamma_r', gammawheel.red); effectstackmodel.adjust(liftgammagain.effectName, 'gamma_g', gammawheel.green); effectstackmodel.adjust(liftgammagain.effectName, 'gamma_b', gammawheel.blue); } } } Column { height: parent.height width: liftgammagain.width / 3 Label { text: 'Gain' } ColorWheelItem { id: gainwheel width: liftgammagain.width / 3 height: width onColorChanged: { effectstackmodel.adjust(liftgammagain.effectName, 'gain_r', gainwheel.red); effectstackmodel.adjust(liftgammagain.effectName, 'gain_g', gainwheel.green); effectstackmodel.adjust(liftgammagain.effectName, 'gain_b', gainwheel.blue); } } } } Component.onCompleted: { liftwheel.setFactorDefaultZero(liftgammagain.liftFactor, 0, 0.5); gammawheel.setFactorDefaultZero(liftgammagain.gammaFactor, 1, 0); gainwheel.setFactorDefaultZero(liftgammagain.gainFactor, 1, 0); } }