Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

timeline effets: do not allow audio effects on video clips.

Integrate paste effects with undo/redo
parent b2859a8f
......@@ -96,7 +96,7 @@ std::shared_ptr<ProjectClip> ProjectClip::construct(const QString &id, const QIc
{
std::shared_ptr<ProjectClip> self(new ProjectClip(id, thumb, model, producer));
baseFinishConstruct(self);
self->m_effectStack->importEffects(producer, true);
self->m_effectStack->importEffects(producer, PlaylistState::Disabled, true);
model->loadSubClips(id, self->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipzone.")));
return self;
}
......
......@@ -146,12 +146,34 @@ void EffectStackModel::removeEffect(std::shared_ptr<EffectItemModel> effect)
}
}
void EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem, bool logUndo)
bool EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem, PlaylistState::ClipState state, bool logUndo)
{
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
bool result = copyEffect(sourceItem, state, undo, redo);
if (result && logUndo) {
std::shared_ptr<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(sourceItem);
QString effectName = EffectsRepository::get()->getName(sourceEffect->getAssetId());
PUSH_UNDO(undo, redo, i18n("copy effect %1", effectName));
}
return result;
}
bool EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem, PlaylistState::ClipState state, Fun &undo, Fun &redo)
{
QWriteLocker locker(&m_lock);
if (sourceItem->childCount() > 0) {
// TODO: group
return;
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<EffectItemModel> sourceEffect = std::static_pointer_cast<EffectItemModel>(sourceItem);
const QString effectId = sourceEffect->getAssetId();
......@@ -159,9 +181,9 @@ void EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem
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());
Fun local_undo = removeItem_lambda(effect->getId());
// TODO the parent should probably not always be the root
Fun redo = addItem_lambda(effect, rootItem->getId());
Fun local_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")) {
......@@ -169,13 +191,18 @@ void EffectStackModel::copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
fadeOuts.insert(effect->getId());
}
bool res = redo();
if (res && logUndo) {
QModelIndex ix = getIndexFromItem(effect);
emit dataChanged(ix, ix, QVector<int>());
QString effectName = EffectsRepository::get()->getName(effectId);
PUSH_UNDO(undo, redo, i18n("copy effect %1", effectName));
bool res = local_redo();
if (res) {
Fun update = [this]() {
emit dataChanged(QModelIndex(), QModelIndex(), QVector<int>());
return true;
};
update();
PUSH_LAMBDA(update, local_redo);
PUSH_LAMBDA(update, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
return res;
}
void EffectStackModel::appendEffect(const QString &effectId, bool makeCurrent)
......@@ -537,18 +564,42 @@ std::shared_ptr<AbstractEffectItem> EffectStackModel::getEffectStackRow(int row,
return std::static_pointer_cast<AbstractEffectItem>(parentItem ? rootItem->child(row) : rootItem->child(row));
}
void EffectStackModel::importEffects(std::shared_ptr<EffectStackModel> sourceStack)
bool EffectStackModel::importEffects(std::shared_ptr<EffectStackModel> 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);
copyEffect(item, false);
//NO undo. this should only be used on project opening
if (copyEffect(item, state, false)) {
found = true;
}
}
modelChanged();
if (found) {
modelChanged();
}
return found;
}
bool EffectStackModel::importEffects(std::shared_ptr<EffectStackModel> sourceStack, PlaylistState::ClipState state, Fun &undo, Fun &redo)
{
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);
if (copyEffect(item, state, undo, redo)) {
found = true;
}
}
if (found) {
modelChanged();
}
return found;
}
void EffectStackModel::importEffects(std::weak_ptr<Mlt::Service> service, bool alreadyExist)
void EffectStackModel::importEffects(std::weak_ptr<Mlt::Service> service, PlaylistState::ClipState state, bool alreadyExist)
{
QWriteLocker locker(&m_lock);
m_loadingExisting = alreadyExist;
......@@ -570,6 +621,15 @@ void EffectStackModel::importEffects(std::weak_ptr<Mlt::Service> service, bool a
asset->inherit(*(ptr->filter(i)));
effect = EffectItemModel::construct(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());
......
......@@ -63,15 +63,17 @@ public:
/* @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<AbstractEffectItem> sourceItem, bool logUndo = true);
bool copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem, PlaylistState::ClipState state, bool logUndo = true);
bool copyEffect(std::shared_ptr<AbstractEffectItem> sourceItem, PlaylistState::ClipState state, Fun &undo, Fun &redo);
/* @brief Import all effects from the given effect stack
*/
void importEffects(std::shared_ptr<EffectStackModel> sourceStack);
bool importEffects(std::shared_ptr<EffectStackModel> sourceStack, PlaylistState::ClipState state);
/* @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<Mlt::Service> service, bool alreadyExist = false);
bool importEffects(std::shared_ptr<EffectStackModel> sourceStack, PlaylistState::ClipState state, Fun &undo, Fun &redo);
void importEffects(std::weak_ptr<Mlt::Service> service, PlaylistState::ClipState state, bool alreadyExist = false);
bool removeFade(bool fromStart);
/* @brief This function change the global (timeline-wise) enabled state of the effects
......
......@@ -2781,12 +2781,7 @@ void MainWindow::slotPaste()
void MainWindow::slotPasteEffects()
{
// TODO refac
/*
if (pCore->projectManager()->currentTimeline()) {
pCore->projectManager()->currentTimeline()->projectView()->pasteClipEffects();
}
*/
getMainTimeline()->controller()->pasteEffects();
}
void MainWindow::slotClipInTimeline(const QString &clipId, QList<int> ids)
......
......@@ -852,7 +852,7 @@ void ClipController::addEffect(const QString &effectId)
bool ClipController::copyEffect(std::shared_ptr<EffectStackModel> stackModel, int rowId)
{
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId));
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), !m_hasAudio ? PlaylistState::VideoOnly : !m_hasVideo ? PlaylistState::AudioOnly : PlaylistState::Disabled);
return true;
}
......
......@@ -108,8 +108,8 @@ int ClipModel::construct(const std::shared_ptr<TimelineModel> &parent, const QSt
}
auto result = binClip->giveMasterAndGetTimelineProducer(id, producer, state);
std::shared_ptr<ClipModel> clip(new ClipModel(parent, result.first, binClipId, id, state, speed));
clip->m_effectStack->importEffects(producer, result.second);
clip->setClipState_lambda(state)();
clip->m_effectStack->importEffects(producer, state, result.second);
parent->registerClip(clip);
return id;
}
......@@ -292,21 +292,21 @@ bool ClipModel::addEffect(const QString &effectId)
bool ClipModel::copyEffect(std::shared_ptr<EffectStackModel> stackModel, int rowId)
{
QWriteLocker locker(&m_lock);
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId));
m_effectStack->copyEffect(stackModel->getEffectStackRow(rowId), m_currentState);
return true;
}
bool ClipModel::importEffects(std::shared_ptr<EffectStackModel> stackModel)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(stackModel);
m_effectStack->importEffects(stackModel, m_currentState);
return true;
}
bool ClipModel::importEffects(std::weak_ptr<Mlt::Service> service)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(service);
m_effectStack->importEffects(service, m_currentState);
return true;
}
......
......@@ -56,7 +56,7 @@ bool TimelineFunctions::copyClip(std::shared_ptr<TimelineItemModel> timeline, in
}
std::shared_ptr<EffectStackModel> sourceStack = timeline->getClipEffectStackModel(clipId);
std::shared_ptr<EffectStackModel> destStack = timeline->getClipEffectStackModel(newId);
destStack->importEffects(sourceStack);
destStack->importEffects(sourceStack, state);
return res;
}
......
......@@ -21,6 +21,7 @@
#include "timelinemodel.hpp"
#include "assets/model/assetparametermodel.hpp"
#include "effects/effectsrepository.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "clipmodel.hpp"
......@@ -195,8 +196,15 @@ int TimelineModel::getClipIn(int clipId) const
READ_LOCK();
Q_ASSERT(m_allClips.count(clipId) > 0);
const auto clip = m_allClips.at(clipId);
int pos = clip->getIn();
return pos;
return clip->getIn();
}
PlaylistState::ClipState TimelineModel::getClipState(int clipId) const
{
READ_LOCK();
Q_ASSERT(m_allClips.count(clipId) > 0);
const auto clip = m_allClips.at(clipId);
return clip->clipState();
}
const QString TimelineModel::getClipBinId(int clipId) const
......@@ -1799,10 +1807,15 @@ std::shared_ptr<ClipModel> TimelineModel::getClipPtr(int clipId) const
return m_allClips.at(clipId);
}
bool TimelineModel::addClipEffect(int clipId, const QString &effectId)
bool TimelineModel::addClipEffect(int clipId, const QString &effectId, bool notify)
{
Q_ASSERT(m_allClips.count(clipId) > 0);
return m_allClips.at(clipId)->addEffect(effectId);
bool result = m_allClips.at(clipId)->addEffect(effectId);
if (!result && notify) {
QString effectName = EffectsRepository::get()->getName(effectId);
pCore->displayMessage(i18n("Cannot add effect %1 to selected clip", effectName), InformationMessage, 500);
}
return result;
}
bool TimelineModel::removeFade(int clipId, bool fromStart)
......
......@@ -236,7 +236,7 @@ public:
@param clipId Id of the clip to test
*/
Q_INVOKABLE int getClipPosition(int clipId) const;
Q_INVOKABLE bool addClipEffect(int clipId, const QString &effectId);
Q_INVOKABLE bool addClipEffect(int clipId, const QString &effectId, bool notify = true);
Q_INVOKABLE bool addTrackEffect(int trackId, const QString &effectId);
bool removeFade(int clipId, bool fromStart);
Q_INVOKABLE bool copyClipEffect(int clipId, const QString &sourceId);
......@@ -256,6 +256,10 @@ public:
*/
int getClipIn(int clipId) const;
/* @brief Returns the clip state (audio/video only)
*/
PlaylistState::ClipState getClipState(int clipId) const;
/* @brief Returns the bin id of the clip master
@param clipId Id of the clip to test
*/
......
......@@ -1108,6 +1108,6 @@ bool TrackModel::isMute() const
bool TrackModel::importEffects(std::weak_ptr<Mlt::Service> service)
{
QWriteLocker locker(&m_lock);
m_effectStack->importEffects(service);
m_effectStack->importEffects(service, trackType());
return true;
}
......@@ -39,7 +39,7 @@ Menu {
MenuItem {
visible: root.copiedClip != -1 && root.copiedClip != clipId
text: i18n('Paste Effects')
onTriggered: timeline.pasteEffects(clipId, root.copiedClip)
onTriggered: timeline.pasteEffects(clipId)
}
MenuSeparator {
visible: true
......
......@@ -93,6 +93,10 @@ Rectangle {
return (scrollView.flickableItem.contentX + tracksArea.mouseX) / timeline.scaleFactor
}
function getCopiedItemId() {
return copiedClip
}
function getMouseTrack() {
return Logic.getTrackIdFromPos(tracksArea.mouseY - ruler.height + scrollView.flickableItem.contentY)
}
......
......@@ -714,8 +714,15 @@ void TimelineController::addAsset(const QVariantMap data)
}
}
}
bool foundMatch = false;
for (int id : effectSelection) {
m_model->addClipEffect(id, effect);
if (m_model->addClipEffect(id, effect, false)) {
foundMatch = true;
}
}
if (!foundMatch) {
QString effectName = EffectsRepository::get()->getName(effect);
pCore->displayMessage(i18n("Cannot add effect %1 to selected clip", effectName), InformationMessage, 500);
}
} else {
pCore->displayMessage(i18n("Select a clip to apply an effect"), InformationMessage, 500);
......@@ -1691,14 +1698,28 @@ void TimelineController::selectCurrentTrack()
setSelection(ids);
}
void TimelineController::pasteEffects(int targetId, int sourceId)
void TimelineController::pasteEffects(int targetId)
{
QVariant returnedValue;
QMetaObject::invokeMethod(m_root, "getCopiedItemId", Q_RETURN_ARG(QVariant, returnedValue));
int sourceId = returnedValue.toInt();
if (targetId == -1 && !m_selection.selectedItems.isEmpty()) {
targetId = m_selection.selectedItems.constFirst();
}
if (!m_model->isClip(targetId) || !m_model->isClip(sourceId)) {
return;
}
std::function<bool(void)> undo = []() { return true; };
std::function<bool(void)> redo = []() { return true; };
std::shared_ptr<EffectStackModel> sourceStack = m_model->getClipEffectStackModel(sourceId);
std::shared_ptr<EffectStackModel> destStack = m_model->getClipEffectStackModel(targetId);
destStack->importEffects(sourceStack);
bool result = destStack->importEffects(sourceStack, m_model->m_allClips[targetId]->clipState(), undo, redo);
if (result) {
pCore->pushUndo(undo, redo, i18n("Paste effects"));
} else {
pCore->displayMessage(i18n("Cannot paste effect on selected clip"), InformationMessage, 500);
undo();
}
}
double TimelineController::fps() const
......
......@@ -298,7 +298,7 @@ public:
/* @brief Seeks to selected clip start / end
*/
Q_INVOKABLE void pasteEffects(int targetId, int sourceId);
Q_INVOKABLE void pasteEffects(int targetId = -1);
Q_INVOKABLE double fps() const;
void switchTrackLock(bool applyToAll = false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment