diff --git a/src/assets/assetpanel.cpp b/src/assets/assetpanel.cpp
index 2f9930841..8ef9524e6 100644
--- a/src/assets/assetpanel.cpp
+++ b/src/assets/assetpanel.cpp
@@ -1,354 +1,354 @@
/***************************************************************************
* 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 "assetpanel.hpp"
#include "core.h"
#include "definitions.h"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "effects/effectstack/view/effectstackview.hpp"
#include "kdenlivesettings.h"
#include "model/assetparametermodel.hpp"
#include "transitions/transitionsrepository.hpp"
#include "transitions/view/transitionstackview.hpp"
#include "view/assetparameterview.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
AssetPanel::AssetPanel(QWidget *parent)
: QWidget(parent)
, m_lay(new QVBoxLayout(this))
, m_assetTitle(new KSqueezedTextLabel(this))
, m_container(new QWidget(this))
, m_transitionWidget(new TransitionStackView(this))
, m_effectStackWidget(new EffectStackView(this))
{
auto *buttonToolbar = new QToolBar(this);
buttonToolbar->addWidget(m_assetTitle);
int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
QSize iconSize(size, size);
buttonToolbar->setIconSize(iconSize);
// spacer
QWidget *empty = new QWidget();
empty->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
buttonToolbar->addWidget(empty);
m_switchBuiltStack = new QToolButton(this);
m_switchBuiltStack->setIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_switchBuiltStack->setToolTip(i18n("Adjust clip"));
m_switchBuiltStack->setCheckable(true);
m_switchBuiltStack->setChecked(KdenliveSettings::showbuiltstack());
m_switchBuiltStack->setVisible(false);
// connect(m_switchBuiltStack, &QToolButton::toggled, m_effectStackWidget, &EffectStackView::switchBuiltStack);
buttonToolbar->addWidget(m_switchBuiltStack);
m_splitButton = new KDualAction(i18n("Normal view"), i18n("Compare effect"), this);
m_splitButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("view-right-close")));
m_splitButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right")));
m_splitButton->setToolTip(i18n("Compare effect"));
m_splitButton->setVisible(false);
connect(m_splitButton, &KDualAction::activeChangedByUser, this, &AssetPanel::processSplitEffect);
buttonToolbar->addAction(m_splitButton);
m_enableStackButton = new KDualAction(i18n("Effects disabled"), i18n("Effects enabled"), this);
m_enableStackButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("hint")));
m_enableStackButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("visibility")));
connect(m_enableStackButton, &KDualAction::activeChangedByUser, this, &AssetPanel::enableStack);
m_enableStackButton->setVisible(false);
buttonToolbar->addAction(m_enableStackButton);
m_timelineButton = new KDualAction(i18n("Hide keyframes"), i18n("Display keyframes in timeline"), this);
m_timelineButton->setInactiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_timelineButton->setActiveIcon(QIcon::fromTheme(QStringLiteral("adjustlevels")));
m_timelineButton->setToolTip(i18n("Display keyframes in timeline"));
m_timelineButton->setVisible(false);
connect(m_timelineButton, &KDualAction::activeChangedByUser, this, &AssetPanel::showKeyframes);
buttonToolbar->addAction(m_timelineButton);
m_lay->addWidget(buttonToolbar);
m_lay->setContentsMargins(0, 0, 0, 0);
m_lay->setSpacing(0);
auto *lay = new QVBoxLayout(m_container);
lay->setContentsMargins(0, 0, 0, 0);
lay->addWidget(m_transitionWidget);
lay->addWidget(m_effectStackWidget);
auto *sc = new QScrollArea;
sc->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
sc->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
sc->setFrameStyle(QFrame::NoFrame);
sc->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding));
m_container->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding));
sc->setWidgetResizable(true);
m_lay->addWidget(sc);
sc->setWidget(m_container);
m_transitionWidget->setVisible(false);
m_effectStackWidget->setVisible(false);
updatePalette();
connect(m_effectStackWidget, &EffectStackView::seekToPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::reloadEffect, this, &AssetPanel::reloadEffect);
connect(m_transitionWidget, &TransitionStackView::seekToTransPos, this, &AssetPanel::seekToPos);
connect(m_effectStackWidget, &EffectStackView::updateEnabledState, [this]() { m_enableStackButton->setActive(m_effectStackWidget->isStackEnabled()); });
}
void AssetPanel::showTransition(int tid, const std::shared_ptr &transitionModel)
{
Q_UNUSED(tid)
ObjectId id = transitionModel->getOwnerId();
if (m_transitionWidget->stackOwner() == id) {
// already on this effect stack, do nothing
return;
}
clear();
QString transitionId = transitionModel->getAssetId();
QString transitionName = TransitionsRepository::get()->getName(transitionId);
- m_assetTitle->setText(i18n("%1 properties").arg(i18n(transitionName.toUtf8().data())));
+ m_assetTitle->setText(i18n("%1 properties", i18n(transitionName.toUtf8().data())));
m_transitionWidget->setVisible(true);
m_timelineButton->setVisible(true);
m_enableStackButton->setVisible(false);
m_transitionWidget->setModel(transitionModel, QSize(), true);
}
void AssetPanel::showEffectStack(const QString &itemName, const std::shared_ptr &effectsModel, QSize frameSize, bool showKeyframes)
{
m_splitButton->setActive(false);
if (effectsModel == nullptr) {
// Item is not ready
m_splitButton->setVisible(false);
m_enableStackButton->setVisible(false);
clear();
return;
}
ObjectId id = effectsModel->getOwnerId();
if (m_effectStackWidget->stackOwner() == id) {
// already on this effect stack, do nothing
return;
}
clear();
QString title;
bool showSplit = false;
bool enableKeyframes = false;
switch (id.first) {
case ObjectType::TimelineClip:
title = i18n("%1 effects", itemName);
showSplit = true;
enableKeyframes = true;
break;
case ObjectType::TimelineComposition:
title = i18n("%1 parameters", itemName);
enableKeyframes = true;
break;
case ObjectType::TimelineTrack:
title = i18n("Track %1 effects", itemName);
// TODO: track keyframes
// enableKeyframes = true;
break;
case ObjectType::BinClip:
title = i18n("Bin %1 effects", itemName);
showSplit = true;
break;
default:
title = itemName;
break;
}
m_assetTitle->setText(title);
m_splitButton->setVisible(showSplit);
m_enableStackButton->setVisible(id.first != ObjectType::TimelineComposition);
m_enableStackButton->setActive(effectsModel->isStackEnabled());
if (showSplit) {
m_splitButton->setEnabled(effectsModel->rowCount() > 0);
QObject::connect(effectsModel.get(), &EffectStackModel::dataChanged, [&]() {
if (m_effectStackWidget->isEmpty()) {
m_splitButton->setActive(false);
}
m_splitButton->setEnabled(!m_effectStackWidget->isEmpty());
});
}
m_timelineButton->setVisible(enableKeyframes);
m_timelineButton->setActive(showKeyframes);
// Disable built stack until properly implemented
// m_switchBuiltStack->setVisible(true);
m_effectStackWidget->setVisible(true);
m_effectStackWidget->setModel(effectsModel, frameSize);
}
void AssetPanel::clearAssetPanel(int itemId)
{
ObjectId id = m_effectStackWidget->stackOwner();
if (id.first == ObjectType::TimelineClip && id.second == itemId) {
clear();
} else {
id = m_transitionWidget->stackOwner();
if (id.first == ObjectType::TimelineComposition && id.second == itemId) {
clear();
}
}
}
void AssetPanel::clear()
{
m_transitionWidget->setVisible(false);
m_transitionWidget->unsetModel();
m_effectStackWidget->setVisible(false);
m_splitButton->setVisible(false);
m_timelineButton->setVisible(false);
m_switchBuiltStack->setVisible(false);
m_effectStackWidget->unsetModel();
m_assetTitle->setText(QString());
}
void AssetPanel::updatePalette()
{
QString styleSheet = getStyleSheet();
setStyleSheet(styleSheet);
m_transitionWidget->setStyleSheet(styleSheet);
m_effectStackWidget->setStyleSheet(styleSheet);
}
// static
const QString AssetPanel::getStyleSheet()
{
KColorScheme scheme(QApplication::palette().currentColorGroup(), KColorScheme::View);
QColor selected_bg = scheme.decoration(KColorScheme::FocusColor).color();
QColor hgh = KColorUtils::mix(QApplication::palette().window().color(), selected_bg, 0.2);
QColor hover_bg = scheme.decoration(KColorScheme::HoverColor).color();
QColor light_bg = scheme.shade(KColorScheme::LightShade);
QColor alt_bg = scheme.background(KColorScheme::NormalBackground).color();
QString stylesheet;
// effect background
stylesheet.append(QStringLiteral("QFrame#decoframe {border-bottom:2px solid "
"palette(mid);background: transparent} QFrame#decoframe[active=\"true\"] {background: %1;}")
.arg(hgh.name()));
// effect in group background
stylesheet.append(
QStringLiteral("QFrame#decoframesub {border-top:1px solid palette(light);} QFrame#decoframesub[active=\"true\"] {background: %1;}").arg(hgh.name()));
// group background
stylesheet.append(QStringLiteral("QFrame#decoframegroup {border:2px solid palette(dark);margin:0px;margin-top:2px;} "));
// effect title bar
stylesheet.append(QStringLiteral("QFrame#frame {margin-bottom:2px;} QFrame#frame[target=\"true\"] "
"{background: palette(highlight);}"));
// group effect title bar
stylesheet.append(QStringLiteral("QFrame#framegroup {background: palette(dark);} "
"QFrame#framegroup[target=\"true\"] {background: palette(highlight);} "));
// draggable effect bar content
stylesheet.append(QStringLiteral("QProgressBar::chunk:horizontal {background: palette(button);border-top-left-radius: 4px;border-bottom-left-radius: 4px;} "
"QProgressBar::chunk:horizontal#dragOnly {background: %1;border-top-left-radius: 4px;border-bottom-left-radius: 4px;} "
"QProgressBar::chunk:horizontal:hover {background: %2;}")
.arg(alt_bg.name(), selected_bg.name()));
// draggable effect bar
stylesheet.append(QStringLiteral("QProgressBar:horizontal {border: 1px solid palette(dark);border-top-left-radius: 4px;border-bottom-left-radius: "
"4px;border-right:0px;background:%3;padding: 0px;text-align:left center} QProgressBar:horizontal:disabled {border: 1px "
"solid palette(button)} QProgressBar:horizontal#dragOnly {background: %3} QProgressBar:horizontal[inTimeline=\"true\"] { "
"border: 1px solid %1;border-right: 0px;background: %2;padding: 0px;text-align:left center } "
"QProgressBar::chunk:horizontal[inTimeline=\"true\"] {background: %1;}")
.arg(hover_bg.name(), light_bg.name(), alt_bg.name()));
// spin box for draggable widget
stylesheet.append(
QStringLiteral("QAbstractSpinBox#dragBox {border: 1px solid palette(dark);border-top-right-radius: 4px;border-bottom-right-radius: "
"4px;padding-right:0px;} QAbstractSpinBox::down-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox:disabled#dragBox {border: 1px "
"solid palette(button);} QAbstractSpinBox::up-button#dragBox {width:0px;padding:0px;} QAbstractSpinBox[inTimeline=\"true\"]#dragBox { "
"border: 1px solid %1;} QAbstractSpinBox:hover#dragBox {border: 1px solid %2;} ")
.arg(hover_bg.name(), selected_bg.name()));
// group editable labels
stylesheet.append(QStringLiteral("MyEditableLabel { background-color: transparent; color: palette(bright-text); border-radius: 2px;border: 1px solid "
"transparent;} MyEditableLabel:hover {border: 1px solid palette(highlight);} "));
// transparent qcombobox
stylesheet.append(QStringLiteral("QComboBox { background-color: transparent;} "));
return stylesheet;
}
void AssetPanel::processSplitEffect(bool enable)
{
ObjectType id = m_effectStackWidget->stackOwner().first;
if (id == ObjectType::TimelineClip) {
emit doSplitEffect(enable);
} else if (id == ObjectType::BinClip) {
emit doSplitBinEffect(enable);
}
}
void AssetPanel::showKeyframes(bool enable)
{
if (m_transitionWidget->isVisible()) {
pCore->showClipKeyframes(m_transitionWidget->stackOwner(), enable);
} else {
pCore->showClipKeyframes(m_effectStackWidget->stackOwner(), enable);
}
}
ObjectId AssetPanel::effectStackOwner()
{
if (m_transitionWidget->isVisible()) {
return m_transitionWidget->stackOwner();
}
if (!m_effectStackWidget->isVisible()) {
return ObjectId(ObjectType::NoItem, -1);
}
return m_effectStackWidget->stackOwner();
}
bool AssetPanel::addEffect(const QString &effectId)
{
if (!m_effectStackWidget->isVisible()) {
return false;
}
return m_effectStackWidget->addEffect(effectId);
}
void AssetPanel::enableStack(bool enable)
{
if (!m_effectStackWidget->isVisible()) {
return;
}
m_effectStackWidget->enableStack(enable);
}
void AssetPanel::deleteCurrentEffect()
{
if (m_effectStackWidget->isVisible()) {
m_effectStackWidget->removeCurrentEffect();
}
}
diff --git a/src/assets/model/assetcommand.cpp b/src/assets/model/assetcommand.cpp
index 3511340bd..4317d8c7d 100644
--- a/src/assets/model/assetcommand.cpp
+++ b/src/assets/model/assetcommand.cpp
@@ -1,212 +1,220 @@
/***************************************************************************
* Copyright (C) 2017 by by Jean-Baptiste Mardelle *
* 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 "assetcommand.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "effects/effectsrepository.hpp"
#include "transitions/transitionsrepository.hpp"
#include
#include
AssetCommand::AssetCommand(const std::shared_ptr &model, const QModelIndex &index, QString value, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_index(index)
, m_value(std::move(value))
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
QLocale locale;
m_name = m_model->data(index, AssetParameterModel::NameRole).toString();
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
- setText(i18n("Edit %1").arg(i18n(EffectsRepository::get()->getName(id).toUtf8().data())));
+ QString effectName = EffectsRepository::get()->getName(id);
+ setText(i18n("Edit %1", i18n(effectName.toUtf8().data())));
} else if (TransitionsRepository::get()->exists(id)) {
- setText(i18n("Edit %1").arg(i18n(TransitionsRepository::get()->getName(id).toUtf8().data())));
+ QString compoName = TransitionsRepository::get()->getName(id);
+ setText(i18n("Edit %1", i18n(compoName.toUtf8().data())));
}
QVariant previousVal = m_model->data(index, AssetParameterModel::ValueRole);
m_oldValue = previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString();
}
void AssetCommand::undo()
{
m_model->setParameter(m_name, m_oldValue, true, m_index);
}
// virtual
void AssetCommand::redo()
{
m_model->setParameter(m_name, m_value, m_updateView, m_index);
m_updateView = true;
}
// virtual
int AssetCommand::id() const
{
return 1;
}
// virtual
bool AssetCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_index != m_index ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) {
return false;
}
m_value = static_cast(other)->m_value;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetMultiCommand::AssetMultiCommand(const std::shared_ptr &model, const QList indexes, const QStringList values, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_indexes(indexes)
, m_values(values)
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
QLocale locale;
qDebug()<<"CREATING MULTIPLE COMMAND!!!\nVALUES: "<data(indexes.first(), AssetParameterModel::NameRole).toString();
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
- setText(i18n("Edit %1").arg(i18n(EffectsRepository::get()->getName(id).toUtf8().data())));
+ QString effectName = EffectsRepository::get()->getName(id);
+ setText(i18n("Edit %1", i18n(effectName.toUtf8().data())));
} else if (TransitionsRepository::get()->exists(id)) {
- setText(i18n("Edit %1").arg(i18n(TransitionsRepository::get()->getName(id).toUtf8().data())));
+ QString compoName = TransitionsRepository::get()->getName(id);
+ setText(i18n("Edit %1", i18n(compoName.toUtf8().data())));
}
for (QModelIndex ix : m_indexes) {
QVariant previousVal = m_model->data(ix, AssetParameterModel::ValueRole);
m_oldValues << (previousVal.type() == QVariant::Double ? locale.toString(previousVal.toDouble()) : previousVal.toString());
}
}
void AssetMultiCommand::undo()
{
int indx = 0;
int max = m_indexes.size() - 1;
for (const QModelIndex &ix : m_indexes) {
m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_oldValues.at(indx), indx == max, ix);
indx++;
}
}
// virtual
void AssetMultiCommand::redo()
{
int indx = 0;
int max = m_indexes.size() - 1;
for (const QModelIndex &ix : m_indexes) {
m_model->setParameter(m_model->data(ix, AssetParameterModel::NameRole).toString(), m_values.at(indx), m_updateView && indx == max, ix);
indx++;
}
m_updateView = true;
}
// virtual
int AssetMultiCommand::id() const
{
return 1;
}
// virtual
bool AssetMultiCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_indexes != m_indexes ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 3000) {
return false;
}
m_values = static_cast(other)->m_values;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetKeyframeCommand::AssetKeyframeCommand(const std::shared_ptr &model, const QModelIndex &index, QVariant value, GenTime pos,
QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_index(index)
, m_value(std::move(value))
, m_pos(pos)
, m_updateView(false)
, m_stamp(QTime::currentTime())
{
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
- setText(i18n("Edit %1 keyframe", EffectsRepository::get()->getName(id)));
+ QString effectName = EffectsRepository::get()->getName(id);
+ setText(i18n("Edit %1 keyframe", i18n(effectName.toUtf8().data())));
} else if (TransitionsRepository::get()->exists(id)) {
- setText(i18n("Edit %1 keyframe", TransitionsRepository::get()->getName(id)));
+ QString compoName = TransitionsRepository::get()->getName(id);
+ setText(i18n("Edit %1 keyframe", i18n(compoName.toUtf8().data())));
}
m_oldValue = m_model->getKeyframeModel()->getKeyModel(m_index)->getInterpolatedValue(m_pos);
}
void AssetKeyframeCommand::undo()
{
m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_oldValue);
}
// virtual
void AssetKeyframeCommand::redo()
{
m_model->getKeyframeModel()->getKeyModel(m_index)->directUpdateKeyframe(m_pos, m_value);
m_updateView = true;
}
// virtual
int AssetKeyframeCommand::id() const
{
return 2;
}
// virtual
bool AssetKeyframeCommand::mergeWith(const QUndoCommand *other)
{
if (other->id() != id() || static_cast(other)->m_index != m_index ||
m_stamp.msecsTo(static_cast(other)->m_stamp) > 1000) {
return false;
}
m_value = static_cast(other)->m_value;
m_stamp = static_cast(other)->m_stamp;
return true;
}
AssetUpdateCommand::AssetUpdateCommand(const std::shared_ptr &model, QVector> parameters, QUndoCommand *parent)
: QUndoCommand(parent)
, m_model(model)
, m_value(std::move(parameters))
{
const QString id = model->getAssetId();
if (EffectsRepository::get()->exists(id)) {
- setText(i18n("Update %1", EffectsRepository::get()->getName(id)));
+ QString effectName = EffectsRepository::get()->getName(id);
+ setText(i18n("Update %1", i18n(effectName.toUtf8().data())));
} else if (TransitionsRepository::get()->exists(id)) {
- setText(i18n("Update %1", TransitionsRepository::get()->getName(id)));
+ QString compoName = TransitionsRepository::get()->getName(id);
+ setText(i18n("Update %1", i18n(compoName.toUtf8().data())));
}
m_oldValue = m_model->getAllParameters();
}
void AssetUpdateCommand::undo()
{
m_model->setParameters(m_oldValue);
}
// virtual
void AssetUpdateCommand::redo()
{
m_model->setParameters(m_value);
}
// virtual
int AssetUpdateCommand::id() const
{
return 3;
}
diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp
index a51a0b106..53745833f 100644
--- a/src/effects/effectstack/model/effectstackmodel.cpp
+++ b/src/effects/effectstack/model/effectstackmodel.cpp
@@ -1,1199 +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"));
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())));
+ PUSH_UNDO(undo, redo, i18n("Add effect %1", 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));
}