diff --git a/src/assets/assetpanel.cpp b/src/assets/assetpanel.cpp
index 87fbab8cc..c4bb237f1 100644
--- a/src/assets/assetpanel.cpp
+++ b/src/assets/assetpanel.cpp
@@ -1,354 +1,361 @@
/***************************************************************************
* 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.cpp"
#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
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))
{
QToolBar *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);
QVBoxLayout *lay = new QVBoxLayout(m_container);
lay->setContentsMargins(0, 0, 0, 0);
lay->addWidget(m_transitionWidget);
lay->addWidget(m_effectStackWidget);
QScrollArea *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, 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", transitionName));
m_transitionWidget->setVisible(true);
m_timelineButton->setVisible(true);
m_enableStackButton->setVisible(false);
m_transitionWidget->setModel(transitionModel, QSize(), true);
}
void AssetPanel::showEffectStack(const QString &itemName, 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();
}
void AssetPanel::parameterChanged(QString name, int value)
{
Q_UNUSED(name)
emit changeSpeed(value);
}
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/assetpanel.hpp b/src/assets/assetpanel.hpp
index aa6d471c3..b44f65749 100644
--- a/src/assets/assetpanel.hpp
+++ b/src/assets/assetpanel.hpp
@@ -1,104 +1,105 @@
/***************************************************************************
* 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 ASSETPANEL_H
#define ASSETPANEL_H
#include
#include
#include
#include "definitions.h"
class KSqueezedTextLabel;
class KDualAction;
class QToolButton;
/** @brief This class is the widget that provides interaction with the asset currently selected.
That is, it either displays an effectStack or the parameters of a transition
*/
class AssetParameterModel;
class AssetParameterView;
class EffectStackModel;
class EffectStackView;
class TransitionStackView;
class QLabel;
class AssetPanel : public QWidget
{
Q_OBJECT
public:
AssetPanel(QWidget *parent);
/* @brief Shows the parameters of the given transition model */
void showTransition(int tid, std::shared_ptr transition_model);
/* @brief Shows the parameters of the given effect stack model */
void showEffectStack(const QString &itemName, std::shared_ptr effectsModel, QSize frameSize, bool showKeyframes);
/* @brief Clear the panel so that it doesn't display anything */
void clear();
/* @brief This method should be called when the style changes */
void updatePalette();
/* @brief Returns the object type / id of effectstack owner */
ObjectId effectStackOwner();
/* @brief Add an effect to the current stack owner */
bool addEffect(const QString &effectId);
public slots:
/** @brief Clear panel if displaying itemId */
void clearAssetPanel(int itemId);
void parameterChanged(QString name, int value);
+ void deleteCurrentEffect();
protected:
/** @brief Return the stylesheet used to display the panel (based on current palette). */
static const QString getStyleSheet();
QVBoxLayout *m_lay;
KSqueezedTextLabel *m_assetTitle;
QWidget *m_container;
TransitionStackView *m_transitionWidget;
EffectStackView *m_effectStackWidget;
private:
QToolButton *m_switchBuiltStack;
KDualAction *m_splitButton;
KDualAction *m_enableStackButton;
KDualAction *m_timelineButton;
private slots:
void processSplitEffect(bool enable);
/** Displays the owner clip keyframes in timeline */
void showKeyframes(bool enable);
/** Enable / disable effect stack */
void enableStack(bool enable);
signals:
void doSplitEffect(bool);
void doSplitBinEffect(bool);
void seekToPos(int);
void changeSpeed(int);
void reloadEffect(const QString &path);
};
#endif
diff --git a/src/core.cpp b/src/core.cpp
index 2f88345f8..5ede635f9 100644
--- a/src/core.cpp
+++ b/src/core.cpp
@@ -1,657 +1,654 @@
/*
Copyright (C) 2014 Till Theato
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 3 of the License, or
(at your option) any later version.
*/
#include "core.h"
#include "bin/bin.h"
#include "bin/projectitemmodel.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "jobs/jobmanager.h"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
#include "library/librarywidget.h"
#include "mainwindow.h"
#include "mltconnection.h"
#include "mltcontroller/clipcontroller.h"
#include "monitor/monitormanager.h"
#include "profiles/profilemodel.hpp"
#include "profiles/profilerepository.hpp"
#include "project/projectmanager.h"
#include "timeline2/model/timelineitemmodel.hpp"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinewidget.h"
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_MAC
#include
#endif
std::unique_ptr Core::m_self;
Core::Core()
: m_mainWindow(nullptr)
, m_projectManager(nullptr)
, m_monitorManager(nullptr)
, m_binWidget(nullptr)
, m_library(nullptr)
, m_thumbProfile(nullptr)
{
}
void Core::prepareShutdown()
{
m_guiConstructed = false;
}
Core::~Core()
{
if (m_monitorManager) {
delete m_monitorManager;
}
// delete m_binWidget;
if (m_projectManager) {
delete m_projectManager;
}
}
void Core::build(const QString &MltPath)
{
if (m_self) {
qDebug() << "DEBUG: Warning : trying to create a second Core";
return;
}
m_self.reset(new Core());
m_self->initLocale();
qRegisterMetaType("audioShortVector");
qRegisterMetaType>("QVector");
qRegisterMetaType("MessageType");
qRegisterMetaType("stringMap");
qRegisterMetaType("audioByteArray");
qRegisterMetaType>("QList");
qRegisterMetaType>("std::shared_ptr");
qRegisterMetaType>();
qRegisterMetaType("QDomElement");
qRegisterMetaType("requestClipInfo");
// Open connection with Mlt
MltConnection::construct(MltPath);
// load the profile from disk
ProfileRepository::get()->refresh();
// load default profile
m_self->m_profile = KdenliveSettings::default_profile();
if (m_self->m_profile.isEmpty()) {
m_self->m_profile = ProjectManager::getDefaultProjectFormat();
KdenliveSettings::setDefault_profile(m_self->m_profile);
}
// Init producer shown for unavailable media
// TODO make it a more proper image, it currently causes a crash on exit
ClipController::mediaUnavailable = std::make_shared(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue");
ClipController::mediaUnavailable->set("length", 99999999);
m_self->m_projectItemModel = ProjectItemModel::construct();
// Job manger must be created before bin to correctly connect
m_self->m_jobManager.reset(new JobManager(m_self.get()));
}
void Core::initGUI(const QUrl &Url)
{
m_guiConstructed = true;
m_profile = KdenliveSettings::default_profile();
m_currentProfile = m_profile;
profileChanged();
m_mainWindow = new MainWindow();
// load default profile and ask user to select one if not found.
if (m_profile.isEmpty()) {
m_profile = ProjectManager::getDefaultProjectFormat();
profileChanged();
KdenliveSettings::setDefault_profile(m_profile);
}
if (!ProfileRepository::get()->profileExists(m_profile)) {
KMessageBox::sorry(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value."));
// TODO this simple widget should be improved and probably use profileWidget
// we get the list of profiles
QVector> all_profiles = ProfileRepository::get()->getAllProfiles();
QStringList all_descriptions;
for (const auto &profile : all_profiles) {
all_descriptions << profile.first;
}
// ask the user
bool ok;
QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok);
if (ok) {
ok = false;
for (const auto &profile : all_profiles) {
if (profile.first == item) {
m_profile = profile.second;
ok = true;
}
}
}
if (!ok) {
KMessageBox::error(
m_mainWindow,
i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel"));
m_profile = QStringLiteral("dv_pal");
}
KdenliveSettings::setDefault_profile(m_profile);
profileChanged();
}
m_projectManager = new ProjectManager(this);
m_binWidget = new Bin(m_projectItemModel, m_mainWindow);
m_library = new LibraryWidget(m_projectManager, m_mainWindow);
connect(m_library, SIGNAL(addProjectClips(QList)), m_binWidget, SLOT(droppedUrls(QList)));
connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath);
m_monitorManager = new MonitorManager(this);
// Producer queue, creating MLT::Producers on request
/*
m_producerQueue = new ProducerQueue(m_binController);
connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady);
connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady);
connect(m_producerQueue, &ProducerQueue::requestProxy,
[this](const QString &id){ m_binWidget->startJob(id, AbstractClipJob::PROXYJOB);});
connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection);
connect(m_producerQueue, SIGNAL(addClip(QString, QMap)), m_binWidget, SLOT(slotAddUrl(QString, QMap)));
connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int)));
connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection);
// TODO
connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/
m_mainWindow->init();
projectManager()->init(Url, QString());
if (qApp->isSessionRestored()) {
// NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow
m_mainWindow->restore(1, false);
}
QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection);
m_mainWindow->show();
}
std::unique_ptr &Core::self()
{
if (!m_self) {
qDebug() << "Error : Core has not been created";
}
return m_self;
}
MainWindow *Core::window()
{
return m_mainWindow;
}
ProjectManager *Core::projectManager()
{
return m_projectManager;
}
MonitorManager *Core::monitorManager()
{
return m_monitorManager;
}
Monitor *Core::getMonitor(int id)
{
if (id == Kdenlive::ClipMonitor) {
return m_monitorManager->clipMonitor();
}
return m_monitorManager->projectMonitor();
}
Bin *Core::bin()
{
return m_binWidget;
}
void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone)
{
m_binWidget->selectClipById(clipId, frame, zone);
}
std::shared_ptr Core::jobManager()
{
return m_jobManager;
}
LibraryWidget *Core::library()
{
return m_library;
}
void Core::initLocale()
{
QLocale systemLocale = QLocale();
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, nullptr);
#else
setlocale(LC_NUMERIC_MASK, nullptr);
#endif
// localeconv()->decimal_point does not give reliable results on Windows
#ifndef Q_OS_WIN
char *separator = localeconv()->decimal_point;
if (QString::fromUtf8(separator) != QChar(systemLocale.decimalPoint())) {
// qCDebug(KDENLIVE_LOG)<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
// HACK: There is a locale conflict, so set locale to C
// Make sure to override exported values or it won't work
qputenv("LANG", "C");
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, "C");
#else
setlocale(LC_NUMERIC_MASK, "C");
#endif
systemLocale = QLocale::c();
}
#endif
systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
QLocale::setDefault(systemLocale);
}
std::unique_ptr &Core::getMltRepository()
{
return MltConnection::self()->getMltRepository();
}
std::unique_ptr &Core::getCurrentProfile() const
{
return ProfileRepository::get()->getProfile(m_currentProfile);
}
const QString &Core::getCurrentProfilePath() const
{
return m_currentProfile;
}
bool Core::setCurrentProfile(const QString &profilePath)
{
if (m_currentProfile == profilePath) {
// no change required
return true;
}
if (ProfileRepository::get()->profileExists(profilePath)) {
m_currentProfile = profilePath;
m_thumbProfile.reset();
// inform render widget
m_mainWindow->updateRenderWidgetProfile();
if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) {
m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(&getCurrentProfile()->profile());
}
return true;
}
return false;
}
double Core::getCurrentSar() const
{
return getCurrentProfile()->sar();
}
double Core::getCurrentDar() const
{
return getCurrentProfile()->dar();
}
double Core::getCurrentFps() const
{
return getCurrentProfile()->fps();
}
QSize Core::getCurrentFrameDisplaySize() const
{
return QSize((int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height());
}
QSize Core::getCurrentFrameSize() const
{
return QSize(getCurrentProfile()->width(), getCurrentProfile()->height());
}
void Core::requestMonitorRefresh()
{
if (!m_guiConstructed) return;
m_monitorManager->refreshProjectMonitor();
}
void Core::refreshProjectRange(QSize range)
{
if (!m_guiConstructed) return;
m_monitorManager->refreshProjectRange(range);
}
int Core::getItemPosition(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second);
}
break;
case ObjectType::BinClip:
return 0;
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
int Core::getItemIn(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second);
}
break;
case ObjectType::TimelineComposition:
return 0;
break;
case ObjectType::BinClip:
return 0;
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
PlaylistState::ClipState Core::getItemState(const ObjectId &id)
{
if (!m_guiConstructed) return PlaylistState::Disabled;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipState(id.second);
}
break;
case ObjectType::TimelineComposition:
return PlaylistState::Disabled;
break;
case ObjectType::BinClip:
return m_binWidget->getClipState(id.second);
break;
default:
qDebug() << "ERROR: unhandled object type";
break;
}
return PlaylistState::Disabled;
}
int Core::getItemDuration(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second);
}
break;
case ObjectType::BinClip:
return m_binWidget->getClipDuration(id.second);
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
int Core::getItemTrack(const ObjectId &id)
{
if (!m_guiConstructed) return 0;
switch (id.first) {
case ObjectType::TimelineClip:
case ObjectType::TimelineComposition:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second);
break;
default:
qDebug() << "ERROR: unhandled object type";
}
return 0;
}
void Core::refreshProjectItem(const ObjectId &id)
{
if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return;
switch (id.first) {
case ObjectType::TimelineClip:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) {
m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
}
break;
case ObjectType::TimelineComposition:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) {
m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second);
}
break;
case ObjectType::TimelineTrack:
if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) {
requestMonitorRefresh();
}
break;
case ObjectType::BinClip:
m_monitorManager->refreshClipMonitor();
break;
default:
qDebug() << "ERROR: unhandled object type";
}
}
bool Core::hasTimelinePreview() const
{
if (!m_guiConstructed) {
return false;
}
return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0;
}
KdenliveDoc *Core::currentDoc()
{
return m_projectManager->current();
}
int Core::projectDuration() const
{
if (!m_guiConstructed) {
return 0;
}
return m_mainWindow->getCurrentTimeline()->controller()->duration();
}
void Core::profileChanged()
{
GenTime::setFps(getCurrentFps());
}
void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text)
{
undoStack()->push(new FunctionalUndoCommand(undo, redo, text));
}
void Core::pushUndo(QUndoCommand *command)
{
undoStack()->push(command);
}
void Core::displayMessage(const QString &message, MessageType type, int timeout)
{
if (m_mainWindow) {
m_mainWindow->displayMessage(message, type, timeout);
} else {
qDebug() << message;
}
}
void Core::displayBinMessage(const QString &text, int type, const QList &actions)
{
m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, actions);
}
void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo)
{
m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, logInfo);
}
void Core::clearAssetPanel(int itemId)
{
if (m_guiConstructed) m_mainWindow->clearAssetPanel(itemId);
}
std::shared_ptr Core::getItemEffectStack(int itemType, int itemId)
{
if (!m_guiConstructed) return nullptr;
switch (itemType) {
case (int)ObjectType::TimelineClip:
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId);
case (int)ObjectType::TimelineTrack:
// TODO
return nullptr;
break;
case (int)ObjectType::BinClip:
return m_binWidget->getClipEffectStack(itemId);
default:
return nullptr;
}
}
std::shared_ptr Core::undoStack()
{
return projectManager()->undoStack();
}
QMap Core::getVideoTrackNames()
{
if (!m_guiConstructed) return QMap();
return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(true);
}
QPair Core::getCompositionATrack(int cid) const
{
if (!m_guiConstructed) return QPair();
return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid);
}
bool Core::compositionAutoTrack(int cid) const
{
return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid);
}
void Core::setCompositionATrack(int cid, int aTrack)
{
if (!m_guiConstructed) return;
m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack);
}
std::shared_ptr Core::projectItemModel()
{
return m_projectItemModel;
}
void Core::invalidateItem(ObjectId itemId)
{
if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return;
switch (itemId.first) {
case ObjectType::TimelineClip:
- m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
case ObjectType::TimelineComposition:
- if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(itemId.second)) {
- m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
- }
+ m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second);
break;
case ObjectType::TimelineTrack:
// TODO: invalidate all clips in track
break;
default:
// bin clip should automatically be reloaded, compositions should not have effects
break;
}
}
double Core::getClipSpeed(int id) const
{
return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id);
}
void Core::updateItemKeyframes(ObjectId id)
{
if (id.first == ObjectType::TimelineClip && m_mainWindow) {
m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole});
}
}
void Core::updateItemModel(ObjectId id, const QString &service)
{
if (m_mainWindow && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade")) && id.first == ObjectType::TimelineClip) {
bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black");
m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole});
}
}
void Core::showClipKeyframes(ObjectId id, bool enable)
{
if (id.first == ObjectType::TimelineClip) {
m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable);
} else if (id.first == ObjectType::TimelineComposition) {
m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable);
}
}
Mlt::Profile *Core::thumbProfile()
{
if (!m_thumbProfile) {
m_thumbProfile = std::unique_ptr(new Mlt::Profile(m_currentProfile.toStdString().c_str()));
m_thumbProfile->set_height(200);
int width = 200 * m_thumbProfile->dar();
width += width % 8;
m_thumbProfile->set_width(width);
}
return m_thumbProfile.get();
}
void Core::clearSelection()
{
if (m_mainWindow) {
m_mainWindow->getCurrentTimeline()->controller()->clearSelection();
}
}
void Core::triggerAction(const QString &name)
{
QAction *action = m_mainWindow->actionCollection()->action(name);
if (action) {
action->trigger();
}
}
diff --git a/src/effects/effectstack/model/effectstackmodel.cpp b/src/effects/effectstack/model/effectstackmodel.cpp
index 9da34a621..4c64ce209 100644
--- a/src/effects/effectstack/model/effectstackmodel.cpp
+++ b/src/effects/effectstack/model/effectstackmodel.cpp
@@ -1,1010 +1,1025 @@
/***************************************************************************
* 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
#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::removeCurrentEffect()
+{
+ int ix = 0;
+ if (auto ptr = m_services.front().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(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));
}
}
bool EffectStackModel::copyEffect(std::shared_ptr sourceItem, PlaylistState::ClipState state, bool logUndo)
{
std::function undo = []() { return true; };
std::function redo = []() { return true; };
bool result = copyEffect(sourceItem, state, undo, redo);
if (result && logUndo) {
std::shared_ptr sourceEffect = std::static_pointer_cast(sourceItem);
QString effectName = EffectsRepository::get()->getName(sourceEffect->getAssetId());
PUSH_UNDO(undo, redo, i18n("copy effect %1", effectName));
}
return result;
}
QDomElement EffectStackModel::toXml(QDomDocument &document)
{
QDomElement container = document.createElement(QStringLiteral("effects"));
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());
sub.setAttribute(QStringLiteral("in"), sourceEffect->filter().get_int("in"));
sub.setAttribute(QStringLiteral("out"), sourceEffect->filter().get_int("out"));
QVector > params = sourceEffect->getAllParameters();
QLocale locale;
for (auto param : 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;
}
void EffectStackModel::fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo)
{
QDomNodeList nodeList = effectsXml.elementsByTagName(QStringLiteral("effect"));
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement node = nodeList.item(i).toElement();
const QString effectId = node.attribute(QStringLiteral("id"));
auto effect = EffectItemModel::construct(effectId, shared_from_this());
int in = node.attribute(QStringLiteral("in")).toInt();
int out = node.attribute(QStringLiteral("out")).toInt();
if (out > 0) {
effect->filter().set("in", in);
effect->filter().set("out", out);
}
QVector> parameters;
QDomNodeList params = node.elementsByTagName(QStringLiteral("property"));
for (int j = 0; j < params.count(); j++) {
QDomElement pnode = params.item(j).toElement();
parameters.append(QPair(pnode.attribute(QStringLiteral("name")), 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")) {
fadeIns.insert(effect->getId());
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
fadeOuts.insert(effect->getId());
}
local_redo();
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
if (true) {
Fun update = [this]() {
emit dataChanged(QModelIndex(), QModelIndex(), QVector());
return true;
};
update();
PUSH_LAMBDA(update, redo);
PUSH_LAMBDA(update, undo);
}
}
bool EffectStackModel::copyEffect(std::shared_ptr sourceItem, PlaylistState::ClipState state, Fun &undo, Fun &redo)
{
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();
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 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")) {
fadeIns.insert(effect->getId());
} else if (effectId == QLatin1String("fadeout") || effectId == QLatin1String("fade_to_black")) {
fadeOuts.insert(effect->getId());
}
bool res = local_redo();
if (res) {
Fun update = [this]() {
emit dataChanged(QModelIndex(), QModelIndex(), QVector());
return true;
};
update();
PUSH_LAMBDA(update, local_redo);
PUSH_LAMBDA(update, local_undo);
UPDATE_UNDO_REDO(local_redo, local_undo, undo, redo);
}
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) {
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);
}
}
}
return res;
}
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);
} else {
qDebug()<<"// NULL Keyframes---------";
}
}
}
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);
}
emit enabledStateChanged();
}
std::shared_ptr EffectStackModel::getEffectStackRow(int row, std::shared_ptr parentItem)
{
return std::static_pointer_cast(parentItem ? rootItem->child(row) : rootItem->child(row));
}
bool EffectStackModel::importEffects(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, false)) {
found = true;
}
}
if (found) {
modelChanged();
}
return found;
}
bool EffectStackModel::importEffects(std::shared_ptr 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 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++) {
if (ptr->filter(i)->get("kdenlive_id") == nullptr) {
// don't consider internal MLT stuff
continue;
}
const QString effectId = qstrdup(ptr->filter(i)->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(ptr->filter(i), shared_from_this());
} else {
// duplicate effect
Mlt::Filter *asset = EffectsRepository::get()->getEffect(effectId);
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());
effect->prepareKeyframes();
if (redo()) {
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());
}
}
}
}
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()) {
// 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") != NULL) {
kdenliveFilterCount++;
}
//qDebug() << "FILTER: "<filter(i)->get("mlt_service");
}
if (kdenliveFilterCount != (int)allFilters.size()) {
qDebug() << "ERROR: Wrong filter count: "<filter(ct)->get("kdenlive_id") == NULL && ct < ptr->filter_count()) {
ct++;
}
auto mltFilter = ptr->filter(ct)->get_filter();
ct++;
auto currentFilter = allFilters[i]->filter().get_filter();
if (mltFilter != currentFilter) {
ct--;
qDebug() << "ERROR: filter " << i << "differ: "<filter().get("mlt_service")<<" = "<filter(ct)->get("mlt_service");
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) 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;
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 updateRedo = [this, toDelete, outEffects]() {
for (int id : toDelete) {
if (outEffects) {
fadeOuts.erase(id);
} else {
fadeIns.erase(id);
}
}
emit dataChanged(QModelIndex(), QModelIndex(), QVector());
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(EffectsRepository::get()->getName(std::static_pointer_cast(rootItem->child(i))->getAssetId()));
}
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;
auto ptr = m_services.front().lock();
if (ptr) {
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->addKeyframe(frame, normalisedVal);
}
bool EffectStackModel::removeKeyFrame(int frame)
{
if (rootItem->childCount() == 0) return false;
int ix = 0;
auto ptr = m_services.front().lock();
if (ptr) {
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, double normalisedVal)
{
if (rootItem->childCount() == 0) return false;
int ix = 0;
auto ptr = m_services.front().lock();
if (ptr) {
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->updateKeyframe(GenTime(oldFrame, pCore->getCurrentFps()), GenTime(newFrame, pCore->getCurrentFps()), normalisedVal);
}
diff --git a/src/effects/effectstack/model/effectstackmodel.hpp b/src/effects/effectstack/model/effectstackmodel.hpp
index d44284f08..d062b80aa 100644
--- a/src/effects/effectstack/model/effectstackmodel.hpp
+++ b/src/effects/effectstack/model/effectstackmodel.hpp
@@ -1,180 +1,182 @@
/***************************************************************************
* 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 */
bool 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
*/
bool copyEffect(std::shared_ptr sourceItem, PlaylistState::ClipState state, bool logUndo = true);
bool copyEffect(std::shared_ptr sourceItem, PlaylistState::ClipState state, Fun &undo, Fun &redo);
/* @brief Import all effects from the given effect stack
*/
bool importEffects(std::shared_ptr 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
*/
bool importEffects(std::shared_ptr sourceStack, PlaylistState::ClipState state, Fun &undo, Fun &redo);
void importEffects(std::weak_ptr service, PlaylistState::ClipState state, 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);
/* @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();
/** Add a keyframe in all model parameters */
bool addEffectKeyFrame(int frame, double normalisedVal);
/** Remove a keyframe in all model parameters */
bool removeKeyFrame(int frame);
/** Update a keyframe in all model parameters (with value updated only in first parameter)*/
bool updateKeyFrame(int oldFrame, int newFrame, double normalisedVal);
/** 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);
/* @brief Returns a comma separated list of effect names */
const QString effectNames() const;
bool isStackEnabled() const;
/* @brief Returns an XML representation of the effect stack with all parameters */
QDomElement toXml(QDomDocument &document);
/* @brief Load an effect stack from an XML representation */
void fromXml(const QDomElement &effectsXml, Fun &undo, Fun &redo);
+ /* @brief Delete active effect from stack */
+ void removeCurrentEffect();
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();
void enabledStateChanged();
};
#endif
diff --git a/src/effects/effectstack/view/effectstackview.cpp b/src/effects/effectstack/view/effectstackview.cpp
index 67ccae404..f9b800fa7 100644
--- a/src/effects/effectstack/view/effectstackview.cpp
+++ b/src/effects/effectstack/view/effectstackview.cpp
@@ -1,378 +1,380 @@
/***************************************************************************
* Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#include "effectstackview.hpp"
#include "assets/assetlist/view/qmltypes/asseticonprovider.hpp"
#include "assets/assetpanel.hpp"
#include "assets/view/assetparameterview.hpp"
#include "builtstack.hpp"
#include "collapsibleeffectview.hpp"
#include "core.h"
#include "effects/effectstack/model/effectitemmodel.hpp"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "kdenlivesettings.h"
#include "monitor/monitor.h"
#include
#include
#include
#include
#include
#include
#include
WidgetDelegate::WidgetDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}
QSize WidgetDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QSize s = QStyledItemDelegate::sizeHint(option, index);
if (m_height.contains(index)) {
s.setHeight(m_height.value(index));
}
return s;
}
void WidgetDelegate::setHeight(const QModelIndex &index, int height)
{
m_height[index] = height;
emit sizeHintChanged(index);
}
void WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt(option);
initStyleOption(&opt, index);
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
}
EffectStackView::EffectStackView(AssetPanel *parent)
: QWidget(parent)
, m_model(nullptr)
, m_thumbnailer(new AssetIconProvider(true))
{
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_lay = new QVBoxLayout(this);
m_lay->setContentsMargins(0, 0, 0, 0);
m_lay->setSpacing(0);
setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont));
setAcceptDrops(true);
/*m_builtStack = new BuiltStack(parent);
m_builtStack->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_lay->addWidget(m_builtStack);
m_builtStack->setVisible(KdenliveSettings::showbuiltstack());*/
m_effectsTree = new QTreeView(this);
m_effectsTree->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
m_effectsTree->setHeaderHidden(true);
m_effectsTree->setRootIsDecorated(false);
QString style = QStringLiteral("QTreeView {border: none;}");
// m_effectsTree->viewport()->setAutoFillBackground(false);
m_effectsTree->setStyleSheet(style);
m_effectsTree->setVisible(!KdenliveSettings::showbuiltstack());
m_lay->addWidget(m_effectsTree);
m_lay->setStretch(1, 10);
}
EffectStackView::~EffectStackView()
{
delete m_thumbnailer;
}
void EffectStackView::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasFormat(QStringLiteral("kdenlive/effect"))) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
} else {
event->setDropAction(Qt::CopyAction);
}
event->setAccepted(true);
} else {
event->setAccepted(false);
}
}
void EffectStackView::dropEvent(QDropEvent *event)
{
event->accept();
QString effectId = event->mimeData()->data(QStringLiteral("kdenlive/effect"));
int row = m_model->rowCount();
for (int i = 0; i < m_model->rowCount(); i++) {
auto item = m_model->getEffectStackRow(i);
if (item->childCount() > 0) {
// TODO: group
continue;
}
std::shared_ptr eff = std::static_pointer_cast(item);
QModelIndex ix = m_model->getIndexFromItem(eff);
QWidget *w = m_effectsTree->indexWidget(ix);
if (w && w->geometry().contains(event->pos())) {
qDebug() << "// DROPPED ON EFF: " << eff->getAssetId();
row = i;
break;
}
}
if (event->source() == this) {
QString sourceData = event->mimeData()->data(QStringLiteral("kdenlive/effectsource"));
int oldRow = sourceData.section(QLatin1Char('-'), 2, 2).toInt();
qDebug() << "// MOVING EFFECT FROM : " << oldRow << " TO " << row;
if (row == oldRow || (row == m_model->rowCount() && oldRow == row - 1)) {
return;
}
m_model->moveEffect(row, m_model->getEffectStackRow(oldRow));
} else {
bool added = false;
if (row < m_model->rowCount()) {
if (m_model->appendEffect(effectId)) {
added = true;
m_model->moveEffect(row, m_model->getEffectStackRow(m_model->rowCount() - 1));
}
} else {
if (m_model->appendEffect(effectId)) {
added = true;
std::shared_ptr item = m_model->getEffectStackRow(m_model->rowCount() - 1);
if (item) {
slotActivateEffect(std::static_pointer_cast(item));
}
}
}
if (!added) {
pCore->displayMessage(i18n("Cannot add effect to clip"), InformationMessage);
}
}
}
void EffectStackView::setModel(std::shared_ptr model, const QSize frameSize)
{
qDebug() << "MUTEX LOCK!!!!!!!!!!!! setmodel";
m_mutex.lock();
unsetModel(false);
m_model = model;
m_sourceFrameSize = frameSize;
m_effectsTree->setModel(m_model.get());
m_effectsTree->setItemDelegateForColumn(0, new WidgetDelegate(this));
m_effectsTree->setColumnHidden(1, true);
m_effectsTree->setAcceptDrops(true);
m_effectsTree->setDragDropMode(QAbstractItemView::DragDrop);
m_effectsTree->setDragEnabled(true);
m_effectsTree->setUniformRowHeights(false);
m_mutex.unlock();
qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! setmodel";
loadEffects();
connect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh);
connect(m_model.get(), &EffectStackModel::enabledStateChanged, this, &EffectStackView::updateEnabledState);
+ connect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect);
// m_builtStack->setModel(model, stackOwner());
}
void EffectStackView::loadEffects()
{
qDebug() << "MUTEX LOCK!!!!!!!!!!!! loadEffects: ";
QMutexLocker lock(&m_mutex);
int max = m_model->rowCount();
if (max == 0) {
// blank stack
ObjectId item = m_model->getOwnerId();
pCore->getMonitor(item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)->slotShowEffectScene(MonitorSceneDefault);
return;
}
int active = qBound(0, m_model->getActiveEffect(), max - 1);
for (int i = 0; i < max; i++) {
std::shared_ptr item = m_model->getEffectStackRow(i);
QSize size;
if (item->childCount() > 0) {
// group, create sub stack
continue;
}
std::shared_ptr effectModel = std::static_pointer_cast(item);
CollapsibleEffectView *view = nullptr;
// We need to rebuild the effect view
QImage effectIcon = m_thumbnailer->requestImage(effectModel->getAssetId(), &size, QSize(QStyle::PM_SmallIconSize, QStyle::PM_SmallIconSize));
view = new CollapsibleEffectView(effectModel, m_sourceFrameSize, effectIcon, this);
connect(view, &CollapsibleEffectView::deleteEffect, m_model.get(), &EffectStackModel::removeEffect);
connect(view, &CollapsibleEffectView::moveEffect, m_model.get(), &EffectStackModel::moveEffect);
connect(view, &CollapsibleEffectView::reloadEffect, this, &EffectStackView::reloadEffect);
connect(view, &CollapsibleEffectView::switchHeight, this, &EffectStackView::slotAdjustDelegate, Qt::DirectConnection);
connect(view, &CollapsibleEffectView::startDrag, this, &EffectStackView::slotStartDrag);
connect(view, &CollapsibleEffectView::createGroup, m_model.get(), &EffectStackModel::slotCreateGroup);
connect(view, &CollapsibleEffectView::activateEffect, this, &EffectStackView::slotActivateEffect);
connect(view, &CollapsibleEffectView::seekToPos, [this](int pos) {
// at this point, the effects returns a pos relative to the clip. We need to convert it to a global time
int clipIn = pCore->getItemPosition(m_model->getOwnerId());
emit seekToPos(pos + clipIn);
});
connect(this, &EffectStackView::doActivateEffect, view, &CollapsibleEffectView::slotActivateEffect);
QModelIndex ix = m_model->getIndexFromItem(effectModel);
m_effectsTree->setIndexWidget(ix, view);
WidgetDelegate *del = static_cast(m_effectsTree->itemDelegate(ix));
del->setHeight(ix, view->height());
view->buttonUp->setEnabled(i > 0);
view->buttonDown->setEnabled(i < max - 1);
if (i == active) {
m_model->setActiveEffect(i);
emit doActivateEffect(ix);
}
}
updateTreeHeight();
qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! loadEffects";
}
void EffectStackView::updateTreeHeight()
{
// For some reason, the treeview height does not update correctly, so enforce it
int totalHeight = 0;
for (int j = 0; j < m_model->rowCount(); j++) {
std::shared_ptr item2 = m_model->getEffectStackRow(j);
std::shared_ptr eff = std::static_pointer_cast(item2);
QModelIndex idx = m_model->getIndexFromItem(eff);
auto w = m_effectsTree->indexWidget(idx);
totalHeight += w->height();
}
setMinimumHeight(totalHeight);
}
void EffectStackView::slotActivateEffect(std::shared_ptr effectModel)
{
qDebug() << "MUTEX LOCK!!!!!!!!!!!! slotactivateeffect: "<row();
QMutexLocker lock(&m_mutex);
m_model->setActiveEffect(effectModel->row());
QModelIndex activeIx = m_model->getIndexFromItem(effectModel);
emit doActivateEffect(activeIx);
qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! slotactivateeffect";
}
void EffectStackView::slotStartDrag(QPixmap pix, std::shared_ptr effectModel)
{
auto *drag = new QDrag(this);
drag->setPixmap(pix);
auto *mime = new QMimeData;
mime->setData(QStringLiteral("kdenlive/effect"), effectModel->getAssetId().toUtf8());
// TODO this will break if source effect is not on the stack of a timeline clip
QByteArray effectSource;
effectSource += QString::number((int)effectModel->getOwnerId().first).toUtf8();
effectSource += '-';
effectSource += QString::number((int)effectModel->getOwnerId().second).toUtf8();
effectSource += '-';
effectSource += QString::number(effectModel->row()).toUtf8();
mime->setData(QStringLiteral("kdenlive/effectsource"), effectSource);
// mime->setData(QStringLiteral("kdenlive/effectrow"), QString::number(effectModel->row()).toUtf8());
// Assign ownership of the QMimeData object to the QDrag object.
drag->setMimeData(mime);
// Start the drag and drop operation
drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction);
}
void EffectStackView::slotAdjustDelegate(std::shared_ptr effectModel, int height)
{
qDebug() << "MUTEX LOCK!!!!!!!!!!!! adjustdelegate: " << height;
QMutexLocker lock(&m_mutex);
QModelIndex ix = m_model->getIndexFromItem(effectModel);
WidgetDelegate *del = static_cast(m_effectsTree->itemDelegate(ix));
del->setHeight(ix, height);
updateTreeHeight();
qDebug() << "MUTEX UNLOCK!!!!!!!!!!!! adjustdelegate";
}
void EffectStackView::refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles)
{
Q_UNUSED(roles)
if (!topLeft.isValid() || !bottomRight.isValid()) {
loadEffects();
return;
}
for (int i = topLeft.row(); i <= bottomRight.row(); ++i) {
for (int j = topLeft.column(); j <= bottomRight.column(); ++j) {
CollapsibleEffectView *w = static_cast(m_effectsTree->indexWidget(m_model->index(i, j, topLeft.parent())));
if (w) {
w->refresh();
}
}
}
}
void EffectStackView::unsetModel(bool reset)
{
// Release ownership of smart pointer
Kdenlive::MonitorId id = Kdenlive::NoMonitor;
if (m_model) {
ObjectId item = m_model->getOwnerId();
id = item.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor;
disconnect(m_model.get(), &EffectStackModel::dataChanged, this, &EffectStackView::refresh);
+ disconnect(this, &EffectStackView::removeCurrentEffect, m_model.get(), &EffectStackModel::removeCurrentEffect);
}
if (reset) {
QMutexLocker lock(&m_mutex);
m_model.reset();
m_effectsTree->setModel(nullptr);
}
if (id != Kdenlive::NoMonitor) {
pCore->getMonitor(id)->slotShowEffectScene(MonitorSceneDefault);
}
}
ObjectId EffectStackView::stackOwner() const
{
if (m_model) {
return m_model->getOwnerId();
}
return ObjectId(ObjectType::NoItem, -1);
}
bool EffectStackView::addEffect(const QString &effectId)
{
if (m_model) {
return m_model->appendEffect(effectId);
}
return false;
}
bool EffectStackView::isEmpty() const
{
return m_model == nullptr ? true : m_model->rowCount() == 0;
}
void EffectStackView::enableStack(bool enable)
{
if (m_model) {
m_model->setEffectStackEnabled(enable);
}
}
bool EffectStackView::isStackEnabled() const
{
if (m_model) {
return m_model->isStackEnabled();
}
return false;
}
/*
void EffectStackView::switchBuiltStack(bool show)
{
m_builtStack->setVisible(show);
m_effectsTree->setVisible(!show);
KdenliveSettings::setShowbuiltstack(show);
}
*/
diff --git a/src/effects/effectstack/view/effectstackview.hpp b/src/effects/effectstack/view/effectstackview.hpp
index 4082c1f66..4d21115ac 100644
--- a/src/effects/effectstack/view/effectstackview.hpp
+++ b/src/effects/effectstack/view/effectstackview.hpp
@@ -1,110 +1,111 @@
/***************************************************************************
* Copyright (C) 2017 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
* This file is part of Kdenlive. See www.kdenlive.org. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) version 3 or any later version accepted by the *
* membership of KDE e.V. (or its successor approved by the membership *
* of KDE e.V.), which shall act as a proxy defined in Section 14 of *
* version 3 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see . *
***************************************************************************/
#ifndef EFFECTSTACKVIEW_H
#define EFFECTSTACKVIEW_H
#include "definitions.h"
#include
#include
#include
#include
class QVBoxLayout;
class QTreeView;
class CollapsibleEffectView;
class AssetParameterModel;
class EffectStackModel;
class EffectItemModel;
class AssetIconProvider;
class BuiltStack;
class AssetPanel;
class WidgetDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit WidgetDelegate(QObject *parent = nullptr);
void setHeight(const QModelIndex &index, int height);
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
private:
QMap m_height;
};
class EffectStackView : public QWidget
{
Q_OBJECT
public:
EffectStackView(AssetPanel *parent);
virtual ~EffectStackView();
void setModel(std::shared_ptr model, const QSize frameSize);
void unsetModel(bool reset = true);
ObjectId stackOwner() const;
/** @brief Add an effect to the current stack
*/
bool addEffect(const QString &effectId);
/** @brief Returns true if effectstack is empty
*/
bool isEmpty() const;
/** @brief Enables / disables the stack
*/
void enableStack(bool enable);
bool isStackEnabled() const;
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
private:
QMutex m_mutex;
QVBoxLayout *m_lay;
// BuiltStack *m_builtStack;
QTreeView *m_effectsTree;
std::shared_ptr m_model;
std::vector m_widgets;
AssetIconProvider *m_thumbnailer;
/** @brief the frame size of the original clip this effect is applied on
*/
QSize m_sourceFrameSize;
const QString getStyleSheet();
void updateTreeHeight();
private slots:
void refresh(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles);
void slotAdjustDelegate(std::shared_ptr effectModel, int height);
void slotStartDrag(QPixmap pix, std::shared_ptr effectModel);
void slotActivateEffect(std::shared_ptr effectModel);
void loadEffects();
// void switchBuiltStack(bool show);
signals:
void doActivateEffect(QModelIndex);
void seekToPos(int);
void reloadEffect(const QString &path);
void updateEnabledState();
+ void removeCurrentEffect();
};
#endif
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 31083aae2..2de272553 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,3791 +1,3790 @@
/***************************************************************************
* Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "mainwindow.h"
#include "assets/assetpanel.hpp"
#include "bin/clipcreator.hpp"
#include "bin/generators/generators.h"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "dialogs/kdenlivesettingsdialog.h"
#include "dialogs/renderwidget.h"
#include "dialogs/wizard.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "effects/effectlist/view/effectlistwidget.hpp"
#include "effectslist/effectbasket.h"
#include "hidetitlebars.h"
#include "jobs/jobmanager.h"
#include "jobs/scenesplitjob.hpp"
#include "jobs/speedjob.hpp"
#include "jobs/stabilizejob.hpp"
#include "kdenlivesettings.h"
#include "layoutmanagement.h"
#include "library/librarywidget.h"
#include "mainwindowadaptor.h"
#include "mltconnection.h"
#include "mltcontroller/clipcontroller.h"
#include "monitor/monitor.h"
#include "monitor/monitormanager.h"
#include "monitor/scopes/audiographspectrum.h"
#include "profiles/profilemodel.hpp"
#include "project/cliptranscode.h"
#include "project/dialogs/archivewidget.h"
#include "project/dialogs/projectsettings.h"
#include "project/projectcommands.h"
#include "project/projectmanager.h"
#include "scopes/scopemanager.h"
#include "timeline2/view/timelinecontroller.h"
#include "timeline2/view/timelinetabs.hpp"
#include "timeline2/view/timelinewidget.h"
#include "titler/titlewidget.h"
#include "transitions/transitionlist/view/transitionlistwidget.hpp"
#include "transitions/transitionsrepository.hpp"
#include "utils/resourcewidget.h"
#include "utils/thememanager.h"
#include "profiles/profilerepository.hpp"
#include "widgets/progressbutton.h"
#include
#include "project/dialogs/temporarydata.h"
#ifdef USE_JOGSHUTTLE
#include "jogshuttle/jogmanager.h"
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include