diff --git a/src/assets/abstractassetsrepository.hpp b/src/assets/abstractassetsrepository.hpp
index 04fb1ba9d..afbc60dd0 100644
--- a/src/assets/abstractassetsrepository.hpp
+++ b/src/assets/abstractassetsrepository.hpp
@@ -1,121 +1,121 @@
/***************************************************************************
* 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 ASSETSREPOSITORY_H
#define ASSETSREPOSITORY_H
#include "definitions.h"
#include
#include
#include
#include
#include
#include
/** @brief This class is the base class for assets (transitions or effets) repositories
*/
template class AbstractAssetsRepository
{
public:
AbstractAssetsRepository();
virtual ~AbstractAssetsRepository() = default;
/* @brief Returns true if a given asset exists
*/
bool exists(const QString &assetId) const;
/* @brief Returns a vector of pair (asset id, asset name)
*/
QVector> getNames() const;
/* @brief Return type of asset */
AssetType getType(const QString &assetId) const;
/* @brief Return name of asset */
Q_INVOKABLE QString getName(const QString &assetId) const;
/* @brief Return description of asset */
QString getDescription(const QString &assetId) const;
/* @brief Set an asset as favorite (or not)*/
virtual void setFavorite(const QString &assetId, bool favorite) = 0;
/* @brief Returns a DomElement representing the asset's properties */
QDomElement getXml(const QString &assetId) const;
protected:
struct Info
{
QString id; // identifier of the asset
QString mltId; //"tag" of the asset, that is the name of the mlt service
QString name, description, author, version_str;
- int version;
+ int version{};
QDomElement xml;
AssetType type;
};
// Reads the blacklist file and populate appropriate structure
void parseBlackList(const QString &path);
void init();
virtual Mlt::Properties *retrieveListFromMlt() const = 0;
virtual void parseFavorites() = 0;
/* @brief Parse some info from a mlt structure
@param res Datastructure to fill
@return true on success
*/
- bool parseInfoFromMlt(const QString &effectId, Info &res);
+ bool parseInfoFromMlt(const QString &assetId, Info &res);
/* @brief Returns the metadata associated with the given asset*/
virtual Mlt::Properties *getMetadata(const QString &assetId) = 0;
/* @brief Parse one asset from its XML content
@param res data structure to fill
@return true of success
*/
bool parseInfoFromXml(const QDomElement ¤tAsset, Info &res) const;
/* @brief Figure what is the type of the asset based on its metadata and store it in res*/
virtual void parseType(QScopedPointer &metadata, Info &res) = 0;
/* @brief Retrieves additional info about asset from a custom XML file
The resulting assets are stored in customAssets
*/
virtual void parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const = 0;
/* @brief Returns the path to custom XML description of the assets*/
virtual QStringList assetDirs() const = 0;
/* @brief Returns the path to the assets' blacklist*/
virtual QString assetBlackListPath() const = 0;
std::unordered_map m_assets;
QSet m_blacklist;
QSet m_favorites;
};
#include "abstractassetsrepository.ipp"
#endif
diff --git a/src/assets/abstractassetsrepository.ipp b/src/assets/abstractassetsrepository.ipp
index 9215282be..c81231110 100644
--- a/src/assets/abstractassetsrepository.ipp
+++ b/src/assets/abstractassetsrepository.ipp
@@ -1,315 +1,315 @@
/***************************************************************************
* 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 "xml/xml.hpp"
#include
#include
#include
#include
#include
#include
#ifdef Q_OS_MAC
#include
#endif
template AbstractAssetsRepository::AbstractAssetsRepository() = default;
template void AbstractAssetsRepository::init()
{
// Warning: Mlt::Factory::init() resets the locale to the default system value, make sure we keep correct locale
#ifndef Q_OS_MAC
setlocale(LC_NUMERIC, nullptr);
#else
setlocale(LC_NUMERIC_MASK, nullptr);
#endif
// Parse effects blacklist
parseBlackList(assetBlackListPath());
parseFavorites();
// Retrieve the list of MLT's available assets.
QScopedPointer assets(retrieveListFromMlt());
int max = assets->count();
QString sox = QStringLiteral("sox.");
for (int i = 0; i < max; ++i) {
Info info;
QString name = assets->get_name(i);
info.id = name;
if (name.startsWith(sox)) {
// sox effects are not usage directly (parameters not available)
continue;
}
// qDebug() << "trying to parse " < customAssets;
for (const auto &dir : asset_dirs) {
QDir current_dir(dir);
QStringList filter;
filter << QStringLiteral("*.xml");
QStringList fileList = current_dir.entryList(filter, QDir::Files);
for (const auto &file : fileList) {
QString path = current_dir.absoluteFilePath(file);
parseCustomAssetFile(path, customAssets);
}
}
// We add the custom assets
for (const auto &custom : customAssets) {
// Custom assets should override default ones
m_assets[custom.first] = custom.second;
/*if (m_assets.count(custom.second.mltId) > 0) {
m_assets.erase(custom.second.mltId);
}
if (m_assets.count(custom.first) == 0) {
m_assets[custom.first] = custom.second;
} else {
qDebug() << "Error: conflicting asset name " << custom.first;
}*/
}
}
template void AbstractAssetsRepository::parseBlackList(const QString &path)
{
QFile blacklist_file(path);
if (blacklist_file.open(QIODevice::ReadOnly)) {
QTextStream stream(&blacklist_file);
QString line;
while (stream.readLineInto(&line)) {
line = line.simplified();
if (!line.isEmpty() && !line.startsWith('#')) {
m_blacklist.insert(line);
}
}
blacklist_file.close();
}
}
template bool AbstractAssetsRepository::parseInfoFromMlt(const QString &assetId, Info &res)
{
QScopedPointer metadata(getMetadata(assetId));
if (metadata && metadata->is_valid()) {
if (metadata->get("title") && metadata->get("identifier") && strlen(metadata->get("title")) > 0) {
res.name = metadata->get("title");
res.name[0] = res.name[0].toUpper();
res.description = metadata->get("description");
res.author = metadata->get("creator");
res.version_str = metadata->get("version");
res.version = ceil(100 * metadata->get_double("version"));
res.id = res.mltId = assetId;
parseType(metadata, res);
// Create params
QDomDocument doc;
QDomElement eff = doc.createElement(QStringLiteral("effect"));
QString id = metadata->get("identifier");
eff.setAttribute(QStringLiteral("tag"), id);
eff.setAttribute(QStringLiteral("id"), id);
////qCDebug(KDENLIVE_LOG)<<"Effect: "<get_data("parameters"));
for (int j = 0; param_props.is_valid() && j < param_props.count(); ++j) {
QDomElement params = doc.createElement(QStringLiteral("parameter"));
Mlt::Properties paramdesc((mlt_properties)param_props.get_data(param_props.get_name(j)));
params.setAttribute(QStringLiteral("name"), paramdesc.get("identifier"));
if (params.attribute(QStringLiteral("name")) == QLatin1String("argument")) {
// This parameter has to be given as attribute when using command line, do not show it in Kdenlive
continue;
}
- if (paramdesc.get("readonly") && !strcmp(paramdesc.get("readonly"), "yes")) {
+ if (paramdesc.get("readonly") && (strcmp(paramdesc.get("readonly"), "yes") == 0)) {
// Do not expose readonly parameters
continue;
}
if (paramdesc.get("maximum")) {
params.setAttribute(QStringLiteral("max"), paramdesc.get("maximum"));
}
if (paramdesc.get("minimum")) {
params.setAttribute(QStringLiteral("min"), paramdesc.get("minimum"));
}
QString paramType = paramdesc.get("type");
if (paramType == QLatin1String("integer")) {
if (params.attribute(QStringLiteral("min")) == QLatin1String("0") && params.attribute(QStringLiteral("max")) == QLatin1String("1")) {
params.setAttribute(QStringLiteral("type"), QStringLiteral("bool"));
} else {
params.setAttribute(QStringLiteral("type"), QStringLiteral("constant"));
}
} else if (paramType == QLatin1String("float")) {
params.setAttribute(QStringLiteral("type"), QStringLiteral("constant"));
// param type is float, set default decimals to 3
params.setAttribute(QStringLiteral("decimals"), QStringLiteral("3"));
} else if (paramType == QLatin1String("boolean")) {
params.setAttribute(QStringLiteral("type"), QStringLiteral("bool"));
} else if (paramType == QLatin1String("geometry")) {
params.setAttribute(QStringLiteral("type"), QStringLiteral("geometry"));
} else if (paramType == QLatin1String("string")) {
// string parameter are not really supported, so if we have a default value, enforce it
params.setAttribute(QStringLiteral("type"), QStringLiteral("fixed"));
if (paramdesc.get("default")) {
QString stringDefault = paramdesc.get("default");
stringDefault.remove(QLatin1Char('\''));
params.setAttribute(QStringLiteral("value"), stringDefault);
} else {
// String parameter without default, skip it completely
continue;
}
} else {
params.setAttribute(QStringLiteral("type"), paramType);
if (!QString(paramdesc.get("format")).isEmpty()) {
params.setAttribute(QStringLiteral("format"), paramdesc.get("format"));
}
}
if (!params.hasAttribute(QStringLiteral("value"))) {
if (paramdesc.get("default")) {
params.setAttribute(QStringLiteral("default"), paramdesc.get("default"));
}
if (paramdesc.get("value")) {
params.setAttribute(QStringLiteral("value"), paramdesc.get("value"));
} else {
params.setAttribute(QStringLiteral("value"), paramdesc.get("default"));
}
}
QString paramName = paramdesc.get("title");
if (!paramName.isEmpty()) {
QDomElement pname = doc.createElement(QStringLiteral("name"));
pname.appendChild(doc.createTextNode(paramName));
params.appendChild(pname);
}
if (paramdesc.get("description")) {
QDomElement comment = doc.createElement(QStringLiteral("comment"));
comment.appendChild(doc.createTextNode(paramdesc.get("description")));
params.appendChild(comment);
}
eff.appendChild(params);
}
doc.appendChild(eff);
res.xml = eff;
return true;
}
}
return false;
}
template bool AbstractAssetsRepository::exists(const QString &assetId) const
{
return m_assets.count(assetId) > 0;
}
template QVector> AbstractAssetsRepository::getNames() const
{
QVector> res;
res.reserve((int)m_assets.size());
for (const auto &asset : m_assets) {
res.push_back({asset.first, asset.second.name});
}
std::sort(res.begin(), res.end(), [](const QPair &a, const QPair &b) { return a.second < b.second; });
return res;
}
template AssetType AbstractAssetsRepository::getType(const QString &assetId) const
{
Q_ASSERT(m_assets.count(assetId) > 0);
return m_assets.at(assetId).type;
}
template QString AbstractAssetsRepository::getName(const QString &assetId) const
{
Q_ASSERT(m_assets.count(assetId) > 0);
return m_assets.at(assetId).name;
}
template QString AbstractAssetsRepository::getDescription(const QString &assetId) const
{
Q_ASSERT(m_assets.count(assetId) > 0);
return m_assets.at(assetId).description;
}
template bool AbstractAssetsRepository::parseInfoFromXml(const QDomElement ¤tAsset, Info &res) const
{
QString tag = currentAsset.attribute(QStringLiteral("tag"), QString());
QString id = currentAsset.attribute(QStringLiteral("id"), QString());
if (id.isEmpty()) {
id = tag;
}
if (!exists(tag)) {
qDebug() << "++++++ Unknown asset : " << tag;
return false;
}
// Check if there is a maximal version set
if (currentAsset.hasAttribute(QStringLiteral("version"))) {
// a specific version of the filter is required
if (m_assets.at(tag).version < (int)(100 * currentAsset.attribute(QStringLiteral("version")).toDouble())) {
return false;
}
}
res = m_assets.at(tag);
res.id = id;
res.mltId = tag;
// Update description if the xml provide one
QString description = Xml::getSubTagContent(currentAsset, QStringLiteral("description"));
if (!description.isEmpty()) {
res.description = description;
}
// Update name if the xml provide one
QString name = Xml::getSubTagContent(currentAsset, QStringLiteral("name"));
if (!name.isEmpty()) {
res.name = name;
}
return true;
}
template QDomElement AbstractAssetsRepository::getXml(const QString &assetId) const
{
if (m_assets.count(assetId) == 0) {
qDebug() << "Error : Requesting info on unknown transition " << assetId;
return QDomElement();
}
return m_assets.at(assetId).xml.cloneNode().toElement();
}
diff --git a/src/assets/model/assetparametermodel.cpp b/src/assets/model/assetparametermodel.cpp
index a5c272673..658ec4548 100644
--- a/src/assets/model/assetparametermodel.cpp
+++ b/src/assets/model/assetparametermodel.cpp
@@ -1,817 +1,817 @@
/***************************************************************************
* 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 "assetparametermodel.hpp"
#include "assets/keyframes/model/keyframemodellist.hpp"
#include "core.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "profiles/profilemodel.hpp"
#include
#include
#include
#include
#include
AssetParameterModel::AssetParameterModel(std::unique_ptr asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
QObject *parent)
: QAbstractListModel(parent)
, monitorId(ownerId.first == ObjectType::BinClip ? Kdenlive::ClipMonitor : Kdenlive::ProjectMonitor)
, m_assetId(assetId)
, m_ownerId(ownerId)
, m_asset(std::move(asset))
, m_keyframes(nullptr)
{
Q_ASSERT(m_asset->is_valid());
QDomNodeList nodeList = assetXml.elementsByTagName(QStringLiteral("parameter"));
m_hideKeyframesByDefault = assetXml.hasAttribute(QStringLiteral("hideKeyframes"));
bool needsLocaleConversion = false;
QChar separator, oldSeparator;
// Check locale, default effects xml has no LC_NUMERIC defined and always uses the C locale
QLocale locale;
if (assetXml.hasAttribute(QStringLiteral("LC_NUMERIC"))) {
QLocale effectLocale = QLocale(assetXml.attribute(QStringLiteral("LC_NUMERIC")));
if (QLocale::c().decimalPoint() != effectLocale.decimalPoint()) {
needsLocaleConversion = true;
separator = QLocale::c().decimalPoint();
oldSeparator = effectLocale.decimalPoint();
}
}
qDebug() << "XML parsing of " << assetId << ". found : " << nodeList.count();
for (int i = 0; i < nodeList.count(); ++i) {
QDomElement currentParameter = nodeList.item(i).toElement();
// Convert parameters if we need to
if (needsLocaleConversion) {
QDomNamedNodeMap attrs = currentParameter.attributes();
for (int k = 0; k < attrs.count(); ++k) {
QString nodeName = attrs.item(k).nodeName();
if (nodeName != QLatin1String("type") && nodeName != QLatin1String("name")) {
QString val = attrs.item(k).nodeValue();
if (val.contains(oldSeparator)) {
QString newVal = val.replace(oldSeparator, separator);
attrs.item(k).setNodeValue(newVal);
}
}
}
}
// Parse the basic attributes of the parameter
QString name = currentParameter.attribute(QStringLiteral("name"));
QString type = currentParameter.attribute(QStringLiteral("type"));
QString value = currentParameter.attribute(QStringLiteral("value"));
ParamRow currentRow;
currentRow.type = paramTypeFromStr(type);
currentRow.xml = currentParameter;
if (value.isNull()) {
QVariant defaultValue = parseAttribute(m_ownerId, QStringLiteral("default"), currentParameter);
value = defaultValue.type() == QVariant::Double ? locale.toString(defaultValue.toDouble()) : defaultValue.toString();
}
bool isFixed = (type == QLatin1String("fixed"));
if (isFixed) {
m_fixedParams[name] = value;
} else if (currentRow.type == ParamType::Position) {
int val = value.toInt();
if (val < 0) {
int in = pCore->getItemIn(m_ownerId);
int out = in + pCore->getItemDuration(m_ownerId);
val += out;
value = QString::number(val);
}
} else if (currentRow.type == ParamType::KeyframeParam || currentRow.type == ParamType::AnimatedRect) {
if (!value.contains(QLatin1Char('='))) {
value.prepend(QStringLiteral("%1=").arg(pCore->getItemIn(m_ownerId)));
}
}
if (!name.isEmpty()) {
setParameter(name, value, false);
// Keep track of param order
m_paramOrder.push_back(name);
}
if (isFixed) {
// fixed parameters are not displayed so we don't store them.
continue;
}
currentRow.value = value;
QString title = currentParameter.firstChildElement(QStringLiteral("name")).text();
currentRow.name = title.isEmpty() ? name : title;
m_params[name] = currentRow;
m_rows.push_back(name);
}
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Sox effects need to have a special "Effect" value set
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
}
qDebug() << "END parsing of " << assetId << ". Number of found parameters" << m_rows.size();
emit modelChanged();
}
void AssetParameterModel::prepareKeyframes()
{
if (m_keyframes) return;
int ix = 0;
for (const auto &name : m_rows) {
if (m_params[name].type == ParamType::KeyframeParam || m_params[name].type == ParamType::AnimatedRect ||
m_params[name].type == ParamType::Roto_spline) {
addKeyframeParam(index(ix, 0));
}
ix++;
}
}
-void AssetParameterModel::setParameter(const QString &name, const int value, bool update)
+void AssetParameterModel::setParameter(const QString &name, int value, bool update)
{
Q_ASSERT(m_asset->is_valid());
m_asset->set(name.toLatin1().constData(), value);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
if (update) {
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
qDebug() << "// Warning, SOX effect, need unplug/replug";
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
} else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) {
// these effects don't understand param change and need to be rebuild
emit replugEffect(shared_from_this());
} else {
emit modelChanged();
emit dataChanged(index(0, 0), index(m_rows.count() - 1, 0), {});
}
// Update fades in timeline
pCore->updateItemModel(m_ownerId, m_assetId);
// Trigger monitor refresh
pCore->refreshProjectItem(m_ownerId);
// Invalidate timeline preview
pCore->invalidateItem(m_ownerId);
}
}
void AssetParameterModel::setParameter(const QString &name, const QString ¶mValue, bool update, const QModelIndex ¶mIndex)
{
Q_ASSERT(m_asset->is_valid());
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
qDebug() << "// PROCESSING PARAM CHANGE: " << name << ", UPDATE: " << update << ", VAL: " << paramValue;
// TODO: this does not really belong here, but I don't see another way to do it so that undo works
if (data(paramIndex, AssetParameterModel::TypeRole).value() == ParamType::Curve) {
QStringList vals = paramValue.split(QLatin1Char(';'), QString::SkipEmptyParts);
int points = vals.size();
m_asset->set("3", points / 10.);
// for the curve, inpoints are numbered: 6, 8, 10, 12, 14
// outpoints, 7, 9, 11, 13,15 so we need to deduce these enums
for (int i = 0; i < points; i++) {
const QString &pointVal = vals.at(i);
int idx = 2 * i + 6;
m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 0, 0).toDouble());
idx++;
m_asset->set(QString::number(idx).toLatin1().constData(), pointVal.section(QLatin1Char('/'), 1, 1).toDouble());
}
}
bool conversionSuccess;
double doubleValue = locale.toDouble(paramValue, &conversionSuccess);
if (conversionSuccess) {
m_asset->set(name.toLatin1().constData(), doubleValue);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = doubleValue;
} else {
m_fixedParams[name] = doubleValue;
}
} else {
m_asset->set(name.toLatin1().constData(), paramValue.toUtf8().constData());
qDebug() << " = = SET EFFECT PARAM: " << name << " = " << paramValue;
if (m_fixedParams.count(name) == 0) {
m_params[name].value = paramValue;
} else {
m_fixedParams[name] = paramValue;
}
}
if (update) {
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
qDebug() << "// Warning, SOX effect, need unplug/replug";
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
} else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) {
// these effects don't understand param change and need to be rebuild
emit replugEffect(shared_from_this());
} else {
qDebug() << "// SENDING DATA CHANGE....";
if (paramIndex.isValid()) {
emit dataChanged(paramIndex, paramIndex);
} else {
QModelIndex ix = index(m_rows.indexOf(name), 0);
emit dataChanged(ix, ix);
}
emit modelChanged();
}
}
emit updateChildren(name);
// Update timeline view if necessary
if (m_ownerId.first == ObjectType::NoItem) {
// Used for generator clips
if (!update) emit modelChanged();
} else {
// Update fades in timeline
pCore->updateItemModel(m_ownerId, m_assetId);
// Trigger monitor refresh
pCore->refreshProjectItem(m_ownerId);
// Invalidate timeline preview
pCore->invalidateItem(m_ownerId);
}
}
void AssetParameterModel::setParameter(const QString &name, double &value)
{
Q_ASSERT(m_asset->is_valid());
m_asset->set(name.toLatin1().constData(), value);
if (m_fixedParams.count(name) == 0) {
m_params[name].value = value;
} else {
m_fixedParams[name] = value;
}
if (m_assetId.startsWith(QStringLiteral("sox_"))) {
// Warning, SOX effect, need unplug/replug
qDebug() << "// Warning, SOX effect, need unplug/replug";
QStringList effectParam = {m_assetId.section(QLatin1Char('_'), 1)};
for (const QString &pName : m_paramOrder) {
effectParam << m_asset->get(pName.toUtf8().constData());
}
m_asset->set("effect", effectParam.join(QLatin1Char(' ')).toUtf8().constData());
emit replugEffect(shared_from_this());
} else if (m_assetId == QLatin1String("autotrack_rectangle") || m_assetId.startsWith(QStringLiteral("ladspa"))) {
// these effects don't understand param change and need to be rebuild
emit replugEffect(shared_from_this());
} else {
emit modelChanged();
}
pCore->refreshProjectItem(m_ownerId);
pCore->invalidateItem(m_ownerId);
}
AssetParameterModel::~AssetParameterModel() = default;
QVariant AssetParameterModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_rows.size() || !index.isValid()) {
return QVariant();
}
QString paramName = m_rows[index.row()];
Q_ASSERT(m_params.count(paramName) > 0);
const QDomElement &element = m_params.at(paramName).xml;
switch (role) {
case Qt::DisplayRole:
case Qt::EditRole:
return m_params.at(paramName).name;
case NameRole:
return paramName;
case TypeRole:
return QVariant::fromValue(m_params.at(paramName).type);
case CommentRole: {
QDomElement commentElem = element.firstChildElement(QStringLiteral("comment"));
QString comment;
if (!commentElem.isNull()) {
comment = i18n(commentElem.text().toUtf8().data());
}
return comment;
}
case InRole:
return m_asset->get_int("in");
case OutRole:
return m_asset->get_int("out");
case ParentInRole:
return pCore->getItemIn(m_ownerId);
case ParentDurationRole:
return pCore->getItemDuration(m_ownerId);
case ParentPositionRole:
return pCore->getItemPosition(m_ownerId);
case HideKeyframesFirstRole:
return m_hideKeyframesByDefault;
case MinRole:
return parseAttribute(m_ownerId, QStringLiteral("min"), element);
case MaxRole:
return parseAttribute(m_ownerId, QStringLiteral("max"), element);
case FactorRole:
return parseAttribute(m_ownerId, QStringLiteral("factor"), element, 1);
case ScaleRole:
return parseAttribute(m_ownerId, QStringLiteral("scale"), element, 0);
case DecimalsRole:
return parseAttribute(m_ownerId, QStringLiteral("decimals"), element);
case DefaultRole:
return parseAttribute(m_ownerId, QStringLiteral("default"), element);
case FilterRole:
return parseAttribute(m_ownerId, QStringLiteral("filter"), element);
case SuffixRole:
return element.attribute(QStringLiteral("suffix"));
case OpacityRole:
return element.attribute(QStringLiteral("opacity")) != QLatin1String("false");
case RelativePosRole:
return element.attribute(QStringLiteral("relative")) == QLatin1String("true");
case ShowInTimelineRole:
return !element.hasAttribute(QStringLiteral("notintimeline"));
case AlphaRole:
return element.attribute(QStringLiteral("alpha")) == QLatin1String("1");
case ValueRole: {
QString value(m_asset->get(paramName.toUtf8().constData()));
return value.isEmpty() ? (element.attribute(QStringLiteral("value")).isNull() ? parseAttribute(m_ownerId, QStringLiteral("default"), element)
: element.attribute(QStringLiteral("value")))
: value;
}
case ListValuesRole:
return element.attribute(QStringLiteral("paramlist")).split(QLatin1Char(';'));
case ListNamesRole: {
QDomElement namesElem = element.firstChildElement(QStringLiteral("paramlistdisplay"));
return i18n(namesElem.text().toUtf8().data()).split(QLatin1Char(','));
}
case List1Role:
return parseAttribute(m_ownerId, QStringLiteral("list1"), element);
case List2Role:
return parseAttribute(m_ownerId, QStringLiteral("list2"), element);
case Enum1Role:
return m_asset->get_double("1");
case Enum2Role:
return m_asset->get_double("2");
case Enum3Role:
return m_asset->get_double("3");
case Enum4Role:
return m_asset->get_double("4");
case Enum5Role:
return m_asset->get_double("5");
case Enum6Role:
return m_asset->get_double("6");
case Enum7Role:
return m_asset->get_double("7");
case Enum8Role:
return m_asset->get_double("8");
case Enum9Role:
return m_asset->get_double("9");
case Enum10Role:
return m_asset->get_double("10");
case Enum11Role:
return m_asset->get_double("11");
case Enum12Role:
return m_asset->get_double("12");
case Enum13Role:
return m_asset->get_double("13");
case Enum14Role:
return m_asset->get_double("14");
case Enum15Role:
return m_asset->get_double("15");
}
return QVariant();
}
int AssetParameterModel::rowCount(const QModelIndex &parent) const
{
qDebug() << "===================================================== Requested rowCount" << parent << m_rows.size();
if (parent.isValid()) return 0;
return m_rows.size();
}
// static
ParamType AssetParameterModel::paramTypeFromStr(const QString &type)
{
if (type == QLatin1String("double") || type == QLatin1String("float") || type == QLatin1String("constant")) {
return ParamType::Double;
}
if (type == QLatin1String("list")) {
return ParamType::List;
}
if (type == QLatin1String("bool")) {
return ParamType::Bool;
}
if (type == QLatin1String("switch")) {
return ParamType::Switch;
} else if (type == QLatin1String("simplekeyframe")) {
return ParamType::KeyframeParam;
} else if (type == QLatin1String("animatedrect")) {
return ParamType::AnimatedRect;
} else if (type == QLatin1String("geometry")) {
return ParamType::Geometry;
} else if (type == QLatin1String("addedgeometry")) {
return ParamType::Addedgeometry;
} else if (type == QLatin1String("keyframe") || type == QLatin1String("animated")) {
return ParamType::KeyframeParam;
} else if (type == QLatin1String("color")) {
return ParamType::Color;
} else if (type == QLatin1String("colorwheel")) {
return ParamType::ColorWheel;
} else if (type == QLatin1String("position")) {
return ParamType::Position;
} else if (type == QLatin1String("curve")) {
return ParamType::Curve;
} else if (type == QLatin1String("bezier_spline")) {
return ParamType::Bezier_spline;
} else if (type == QLatin1String("roto-spline")) {
return ParamType::Roto_spline;
} else if (type == QLatin1String("wipe")) {
return ParamType::Wipe;
} else if (type == QLatin1String("url")) {
return ParamType::Url;
} else if (type == QLatin1String("keywords")) {
return ParamType::Keywords;
} else if (type == QLatin1String("fontfamily")) {
return ParamType::Fontfamily;
} else if (type == QLatin1String("filterjob")) {
return ParamType::Filterjob;
} else if (type == QLatin1String("readonly")) {
return ParamType::Readonly;
} else if (type == QLatin1String("hidden")) {
return ParamType::Hidden;
}
qDebug() << "WARNING: Unknown type :" << type;
return ParamType::Double;
}
// static
QString AssetParameterModel::getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly)
{
QString keyframes = QString::number(start);
if (linearOnly) {
keyframes.append(QLatin1Char('='));
} else {
switch (KdenliveSettings::defaultkeyframeinterp()) {
case mlt_keyframe_discrete:
keyframes.append(QStringLiteral("|="));
break;
case mlt_keyframe_smooth:
keyframes.append(QStringLiteral("~="));
break;
default:
keyframes.append(QLatin1Char('='));
break;
}
}
keyframes.append(defaultValue);
return keyframes;
}
// static
-QVariant AssetParameterModel::parseAttribute(const ObjectId owner, const QString &attribute, const QDomElement &element, QVariant defaultValue)
+QVariant AssetParameterModel::parseAttribute(const ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue)
{
if (!element.hasAttribute(attribute) && !defaultValue.isNull()) {
return defaultValue;
}
ParamType type = paramTypeFromStr(element.attribute(QStringLiteral("type")));
QString content = element.attribute(attribute);
if (content.contains(QLatin1Char('%'))) {
std::unique_ptr &profile = pCore->getCurrentProfile();
int width = profile->width();
int height = profile->height();
int in = pCore->getItemIn(owner);
int out = in + pCore->getItemDuration(owner);
// replace symbols in the double parameter
content.replace(QLatin1String("%maxWidth"), QString::number(width))
.replace(QLatin1String("%maxHeight"), QString::number(height))
.replace(QLatin1String("%width"), QString::number(width))
.replace(QLatin1String("%height"), QString::number(height))
.replace(QLatin1String("%out"), QString::number(out));
if (type == ParamType::Double) {
// Use a Mlt::Properties to parse mathematical operators
Mlt::Properties p;
p.set("eval", content.toLatin1().constData());
return p.get_double("eval");
}
} else if (type == ParamType::Double || type == ParamType::Hidden) {
QLocale locale;
locale.setNumberOptions(QLocale::OmitGroupSeparator);
if (attribute == QLatin1String("default")) {
int factor = element.attribute(QStringLiteral("factor"), QStringLiteral("1")).toInt();
if (factor > 0) {
return content.toDouble() / factor;
}
}
return locale.toDouble(content);
}
if (attribute == QLatin1String("default")) {
if (type == ParamType::RestrictedAnim) {
content = getDefaultKeyframes(0, content, true);
} else if (type == ParamType::KeyframeParam) {
return content.toDouble();
} else if (type == ParamType::List) {
bool ok;
double res = content.toDouble(&ok);
if (ok) {
return res;
}
} else if (type == ParamType::Bezier_spline) {
QLocale locale;
if (locale.decimalPoint() != QLocale::c().decimalPoint()) {
return content.replace(QLocale::c().decimalPoint(), locale.decimalPoint());
}
}
}
return content;
}
QString AssetParameterModel::getAssetId() const
{
return m_assetId;
}
QVector> AssetParameterModel::getAllParameters() const
{
QVector> res;
res.reserve((int)m_fixedParams.size() + (int)m_params.size());
for (const auto &fixed : m_fixedParams) {
res.push_back(QPair(fixed.first, fixed.second));
}
for (const auto ¶m : m_params) {
res.push_back(QPair(param.first, param.second.value));
}
return res;
}
QJsonDocument AssetParameterModel::toJson() const
{
QJsonArray list;
QLocale locale;
for (const auto &fixed : m_fixedParams) {
QJsonObject currentParam;
QModelIndex ix = index(m_rows.indexOf(fixed.first), 0);
currentParam.insert(QLatin1String("name"), QJsonValue(fixed.first));
currentParam.insert(QLatin1String("value"), fixed.second.toString());
int type = data(ix, AssetParameterModel::TypeRole).toInt();
double min = data(ix, AssetParameterModel::MinRole).toDouble();
double max = data(ix, AssetParameterModel::MaxRole).toDouble();
double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
if (factor > 0) {
min /= factor;
max /= factor;
}
currentParam.insert(QLatin1String("type"), QJsonValue(type));
currentParam.insert(QLatin1String("min"), QJsonValue(min));
currentParam.insert(QLatin1String("max"), QJsonValue(max));
list.push_back(currentParam);
}
for (const auto ¶m : m_params) {
QJsonObject currentParam;
QModelIndex ix = index(m_rows.indexOf(param.first), 0);
currentParam.insert(QLatin1String("name"), QJsonValue(param.first));
currentParam.insert(QLatin1String("value"), QJsonValue(param.second.value.toString()));
int type = data(ix, AssetParameterModel::TypeRole).toInt();
double min = data(ix, AssetParameterModel::MinRole).toDouble();
double max = data(ix, AssetParameterModel::MaxRole).toDouble();
double factor = data(ix, AssetParameterModel::FactorRole).toDouble();
if (factor > 0) {
min /= factor;
max /= factor;
}
currentParam.insert(QLatin1String("type"), QJsonValue(type));
currentParam.insert(QLatin1String("min"), QJsonValue(min));
currentParam.insert(QLatin1String("max"), QJsonValue(max));
list.push_back(currentParam);
}
return QJsonDocument(list);
}
void AssetParameterModel::deletePreset(const QString &presetFile, const QString &presetName)
{
QJsonObject object;
QJsonArray array;
QFile loadFile(presetFile);
if (loadFile.exists()) {
if (loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isArray()) {
qDebug() << " * * ** JSON IS AN ARRAY, DELETING: " << presetName;
array = loadDoc.array();
QList toDelete;
for (int i = 0; i < array.size(); i++) {
QJsonValue val = array.at(i);
if (val.isObject() && val.toObject().keys().contains(presetName)) {
toDelete << i;
}
}
for (int i : toDelete) {
array.removeAt(i);
}
} else if (loadDoc.isObject()) {
QJsonObject obj = loadDoc.object();
qDebug() << " * * ** JSON IS AN OBJECT, DELETING: " << presetName;
if (obj.keys().contains(presetName)) {
obj.remove(presetName);
} else {
qDebug() << " * * ** JSON DOES NOT CONTAIN: " << obj.keys();
}
array.append(obj);
}
loadFile.close();
} else if (!loadFile.open(QIODevice::ReadWrite)) {
// TODO: error message
}
}
if (!loadFile.open(QIODevice::WriteOnly)) {
// TODO: error message
}
loadFile.write(QJsonDocument(array).toJson());
}
void AssetParameterModel::savePreset(const QString &presetFile, const QString &presetName)
{
QJsonObject object;
QJsonArray array;
QJsonDocument doc = toJson();
QFile loadFile(presetFile);
if (loadFile.exists()) {
if (loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isArray()) {
array = loadDoc.array();
QList toDelete;
for (int i = 0; i < array.size(); i++) {
QJsonValue val = array.at(i);
if (val.isObject() && val.toObject().keys().contains(presetName)) {
toDelete << i;
}
}
for (int i : toDelete) {
array.removeAt(i);
}
} else if (loadDoc.isObject()) {
QJsonObject obj = loadDoc.object();
if (obj.keys().contains(presetName)) {
obj.remove(presetName);
}
array.append(obj);
}
loadFile.close();
} else if (!loadFile.open(QIODevice::ReadWrite)) {
// TODO: error message
}
}
if (!loadFile.open(QIODevice::WriteOnly)) {
// TODO: error message
}
object[presetName] = doc.array();
array.append(object);
loadFile.write(QJsonDocument(array).toJson());
}
const QStringList AssetParameterModel::getPresetList(const QString &presetFile) const
{
QFile loadFile(presetFile);
if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isObject()) {
qDebug() << "// PRESET LIST IS AN OBJECT!!!";
return loadDoc.object().keys();
} else if (loadDoc.isArray()) {
qDebug() << "// PRESET LIST IS AN ARRAY!!!";
QStringList result;
QJsonArray array = loadDoc.array();
for (auto &&i : array) {
QJsonValue val = i;
if (val.isObject()) {
result << val.toObject().keys();
}
}
return result;
}
}
return QStringList();
}
const QVector> AssetParameterModel::loadPreset(const QString &presetFile, const QString &presetName)
{
QFile loadFile(presetFile);
QVector> params;
if (loadFile.exists() && loadFile.open(QIODevice::ReadOnly)) {
QByteArray saveData = loadFile.readAll();
QJsonDocument loadDoc(QJsonDocument::fromJson(saveData));
if (loadDoc.isObject() && loadDoc.object().contains(presetName)) {
qDebug() << "..........\n..........\nLOADING OBJECT JSON";
QJsonValue val = loadDoc.object().value(presetName);
if (val.isObject()) {
QVariantMap map = val.toObject().toVariantMap();
QMap::const_iterator i = map.constBegin();
while (i != map.constEnd()) {
params.append({i.key(), i.value()});
++i;
}
}
} else if (loadDoc.isArray()) {
QJsonArray array = loadDoc.array();
for (auto &&i : array) {
QJsonValue val = i;
if (val.isObject() && val.toObject().contains(presetName)) {
QJsonValue preset = val.toObject().value(presetName);
if (preset.isArray()) {
QJsonArray paramArray = preset.toArray();
for (auto &&j : paramArray) {
QJsonValue v1 = j;
if (v1.isObject()) {
QJsonObject ob = v1.toObject();
params.append({ob.value("name").toString(), ob.value("value").toVariant()});
}
}
}
qDebug() << "// LOADED PRESET: " << presetName << "\n" << params;
break;
}
}
}
}
return params;
}
void AssetParameterModel::setParameters(const QVector> ¶ms)
{
QLocale locale;
for (const auto ¶m : params) {
if (param.second.type() == QVariant::Double) {
setParameter(param.first, locale.toString(param.second.toDouble()), false);
} else {
setParameter(param.first, param.second.toString(), false);
}
}
if (m_keyframes) {
m_keyframes->refresh();
}
// emit modelChanged();
emit dataChanged(index(0), index(m_rows.count()), {});
}
ObjectId AssetParameterModel::getOwnerId() const
{
return m_ownerId;
}
-void AssetParameterModel::addKeyframeParam(const QModelIndex index)
+void AssetParameterModel::addKeyframeParam(const QModelIndex &index)
{
if (m_keyframes) {
m_keyframes->addParameter(index);
} else {
m_keyframes.reset(new KeyframeModelList(shared_from_this(), index, pCore->undoStack()));
}
}
std::shared_ptr AssetParameterModel::getKeyframeModel()
{
return m_keyframes;
}
void AssetParameterModel::resetAsset(std::unique_ptr asset)
{
m_asset = std::move(asset);
}
bool AssetParameterModel::hasMoreThanOneKeyframe() const
{
if (m_keyframes) {
return (!m_keyframes->isEmpty() && !m_keyframes->singleKeyframe());
}
return false;
}
int AssetParameterModel::time_to_frames(const QString &time)
{
return m_asset->time_to_frames(time.toUtf8().constData());
}
void AssetParameterModel::passProperties(Mlt::Properties &target)
{
target.set("_profile", pCore->getCurrentProfile()->get_profile(), 0);
target.set_lcnumeric(m_asset->get_lcnumeric());
}
diff --git a/src/assets/model/assetparametermodel.hpp b/src/assets/model/assetparametermodel.hpp
index 5f78e6e5b..e75e81569 100644
--- a/src/assets/model/assetparametermodel.hpp
+++ b/src/assets/model/assetparametermodel.hpp
@@ -1,220 +1,220 @@
/***************************************************************************
* 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 ASSETPARAMETERMODEL_H
#define ASSETPARAMETERMODEL_H
#include "definitions.h"
#include "klocalizedstring.h"
#include
#include
#include
#include
#include
#include
#include
class KeyframeModelList;
/* @brief This class is the model for a list of parameters.
The behaviour of a transition or an effect is typically controlled by several parameters. This class exposes this parameters as a list that can be rendered
using the relevant widgets.
Note that internally parameters are not sorted in any ways, because some effects like sox need a precise order
*/
enum class ParamType {
Double,
List,
Bool,
Switch,
RestrictedAnim, // animated 1 dimensional param with linear support only
Animated,
AnimatedRect,
Geometry,
Addedgeometry,
KeyframeParam,
Color,
ColorWheel,
Position,
Curve,
Bezier_spline,
Roto_spline,
Wipe,
Url,
Keywords,
Fontfamily,
Filterjob,
Readonly,
Hidden
};
Q_DECLARE_METATYPE(ParamType)
class AssetParameterModel : public QAbstractListModel, public enable_shared_from_this_virtual
{
Q_OBJECT
public:
explicit AssetParameterModel(std::unique_ptr asset, const QDomElement &assetXml, const QString &assetId, ObjectId ownerId,
QObject *parent = nullptr);
~AssetParameterModel() override;
enum DataRoles {
NameRole = Qt::UserRole + 1,
TypeRole,
CommentRole,
MinRole,
MaxRole,
DefaultRole,
SuffixRole,
DecimalsRole,
ValueRole,
AlphaRole,
ListValuesRole,
ListNamesRole,
FactorRole,
FilterRole,
ScaleRole,
OpacityRole,
RelativePosRole,
// Don't display this param in timeline keyframes
ShowInTimelineRole,
InRole,
OutRole,
ParentInRole,
ParentPositionRole,
ParentDurationRole,
HideKeyframesFirstRole,
List1Role,
List2Role,
Enum1Role,
Enum2Role,
Enum3Role,
Enum4Role,
Enum5Role,
Enum6Role,
Enum7Role,
Enum8Role,
Enum9Role,
Enum10Role,
Enum11Role,
Enum12Role,
Enum13Role,
Enum14Role,
Enum15Role,
Enum16Role
};
/* @brief Returns the id of the asset represented by this object */
QString getAssetId() const;
/* @brief Set the parameter with given name to the given value
*/
Q_INVOKABLE void setParameter(const QString &name, const QString ¶mValue, bool update = true, const QModelIndex ¶mIndex = QModelIndex());
- void setParameter(const QString &name, const int value, bool update = true);
+ void setParameter(const QString &name, int value, bool update = true);
Q_INVOKABLE void setParameter(const QString &name, double &value);
/* @brief Return all the parameters as pairs (parameter name, parameter value) */
QVector> getAllParameters() const;
/* @brief Returns a json definition of the effect with all param values */
QJsonDocument toJson() const;
void savePreset(const QString &presetFile, const QString &presetName);
void deletePreset(const QString &presetFile, const QString &presetName);
const QStringList getPresetList(const QString &presetFile) const;
const QVector> loadPreset(const QString &presetFile, const QString &presetName);
/* @brief Sets the value of a list of parameters
@param params contains the pairs (parameter name, parameter value)
*/
void setParameters(const QVector> ¶ms);
/* Which monitor is attached to this asset (clip/project)
*/
Kdenlive::MonitorId monitorId;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/* @brief Returns the id of the actual object associated with this asset */
ObjectId getOwnerId() const;
/* @brief Returns the keyframe model associated with this asset
Return empty ptr if there is no keyframable parameter in the asset or if prepareKeyframes was not called
*/
Q_INVOKABLE std::shared_ptr getKeyframeModel();
/* @brief Must be called before using the keyframes of this model */
void prepareKeyframes();
void resetAsset(std::unique_ptr asset);
/* @brief Returns true if the effect has more than one keyframe */
bool hasMoreThanOneKeyframe() const;
int time_to_frames(const QString &time);
void passProperties(Mlt::Properties &target);
protected:
/* @brief Helper function to retrieve the type of a parameter given the string corresponding to it*/
static ParamType paramTypeFromStr(const QString &type);
static QString getDefaultKeyframes(int start, const QString &defaultValue, bool linearOnly);
/* @brief Helper function to get an attribute from a dom element, given its name.
The function additionally parses following keywords:
- %width and %height that are replaced with profile's height and width.
If keywords are found, mathematical operations are supported for double type params. For example "%width -1" is a valid value.
*/
- static QVariant parseAttribute(const ObjectId owner, const QString &attribute, const QDomElement &element, QVariant defaultValue = QVariant());
+ static QVariant parseAttribute(const ObjectId &owner, const QString &attribute, const QDomElement &element, QVariant defaultValue = QVariant());
/* @brief Helper function to register one more parameter that is keyframable.
@param index is the index corresponding to this parameter
*/
- void addKeyframeParam(const QModelIndex index);
+ void addKeyframeParam(const QModelIndex &index);
struct ParamRow
{
ParamType type;
QDomElement xml;
QVariant value;
QString name;
};
QString m_assetId;
ObjectId m_ownerId;
std::vector m_paramOrder; // Keep track of parameter order, important for sox
std::unordered_map m_params; // Store all parameters by name
std::unordered_map m_fixedParams; // We store values of fixed parameters aside
QVector m_rows; // We store the params name in order of parsing. The order is important (cf some effects like sox)
std::unique_ptr m_asset;
std::shared_ptr m_keyframes;
// if true, keyframe tools will be hidden by default
bool m_hideKeyframesByDefault;
signals:
void modelChanged();
/** @brief inform child effects (in case of bin effect with timeline producers)
* that a change occurred and a param update is needed **/
void updateChildren(const QString &name);
void compositionTrackChanged();
void replugEffect(std::shared_ptr asset);
void rebuildEffect(std::shared_ptr asset);
void enabledChange(bool);
};
#endif
diff --git a/src/bin/bin.cpp b/src/bin/bin.cpp
index 690a56630..3ff887dc1 100644
--- a/src/bin/bin.cpp
+++ b/src/bin/bin.cpp
@@ -1,3116 +1,3116 @@
/*
Copyright (C) 2012 Till Theato
Copyright (C) 2014 Jean-Baptiste Mardelle
This file is part of Kdenlive. See www.kdenlive.org.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License or (at your option) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy
defined in Section 14 of version 3 of the license.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "bin.h"
#include "bincommands.h"
#include "clipcreator.hpp"
#include "core.h"
#include "dialogs/clipcreationdialog.h"
#include "doc/documentchecker.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "jobs/audiothumbjob.hpp"
#include "jobs/jobmanager.h"
#include "jobs/loadjob.hpp"
#include "jobs/thumbjob.hpp"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
#include "mainwindow.h"
#include "mlt++/Mlt.h"
#include "mltcontroller/clipcontroller.h"
#include "mltcontroller/clippropertiescontroller.h"
#include "monitor/monitor.h"
#include "project/dialogs/slideshowclip.h"
#include "project/invaliddialog.h"
#include "project/projectcommands.h"
#include "project/projectmanager.h"
#include "projectclip.h"
#include "projectfolder.h"
#include "projectfolderup.h"
#include "projectitemmodel.h"
#include "projectsortproxymodel.h"
#include "projectsubclip.h"
#include "titler/titlewidget.h"
#include "ui_qtextclip_ui.h"
#include "undohelper.hpp"
#include "xml/xml.hpp"
#include "xml/xml.hpp"
#include
#include
#include
#include
#include "kdenlive_debug.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/**
* @class BinItemDelegate
* @brief This class is responsible for drawing items in the QTreeView.
*/
class BinItemDelegate : public QStyledItemDelegate
{
public:
explicit BinItemDelegate(QObject *parent = nullptr)
: QStyledItemDelegate(parent)
{
connect(this, &QStyledItemDelegate::closeEditor, [&]() { m_editorOpen = false; });
}
void setDar(double dar) { m_dar = dar; }
void setEditorData(QWidget *w, const QModelIndex &i) const override
{
if (!m_editorOpen) {
QStyledItemDelegate::setEditorData(w, i);
m_editorOpen = true;
}
}
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
{
Q_UNUSED(model);
Q_UNUSED(option);
Q_UNUSED(index);
if (event->type() == QEvent::MouseButtonPress) {
auto *me = (QMouseEvent *)event;
if (m_audioDragRect.contains(me->pos())) {
dragType = PlaylistState::AudioOnly;
} else if (m_videoDragRect.contains(me->pos())) {
dragType = PlaylistState::VideoOnly;
} else {
dragType = PlaylistState::Disabled;
}
}
event->ignore();
return false;
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
if (index.column() != 0) {
QStyledItemDelegate::updateEditorGeometry(editor, option, index);
return;
}
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
QRect r1 = option.rect;
int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
int decoWidth = 0;
if (opt.decorationSize.height() > 0) {
decoWidth += r1.height() * m_dar;
}
int mid = 0;
if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) {
mid = (int)((r1.height() / 2));
}
r1.adjust(decoWidth, 0, 0, -mid);
QFont ft = option.font;
ft.setBold(true);
QFontMetricsF fm(ft);
QRect r2 = fm.boundingRect(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString()).toRect();
editor->setGeometry(r2);
}
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
QSize hint = QStyledItemDelegate::sizeHint(option, index);
QString text = index.data(AbstractProjectItem::DataName).toString();
QRectF r = option.rect;
QFont ft = option.font;
ft.setBold(true);
QFontMetricsF fm(ft);
QStyle *style = option.widget ? option.widget->style() : QApplication::style();
const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
int width = fm.boundingRect(r, Qt::AlignLeft | Qt::AlignTop, text).width() + option.decorationSize.width() + 2 * textMargin;
hint.setWidth(width);
int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
if (type == AbstractProjectItem::FolderItem || type == AbstractProjectItem::FolderUpItem) {
return QSize(hint.width(), qMin(option.fontMetrics.lineSpacing() + 4, hint.height()));
}
if (type == AbstractProjectItem::ClipItem) {
return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMax(hint.height(), option.decorationSize.height())));
}
if (type == AbstractProjectItem::SubClipItem) {
return QSize(hint.width(), qMax(option.fontMetrics.lineSpacing() * 2 + 4, qMin(hint.height(), (int)(option.decorationSize.height() / 1.5))));
}
QIcon icon = qvariant_cast(index.data(Qt::DecorationRole));
QString line1 = index.data(Qt::DisplayRole).toString();
QString line2 = index.data(Qt::UserRole).toString();
int textW = qMax(option.fontMetrics.width(line1), option.fontMetrics.width(line2));
QSize iconSize = icon.actualSize(option.decorationSize);
return {qMax(textW, iconSize.width()) + 4, option.fontMetrics.lineSpacing() * 2 + 4};
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
{
if (index.column() == 0 && !index.data().isNull()) {
QRect r1 = option.rect;
painter->save();
painter->setClipRect(r1);
QStyleOptionViewItem opt(option);
initStyleOption(&opt, index);
int type = index.data(AbstractProjectItem::ItemTypeRole).toInt();
QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
// QRect r = QStyle::alignedRect(opt.direction, Qt::AlignVCenter | Qt::AlignLeft, opt.decorationSize, r1);
style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
if ((option.state & static_cast(QStyle::State_Selected)) != 0) {
painter->setPen(option.palette.highlightedText().color());
} else {
painter->setPen(option.palette.text().color());
}
QRect r = r1;
QFont font = painter->font();
font.setBold(true);
painter->setFont(font);
if (type == AbstractProjectItem::ClipItem || type == AbstractProjectItem::SubClipItem) {
int decoWidth = 0;
if (opt.decorationSize.height() > 0) {
r.setWidth(r.height() * m_dar);
QPixmap pix = opt.icon.pixmap(opt.icon.actualSize(r.size()));
// Draw icon
decoWidth += r.width() + textMargin;
r.setWidth(r.height() * pix.width() / pix.height());
painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height()));
}
int mid = (int)((r1.height() / 2));
r1.adjust(decoWidth, 0, 0, -mid);
QRect r2 = option.rect;
r2.adjust(decoWidth, mid, 0, 0);
QRectF bounding;
painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
font.setBold(false);
painter->setFont(font);
QString subText = index.data(AbstractProjectItem::DataDuration).toString();
if (!subText.isEmpty()) {
r2.adjust(0, bounding.bottom() - r2.top(), 0, 0);
QColor subTextColor = painter->pen().color();
subTextColor.setAlphaF(.5);
painter->setPen(subTextColor);
// Draw usage counter
int usage = index.data(AbstractProjectItem::UsageCount).toInt();
if (usage > 0) {
subText.append(QString().sprintf(" [%d]", usage));
}
painter->drawText(r2, Qt::AlignLeft | Qt::AlignTop, subText, &bounding);
// Add audio/video icons for selective drag
int cType = index.data(AbstractProjectItem::ClipType).toInt();
bool hasAudioAndVideo = index.data(AbstractProjectItem::ClipHasAudioAndVideo).toBool();
if (hasAudioAndVideo && (cType == ClipType::AV || cType == ClipType::Playlist) && (opt.state & QStyle::State_MouseOver)) {
bounding.moveLeft(bounding.right() + (2 * textMargin));
bounding.adjust(0, textMargin, 0, -textMargin);
QIcon aDrag = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
m_audioDragRect = bounding.toRect();
m_audioDragRect.setWidth(m_audioDragRect.height());
aDrag.paint(painter, m_audioDragRect, Qt::AlignLeft);
m_videoDragRect = m_audioDragRect;
m_videoDragRect.moveLeft(m_audioDragRect.right());
QIcon vDrag = QIcon::fromTheme(QStringLiteral("kdenlive-show-video"));
vDrag.paint(painter, m_videoDragRect, Qt::AlignLeft);
} else {
m_audioDragRect = QRect();
m_videoDragRect = QRect();
}
}
if (type == AbstractProjectItem::ClipItem) {
// Overlay icon if necessary
QVariant v = index.data(AbstractProjectItem::IconOverlay);
if (!v.isNull()) {
QIcon reload = QIcon::fromTheme(v.toString());
r.setTop(r.bottom() - bounding.height());
r.setWidth(bounding.height());
reload.paint(painter, r);
}
int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt();
auto status = index.data(AbstractProjectItem::JobStatus).value();
if (status == JobManagerStatus::Pending || status == JobManagerStatus::Running) {
// Draw job progress bar
int progressWidth = option.fontMetrics.averageCharWidth() * 8;
int progressHeight = option.fontMetrics.ascent() / 4;
QRect progress(r1.x() + 1, opt.rect.bottom() - progressHeight - 2, progressWidth, progressHeight);
painter->setPen(Qt::NoPen);
painter->setBrush(Qt::darkGray);
if (status == JobManagerStatus::Running) {
painter->drawRoundedRect(progress, 2, 2);
painter->setBrush((option.state & static_cast((QStyle::State_Selected) != 0)) != 0 ? option.palette.text()
: option.palette.highlight());
progress.setWidth((progressWidth - 2) * jobProgress / 100);
painter->drawRoundedRect(progress, 2, 2);
} else {
// Draw kind of a pause icon
progress.setWidth(3);
painter->drawRect(progress);
progress.moveLeft(progress.right() + 3);
painter->drawRect(progress);
}
}
bool jobsucceeded = index.data(AbstractProjectItem::JobSuccess).toBool();
if (!jobsucceeded) {
QIcon warning = QIcon::fromTheme(QStringLiteral("process-stop"));
warning.paint(painter, r2);
}
}
} else {
// Folder or Folder Up items
int decoWidth = 0;
if (opt.decorationSize.height() > 0) {
r.setWidth(r.height() * m_dar);
QPixmap pix = opt.icon.pixmap(opt.icon.actualSize(r.size()));
// Draw icon
decoWidth += r.width() + textMargin;
r.setWidth(r.height() * pix.width() / pix.height());
painter->drawPixmap(r, pix, QRect(0, 0, pix.width(), pix.height()));
}
r1.adjust(decoWidth, 0, 0, 0);
QRectF bounding;
painter->drawText(r1, Qt::AlignLeft | Qt::AlignTop, index.data(AbstractProjectItem::DataName).toString(), &bounding);
}
painter->restore();
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
private:
mutable bool m_editorOpen{false};
mutable QRect m_audioDragRect;
mutable QRect m_videoDragRect;
double m_dar{1.778};
public:
PlaylistState::ClipState dragType{PlaylistState::Disabled};
};
MyListView::MyListView(QWidget *parent)
: QListView(parent)
{
setViewMode(QListView::IconMode);
setMovement(QListView::Static);
setResizeMode(QListView::Adjust);
setUniformItemSizes(true);
setDragDropMode(QAbstractItemView::DragDrop);
setAcceptDrops(true);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
}
void MyListView::focusInEvent(QFocusEvent *event)
{
QListView::focusInEvent(event);
if (event->reason() == Qt::MouseFocusReason) {
emit focusView();
}
}
MyTreeView::MyTreeView(QWidget *parent)
: QTreeView(parent)
{
setEditing(false);
}
void MyTreeView::mousePressEvent(QMouseEvent *event)
{
QTreeView::mousePressEvent(event);
if (event->button() == Qt::LeftButton) {
m_startPos = event->pos();
QModelIndex ix = indexAt(m_startPos);
if (ix.isValid()) {
QAbstractItemDelegate *del = itemDelegate(ix);
m_dragType = static_cast(del)->dragType;
} else {
m_dragType = PlaylistState::Disabled;
}
}
}
void MyTreeView::focusInEvent(QFocusEvent *event)
{
QTreeView::focusInEvent(event);
if (event->reason() == Qt::MouseFocusReason) {
emit focusView();
}
}
void MyTreeView::mouseMoveEvent(QMouseEvent *event)
{
bool dragged = false;
if ((event->buttons() & Qt::LeftButton) != 0u) {
int distance = (event->pos() - m_startPos).manhattanLength();
if (distance >= QApplication::startDragDistance()) {
dragged = performDrag();
}
}
if (!dragged) {
QTreeView::mouseMoveEvent(event);
}
}
void MyTreeView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
{
QAbstractItemView::closeEditor(editor, hint);
setEditing(false);
}
void MyTreeView::editorDestroyed(QObject *editor)
{
QAbstractItemView::editorDestroyed(editor);
setEditing(false);
}
bool MyTreeView::isEditing() const
{
return state() == QAbstractItemView::EditingState;
}
void MyTreeView::setEditing(bool edit)
{
setState(edit ? QAbstractItemView::EditingState : QAbstractItemView::NoState);
}
bool MyTreeView::performDrag()
{
QModelIndexList bases = selectedIndexes();
QModelIndexList indexes;
for (int i = 0; i < bases.count(); i++) {
if (bases.at(i).column() == 0) {
indexes << bases.at(i);
}
}
if (indexes.isEmpty()) {
return false;
}
// Check if we want audio or video only
emit updateDragMode(m_dragType);
auto *drag = new QDrag(this);
drag->setMimeData(model()->mimeData(indexes));
QModelIndex ix = indexes.constFirst();
if (ix.isValid()) {
QIcon icon = ix.data(AbstractProjectItem::DataThumbnail).value();
QPixmap pix = icon.pixmap(iconSize());
QSize size = pix.size();
QImage image(size, QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter p(&image);
p.setOpacity(0.7);
p.drawPixmap(0, 0, pix);
p.setOpacity(1);
if (indexes.count() > 1) {
QPalette palette;
int radius = size.height() / 3;
p.setBrush(palette.highlight());
p.setPen(palette.highlightedText().color());
p.drawEllipse(QPoint(size.width() / 2, size.height() / 2), radius, radius);
p.drawText(size.width() / 2 - radius, size.height() / 2 - radius, 2 * radius, 2 * radius, Qt::AlignCenter, QString::number(indexes.count()));
}
p.end();
drag->setPixmap(QPixmap::fromImage(image));
}
drag->exec();
return true;
}
SmallJobLabel::SmallJobLabel(QWidget *parent)
: QPushButton(parent)
{
setFixedWidth(0);
setFlat(true);
m_timeLine = new QTimeLine(500, this);
QObject::connect(m_timeLine, &QTimeLine::valueChanged, this, &SmallJobLabel::slotTimeLineChanged);
QObject::connect(m_timeLine, &QTimeLine::finished, this, &SmallJobLabel::slotTimeLineFinished);
hide();
}
const QString SmallJobLabel::getStyleSheet(const QPalette &p)
{
KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window);
QColor bg = scheme.background(KColorScheme::LinkBackground).color();
QColor fg = scheme.foreground(KColorScheme::LinkText).color();
QString style =
QStringLiteral("QPushButton {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}")
.arg(bg.red())
.arg(bg.green())
.arg(bg.blue())
.arg(fg.red())
.arg(fg.green())
.arg(fg.blue());
bg = scheme.background(KColorScheme::ActiveBackground).color();
fg = scheme.foreground(KColorScheme::ActiveText).color();
style.append(
QStringLiteral("\nQPushButton:hover {margin:3px;padding:2px;background-color: rgb(%1, %2, %3);border-radius: 4px;border: none;color: rgb(%4, %5, %6)}")
.arg(bg.red())
.arg(bg.green())
.arg(bg.blue())
.arg(fg.red())
.arg(fg.green())
.arg(fg.blue()));
return style;
}
void SmallJobLabel::setAction(QAction *action)
{
m_action = action;
}
void SmallJobLabel::slotTimeLineChanged(qreal value)
{
setFixedWidth(qMin(value * 2, qreal(1.0)) * sizeHint().width());
update();
}
void SmallJobLabel::slotTimeLineFinished()
{
if (m_timeLine->direction() == QTimeLine::Forward) {
// Show
m_action->setVisible(true);
} else {
// Hide
m_action->setVisible(false);
setText(QString());
}
}
void SmallJobLabel::slotSetJobCount(int jobCount)
{
- QMutexLocker lk(&locker);
+ QMutexLocker lk(&m_locker);
if (jobCount > 0) {
// prepare animation
setText(i18np("%1 job", "%1 jobs", jobCount));
setToolTip(i18np("%1 pending job", "%1 pending jobs", jobCount));
if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) {
setFixedWidth(sizeHint().width());
m_action->setVisible(true);
return;
}
if (m_action->isVisible()) {
setFixedWidth(sizeHint().width());
update();
return;
}
setFixedWidth(0);
m_action->setVisible(true);
int wantedWidth = sizeHint().width();
setGeometry(-wantedWidth, 0, wantedWidth, height());
m_timeLine->setDirection(QTimeLine::Forward);
if (m_timeLine->state() == QTimeLine::NotRunning) {
m_timeLine->start();
}
} else {
if (style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) != 0) {
setFixedWidth(0);
m_action->setVisible(false);
return;
}
// hide
m_timeLine->setDirection(QTimeLine::Backward);
if (m_timeLine->state() == QTimeLine::NotRunning) {
m_timeLine->start();
}
}
}
LineEventEater::LineEventEater(QObject *parent)
: QObject(parent)
{
}
bool LineEventEater::eventFilter(QObject *obj, QEvent *event)
{
switch (event->type()) {
case QEvent::ShortcutOverride:
if (((QKeyEvent *)event)->key() == Qt::Key_Escape) {
emit clearSearchLine();
}
break;
case QEvent::Resize:
// Workaround Qt BUG 54676
emit showClearButton(((QResizeEvent *)event)->size().width() > QFontMetrics(QApplication::font()).averageCharWidth() * 8);
break;
default:
break;
}
return QObject::eventFilter(obj, event);
}
Bin::Bin(std::shared_ptr model, QWidget *parent)
: QWidget(parent)
, isLoading(false)
, m_itemModel(std::move(model))
, m_itemView(nullptr)
, m_doc(nullptr)
, m_extractAudioAction(nullptr)
, m_transcodeAction(nullptr)
, m_clipsActionsMenu(nullptr)
, m_inTimelineAction(nullptr)
, m_listType((BinViewType)KdenliveSettings::binMode())
, m_iconSize(160, 90)
, m_propertiesPanel(nullptr)
, m_blankThumb()
, m_invalidClipDialog(nullptr)
, m_gainedFocus(false)
, m_audioDuration(0)
, m_processedAudio(0)
{
m_layout = new QVBoxLayout(this);
// Create toolbar for buttons
m_toolbar = new QToolBar(this);
int size = style()->pixelMetric(QStyle::PM_SmallIconSize);
QSize iconSize(size, size);
m_toolbar->setIconSize(iconSize);
m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
m_layout->addWidget(m_toolbar);
m_layout->setSpacing(0);
m_layout->setContentsMargins(0, 0, 0, 0);
// Search line
m_proxyModel = new ProjectSortProxyModel(this);
m_proxyModel->setDynamicSortFilter(true);
m_searchLine = new QLineEdit(this);
m_searchLine->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
// m_searchLine->setClearButtonEnabled(true);
m_searchLine->setPlaceholderText(i18n("Search"));
m_searchLine->setFocusPolicy(Qt::ClickFocus);
connect(m_searchLine, &QLineEdit::textChanged, m_proxyModel, &ProjectSortProxyModel::slotSetSearchString);
auto *leventEater = new LineEventEater(this);
m_searchLine->installEventFilter(leventEater);
connect(leventEater, &LineEventEater::clearSearchLine, m_searchLine, &QLineEdit::clear);
connect(leventEater, &LineEventEater::showClearButton, this, &Bin::showClearButton);
setFocusPolicy(Qt::ClickFocus);
connect(m_itemModel.get(), &ProjectItemModel::refreshPanel, this, &Bin::refreshPanel);
connect(m_itemModel.get(), &ProjectItemModel::refreshAudioThumbs, this, &Bin::doRefreshAudioThumbs);
connect(m_itemModel.get(), &ProjectItemModel::refreshClip, this, &Bin::refreshClip);
connect(m_itemModel.get(), &ProjectItemModel::updateTimelineProducers, this, &Bin::updateTimelineProducers);
connect(m_itemModel.get(), &ProjectItemModel::emitMessage, this, &Bin::emitMessage);
// Connect models
m_proxyModel->setSourceModel(m_itemModel.get());
connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, m_proxyModel, &ProjectSortProxyModel::slotDataChanged);
connect(m_proxyModel, &ProjectSortProxyModel::selectModel, this, &Bin::selectProxyModel);
connect(m_itemModel.get(), static_cast(&ProjectItemModel::itemDropped), this,
static_cast(&Bin::slotItemDropped));
connect(m_itemModel.get(), static_cast &, const QModelIndex &)>(&ProjectItemModel::itemDropped), this,
static_cast &, const QModelIndex &)>(&Bin::slotItemDropped));
connect(m_itemModel.get(), &ProjectItemModel::effectDropped, this, &Bin::slotEffectDropped);
connect(m_itemModel.get(), &QAbstractItemModel::dataChanged, this, &Bin::slotItemEdited);
connect(this, &Bin::refreshPanel, this, &Bin::doRefreshPanel);
// Zoom slider
QWidget *container = new QWidget(this);
auto *lay = new QHBoxLayout;
m_slider = new QSlider(Qt::Horizontal, this);
m_slider->setMaximumWidth(100);
m_slider->setMinimumWidth(40);
m_slider->setRange(0, 10);
m_slider->setValue(KdenliveSettings::bin_zoom());
connect(m_slider, &QAbstractSlider::valueChanged, this, &Bin::slotSetIconSize);
auto *tb1 = new QToolButton(this);
tb1->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
connect(tb1, &QToolButton::clicked, [&]() { m_slider->setValue(qMin(m_slider->value() + 1, m_slider->maximum())); });
auto *tb2 = new QToolButton(this);
tb2->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out")));
connect(tb2, &QToolButton::clicked, [&]() { m_slider->setValue(qMax(m_slider->value() - 1, m_slider->minimum())); });
lay->addWidget(tb2);
lay->addWidget(m_slider);
lay->addWidget(tb1);
container->setLayout(lay);
auto *widgetslider = new QWidgetAction(this);
widgetslider->setDefaultWidget(container);
// View type
KSelectAction *listType = new KSelectAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("View Mode"), this);
pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode"), listType);
QAction *treeViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-tree")), i18n("Tree View"));
listType->addAction(treeViewAction);
treeViewAction->setData(BinTreeView);
if (m_listType == treeViewAction->data().toInt()) {
listType->setCurrentAction(treeViewAction);
}
pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_tree"), treeViewAction);
QAction *iconViewAction = listType->addAction(QIcon::fromTheme(QStringLiteral("view-list-icons")), i18n("Icon View"));
iconViewAction->setData(BinIconView);
if (m_listType == iconViewAction->data().toInt()) {
listType->setCurrentAction(iconViewAction);
}
pCore->window()->actionCollection()->addAction(QStringLiteral("bin_view_mode_icon"), iconViewAction);
QAction *disableEffects = new QAction(i18n("Disable Bin Effects"), this);
connect(disableEffects, &QAction::triggered, [this](bool disable) { this->setBinEffectsEnabled(!disable); });
disableEffects->setIcon(QIcon::fromTheme(QStringLiteral("favorite")));
disableEffects->setData("disable_bin_effects");
disableEffects->setCheckable(true);
disableEffects->setChecked(false);
pCore->window()->actionCollection()->addAction(QStringLiteral("disable_bin_effects"), disableEffects);
m_renameAction = KStandardAction::renameFile(this, SLOT(slotRenameItem()), this);
m_renameAction->setText(i18n("Rename"));
m_renameAction->setData("rename");
pCore->window()->actionCollection()->addAction(QStringLiteral("rename"), m_renameAction);
listType->setToolBarMode(KSelectAction::MenuMode);
connect(listType, static_cast(&KSelectAction::triggered), this, &Bin::slotInitView);
// Settings menu
QMenu *settingsMenu = new QMenu(i18n("Settings"), this);
settingsMenu->addAction(listType);
QMenu *sliderMenu = new QMenu(i18n("Zoom"), this);
sliderMenu->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in")));
sliderMenu->addAction(widgetslider);
settingsMenu->addMenu(sliderMenu);
// Column show / hide actions
m_showDate = new QAction(i18n("Show date"), this);
m_showDate->setCheckable(true);
connect(m_showDate, &QAction::triggered, this, &Bin::slotShowDateColumn);
m_showDesc = new QAction(i18n("Show description"), this);
m_showDesc->setCheckable(true);
connect(m_showDesc, &QAction::triggered, this, &Bin::slotShowDescColumn);
settingsMenu->addAction(m_showDate);
settingsMenu->addAction(m_showDesc);
settingsMenu->addAction(disableEffects);
auto *button = new QToolButton;
button->setIcon(QIcon::fromTheme(QStringLiteral("kdenlive-menu")));
button->setToolTip(i18n("Options"));
button->setMenu(settingsMenu);
button->setPopupMode(QToolButton::InstantPopup);
m_toolbar->addWidget(button);
// small info button for pending jobs
m_infoLabel = new SmallJobLabel(this);
m_infoLabel->setStyleSheet(SmallJobLabel::getStyleSheet(palette()));
connect(pCore->jobManager().get(), &JobManager::jobCount, m_infoLabel, &SmallJobLabel::slotSetJobCount);
QAction *infoAction = m_toolbar->addWidget(m_infoLabel);
m_jobsMenu = new QMenu(this);
// connect(m_jobsMenu, &QMenu::aboutToShow, this, &Bin::slotPrepareJobsMenu);
m_cancelJobs = new QAction(i18n("Cancel All Jobs"), this);
m_cancelJobs->setCheckable(false);
m_discardCurrentClipJobs = new QAction(i18n("Cancel Current Clip Jobs"), this);
m_discardCurrentClipJobs->setCheckable(false);
m_discardPendingJobs = new QAction(i18n("Cancel Pending Jobs"), this);
m_discardPendingJobs->setCheckable(false);
m_jobsMenu->addAction(m_cancelJobs);
m_jobsMenu->addAction(m_discardCurrentClipJobs);
m_jobsMenu->addAction(m_discardPendingJobs);
m_infoLabel->setMenu(m_jobsMenu);
m_infoLabel->setAction(infoAction);
// Hack, create toolbar spacer
QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
m_toolbar->addWidget(spacer);
// Add search line
m_toolbar->addWidget(m_searchLine);
m_binTreeViewDelegate = new BinItemDelegate(this);
// connect(pCore->projectManager(), SIGNAL(projectOpened(Project*)), this, SLOT(setProject(Project*)));
m_headerInfo = QByteArray::fromBase64(KdenliveSettings::treeviewheaders().toLatin1());
m_propertiesPanel = new QScrollArea(this);
m_propertiesPanel->setFrameShape(QFrame::NoFrame);
// Insert listview
m_itemView = new MyTreeView(this);
m_layout->addWidget(m_itemView);
// Info widget for failed jobs, other errors
m_infoMessage = new KMessageWidget(this);
m_layout->addWidget(m_infoMessage);
m_infoMessage->setCloseButtonVisible(false);
connect(m_infoMessage, &KMessageWidget::hideAnimationFinished, this, &Bin::slotResetInfoMessage);
// m_infoMessage->setWordWrap(true);
m_infoMessage->hide();
connect(this, &Bin::requesteInvalidRemoval, this, &Bin::slotQueryRemoval);
connect(this, SIGNAL(displayBinMessage(QString, KMessageWidget::MessageType)), this, SLOT(doDisplayMessage(QString, KMessageWidget::MessageType)));
}
Bin::~Bin()
{
blockSignals(true);
m_proxyModel->selectionModel()->blockSignals(true);
setEnabled(false);
m_propertiesPanel = nullptr;
abortOperations();
m_itemModel->clean();
}
QDockWidget *Bin::clipPropertiesDock()
{
return m_propertiesDock;
}
void Bin::abortOperations()
{
m_infoMessage->hide();
blockSignals(true);
if (m_propertiesPanel) {
for (QWidget *w : m_propertiesPanel->findChildren()) {
delete w;
}
}
delete m_itemView;
m_itemView = nullptr;
blockSignals(false);
}
bool Bin::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease) {
if (!m_monitor->isActive()) {
m_monitor->slotActivateMonitor();
}
bool success = QWidget::eventFilter(obj, event);
if (m_gainedFocus) {
auto *mouseEvent = static_cast(event);
auto *view = qobject_cast(obj->parent());
if (view) {
QModelIndex idx = view->indexAt(mouseEvent->pos());
m_gainedFocus = false;
if (idx.isValid()) {
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx));
editMasterEffect(item);
} else {
editMasterEffect(nullptr);
}
}
// make sure we discard the focus indicator
m_gainedFocus = false;
}
return success;
}
if (event->type() == QEvent::MouseButtonDblClick) {
auto *mouseEvent = static_cast(event);
auto *view = qobject_cast(obj->parent());
if (view) {
QModelIndex idx = view->indexAt(mouseEvent->pos());
if (!idx.isValid()) {
// User double clicked on empty area
slotAddClip();
} else {
slotItemDoubleClicked(idx, mouseEvent->pos());
}
} else {
qCDebug(KDENLIVE_LOG) << " +++++++ NO VIEW-------!!";
}
return true;
}
if (event->type() == QEvent::Wheel) {
auto *e = static_cast(event);
if ((e != nullptr) && e->modifiers() == Qt::ControlModifier) {
slotZoomView(e->delta() > 0);
// emit zoomView(e->delta() > 0);
return true;
}
}
return QWidget::eventFilter(obj, event);
}
void Bin::refreshIcons()
{
QList allMenus = this->findChildren();
for (int i = 0; i < allMenus.count(); i++) {
QMenu *m = allMenus.at(i);
QIcon ic = m->icon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
QIcon newIcon = QIcon::fromTheme(ic.name());
m->setIcon(newIcon);
}
QList allButtons = this->findChildren();
for (int i = 0; i < allButtons.count(); i++) {
QToolButton *m = allButtons.at(i);
QIcon ic = m->icon();
if (ic.isNull() || ic.name().isEmpty()) {
continue;
}
QIcon newIcon = QIcon::fromTheme(ic.name());
m->setIcon(newIcon);
}
}
void Bin::slotSaveHeaders()
{
if ((m_itemView != nullptr) && m_listType == BinTreeView) {
// save current treeview state (column width)
auto *view = static_cast(m_itemView);
m_headerInfo = view->header()->saveState();
KdenliveSettings::setTreeviewheaders(m_headerInfo.toBase64());
}
}
void Bin::slotZoomView(bool zoomIn)
{
if (m_itemModel->rowCount() == 0) {
// Don't zoom on empty bin
return;
}
int progress = (zoomIn) ? 1 : -1;
m_slider->setValue(m_slider->value() + progress);
}
Monitor *Bin::monitor()
{
return m_monitor;
}
const QStringList Bin::getFolderInfo(const QModelIndex &selectedIx)
{
QModelIndexList indexes;
if (selectedIx.isValid()) {
indexes << selectedIx;
} else {
indexes = m_proxyModel->selectionModel()->selectedIndexes();
}
if (indexes.isEmpty()) {
// return root folder info
QStringList folderInfo;
folderInfo << QString::number(-1);
folderInfo << QString();
return folderInfo;
}
QModelIndex ix = indexes.constFirst();
if (ix.isValid() && (m_proxyModel->selectionModel()->isSelected(ix) || selectedIx.isValid())) {
return m_itemModel->getEnclosingFolderInfo(m_proxyModel->mapToSource(ix));
}
// return root folder info
QStringList folderInfo;
folderInfo << QString::number(-1);
folderInfo << QString();
return folderInfo;
}
void Bin::slotAddClip()
{
// Check if we are in a folder
QString parentFolder = getCurrentFolder();
ClipCreationDialog::createClipsCommand(m_doc, parentFolder, m_itemModel);
}
std::shared_ptr Bin::getFirstSelectedClip()
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
if (indexes.isEmpty()) {
return std::shared_ptr();
}
for (const QModelIndex &ix : indexes) {
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
if (item->itemType() == AbstractProjectItem::ClipItem) {
auto clip = std::static_pointer_cast(item);
if (clip) {
return clip;
}
}
}
return nullptr;
}
void Bin::slotDeleteClip()
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
std::vector> items;
bool included = false;
bool usedFolder = false;
auto checkInclusion = [](bool accum, std::shared_ptr item) {
return accum || std::static_pointer_cast(item)->isIncludedInTimeline();
};
for (const QModelIndex &ix : indexes) {
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
if (!item) {
qDebug() << "Suspicious: item not found when trying to delete";
continue;
}
included = included || item->accumulate(false, checkInclusion);
// Check if we are deleting non-empty folders:
usedFolder = usedFolder || item->childCount() > 0;
items.push_back(item);
}
if (included && (KMessageBox::warningContinueCancel(this, i18n("This will delete all selected clips from timeline")) != KMessageBox::Continue)) {
return;
}
if (usedFolder && (KMessageBox::warningContinueCancel(this, i18n("This will delete all folder content")) != KMessageBox::Continue)) {
return;
}
Fun undo = []() { return true; };
Fun redo = []() { return true; };
for (const auto &item : items) {
m_itemModel->requestBinClipDeletion(item, undo, redo);
}
pCore->pushUndo(undo, redo, i18n("Delete bin Clips"));
}
void Bin::slotReloadClip()
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
for (const QModelIndex &ix : indexes) {
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
std::shared_ptr currentItem = nullptr;
if (item->itemType() == AbstractProjectItem::ClipItem) {
currentItem = std::static_pointer_cast(item);
} else if (item->itemType() == AbstractProjectItem::SubClipItem) {
currentItem = std::static_pointer_cast(item)->getMasterClip();
}
if (currentItem) {
emit openClip(std::shared_ptr());
if (currentItem->clipType() == ClipType::Playlist) {
// Check if a clip inside playlist is missing
QString path = currentItem->url();
QFile f(path);
QDomDocument doc;
doc.setContent(&f, false);
f.close();
DocumentChecker d(QUrl::fromLocalFile(path), doc);
if (!d.hasErrorInClips() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) {
QString backupFile = path + QStringLiteral(".backup");
KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile));
if (copyjob->exec()) {
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::sorry(this, i18n("Unable to write to file %1", path));
} else {
QTextStream out(&f);
out << doc.toString();
f.close();
KMessageBox::information(
this,
i18n("Your project file was modified by Kdenlive.\nTo make sure you do not lose data, a backup copy called %1 was created.",
backupFile));
}
}
}
}
currentItem->reloadProducer(false);
}
}
}
void Bin::slotLocateClip()
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
for (const QModelIndex &ix : indexes) {
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
std::shared_ptr currentItem = nullptr;
if (item->itemType() == AbstractProjectItem::ClipItem) {
currentItem = std::static_pointer_cast(item);
} else if (item->itemType() == AbstractProjectItem::SubClipItem) {
currentItem = std::static_pointer_cast(item)->getMasterClip();
}
if (currentItem) {
QUrl url = QUrl::fromLocalFile(currentItem->url()).adjusted(QUrl::RemoveFilename);
bool exists = QFile(url.toLocalFile()).exists();
if (currentItem->hasUrl() && exists) {
QDesktopServices::openUrl(url);
qCDebug(KDENLIVE_LOG) << " / / " + url.toString();
} else {
if (!exists) {
emitMessage(i18n("Could not locate %1", url.toString()), 100, ErrorMessage);
}
return;
}
}
}
}
void Bin::slotDuplicateClip()
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
for (const QModelIndex &ix : indexes) {
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
if (item->itemType() == AbstractProjectItem::ClipItem) {
auto currentItem = std::static_pointer_cast(item);
if (currentItem) {
QDomDocument doc;
QDomElement xml = currentItem->toXml(doc);
if (!xml.isNull()) {
QString currentName = Xml::getXmlProperty(xml, QStringLiteral("kdenlive:clipname"));
if (currentName.isEmpty()) {
QUrl url = QUrl::fromLocalFile(Xml::getXmlProperty(xml, QStringLiteral("resource")));
if (url.isValid()) {
currentName = url.fileName();
}
}
if (!currentName.isEmpty()) {
currentName.append(i18nc("append to clip name to indicate a copied idem", " (copy)"));
Xml::setXmlProperty(xml, QStringLiteral("kdenlive:clipname"), currentName);
}
QString id;
m_itemModel->requestAddBinClip(id, xml, item->parent()->clipId(), i18n("Duplicate clip"));
selectClipById(id);
}
}
} else if (item->itemType() == AbstractProjectItem::SubClipItem) {
auto currentItem = std::static_pointer_cast(item);
QString id;
QPoint clipZone = currentItem->zone();
m_itemModel->requestAddBinSubClip(id, clipZone.x(), clipZone.y(), QString(), currentItem->getMasterClip()->clipId());
selectClipById(id);
}
}
}
void Bin::setMonitor(Monitor *monitor)
{
m_monitor = monitor;
connect(m_monitor, &Monitor::addClipToProject, this, &Bin::slotAddClipToProject);
connect(m_monitor, &Monitor::refreshCurrentClip, this, &Bin::slotOpenCurrent);
connect(this, &Bin::openClip, [&](std::shared_ptr clip, int in, int out) { m_monitor->slotOpenClip(clip, in, out); });
}
void Bin::setDocument(KdenliveDoc *project)
{
blockSignals(true);
m_proxyModel->selectionModel()->blockSignals(true);
setEnabled(false);
// Cleanup previous project
m_itemModel->clean();
delete m_itemView;
m_itemView = nullptr;
m_doc = project;
int iconHeight = QFontInfo(font()).pixelSize() * 3.5;
m_iconSize = QSize(iconHeight * pCore->getCurrentDar(), iconHeight);
setEnabled(true);
blockSignals(false);
m_proxyModel->selectionModel()->blockSignals(false);
connect(m_proxyAction, SIGNAL(toggled(bool)), m_doc, SLOT(slotProxyCurrentItem(bool)));
// connect(m_itemModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)), m_itemView
// connect(m_itemModel, SIGNAL(updateCurrentItem()), this, SLOT(autoSelect()));
slotInitView(nullptr);
bool binEffectsDisabled = getDocumentProperty(QStringLiteral("disablebineffects")).toInt() == 1;
setBinEffectsEnabled(!binEffectsDisabled);
}
void Bin::createClip(const QDomElement &xml)
{
// Check if clip should be in a folder
QString groupId = ProjectClip::getXmlProperty(xml, QStringLiteral("kdenlive:folderid"));
std::shared_ptr parentFolder = m_itemModel->getFolderByBinId(groupId);
if (!parentFolder) {
parentFolder = m_itemModel->getRootFolder();
}
QString path = Xml::getXmlProperty(xml, QStringLiteral("resource"));
if (path.endsWith(QStringLiteral(".mlt")) || path.endsWith(QStringLiteral(".kdenlive"))) {
QFile f(path);
QDomDocument doc;
doc.setContent(&f, false);
f.close();
DocumentChecker d(QUrl::fromLocalFile(path), doc);
if (!d.hasErrorInClips() && doc.documentElement().hasAttribute(QStringLiteral("modified"))) {
QString backupFile = path + QStringLiteral(".backup");
KIO::FileCopyJob *copyjob = KIO::file_copy(QUrl::fromLocalFile(path), QUrl::fromLocalFile(backupFile));
if (copyjob->exec()) {
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
KMessageBox::sorry(this, i18n("Unable to write to file %1", path));
} else {
QTextStream out(&f);
out << doc.toString();
f.close();
KMessageBox::information(
this, i18n("Your project file was modified by Kdenlive.\nTo make sure you do not lose data, a backup copy called %1 was created.",
backupFile));
}
}
}
}
QString id = Xml::getTagContentByAttribute(xml, QStringLiteral("property"), QStringLiteral("name"), QStringLiteral("kdenlive:id"));
if (id.isEmpty()) {
id = QString::number(m_itemModel->getFreeClipId());
}
auto newClip = ProjectClip::construct(id, xml, m_blankThumb, m_itemModel);
parentFolder->appendChild(newClip);
}
QString Bin::slotAddFolder(const QString &folderName)
{
auto parentFolder = m_itemModel->getFolderByBinId(getCurrentFolder());
qDebug() << "pranteforder id" << parentFolder->clipId();
QString newId;
Fun undo = []() { return true; };
Fun redo = []() { return true; };
m_itemModel->requestAddFolder(newId, folderName.isEmpty() ? i18n("Folder") : folderName, parentFolder->clipId(), undo, redo);
pCore->pushUndo(undo, redo, i18n("Create bin folder"));
// Edit folder name
if (!folderName.isEmpty()) {
// We already have a name, no need to edit
return newId;
}
auto folder = m_itemModel->getFolderByBinId(newId);
auto ix = m_itemModel->getIndexFromItem(folder);
qDebug() << "selecting" << ix;
if (ix.isValid()) {
qDebug() << "ix valid";
m_proxyModel->selectionModel()->clearSelection();
int row = ix.row();
const QModelIndex id = m_itemModel->index(row, 0, ix.parent());
const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent());
if (id.isValid() && id2.isValid()) {
m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)),
QItemSelectionModel::Select);
}
m_itemView->edit(m_proxyModel->mapFromSource(ix));
}
return newId;
}
QModelIndex Bin::getIndexForId(const QString &id, bool folderWanted) const
{
QModelIndexList items = m_itemModel->match(m_itemModel->index(0, 0), AbstractProjectItem::DataId, QVariant::fromValue(id), 2, Qt::MatchRecursive);
for (int i = 0; i < items.count(); i++) {
auto *currentItem = static_cast(items.at(i).internalPointer());
AbstractProjectItem::PROJECTITEMTYPE type = currentItem->itemType();
if (folderWanted && type == AbstractProjectItem::FolderItem) {
// We found our folder
return items.at(i);
}
if (!folderWanted && type == AbstractProjectItem::ClipItem) {
// We found our clip
return items.at(i);
}
}
return {};
}
void Bin::selectClipById(const QString &clipId, int frame, const QPoint &zone)
{
if (m_monitor->activeClipId() == clipId) {
if (frame > -1) {
m_monitor->slotSeek(frame);
}
if (!zone.isNull()) {
m_monitor->slotLoadClipZone(zone);
}
return;
}
m_proxyModel->selectionModel()->clearSelection();
std::shared_ptr clip = getBinClip(clipId);
if (clip) {
selectClip(clip);
if (frame > -1) {
m_monitor->slotSeek(frame);
}
if (!zone.isNull()) {
m_monitor->slotLoadClipZone(zone);
}
}
}
void Bin::selectProxyModel(const QModelIndex &id)
{
if (isLoading) {
// return;
}
if (id.isValid()) {
if (id.column() != 0) {
return;
}
std::shared_ptr currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(id));
if (currentItem) {
// Set item as current so that it displays its content in clip monitor
setCurrent(currentItem);
if (currentItem->itemType() == AbstractProjectItem::ClipItem) {
m_reloadAction->setEnabled(true);
m_locateAction->setEnabled(true);
m_duplicateAction->setEnabled(true);
std::shared_ptr clip = std::static_pointer_cast(currentItem);
ClipType::ProducerType type = clip->clipType();
m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::Text || type == ClipType::TextTemplate);
showClipProperties(clip, false);
m_deleteAction->setText(i18n("Delete Clip"));
m_proxyAction->setText(i18n("Proxy Clip"));
emit findInTimeline(clip->clipId(), clip->timelineInstances());
} else if (currentItem->itemType() == AbstractProjectItem::FolderItem) {
// A folder was selected, disable editing clip
m_openAction->setEnabled(false);
m_reloadAction->setEnabled(false);
m_locateAction->setEnabled(false);
m_duplicateAction->setEnabled(false);
m_deleteAction->setText(i18n("Delete Folder"));
m_proxyAction->setText(i18n("Proxy Folder"));
} else if (currentItem->itemType() == AbstractProjectItem::SubClipItem) {
showClipProperties(std::static_pointer_cast(currentItem->parent()), false);
m_openAction->setEnabled(false);
m_reloadAction->setEnabled(false);
m_locateAction->setEnabled(false);
m_duplicateAction->setEnabled(false);
m_deleteAction->setText(i18n("Delete Clip"));
m_proxyAction->setText(i18n("Proxy Clip"));
}
m_deleteAction->setEnabled(true);
} else {
emit findInTimeline(QString());
m_reloadAction->setEnabled(false);
m_locateAction->setEnabled(false);
m_duplicateAction->setEnabled(false);
m_openAction->setEnabled(false);
m_deleteAction->setEnabled(false);
}
} else {
// No item selected in bin
m_openAction->setEnabled(false);
m_deleteAction->setEnabled(false);
showClipProperties(nullptr);
emit findInTimeline(QString());
emit requestClipShow(nullptr);
// clear effect stack
emit requestShowEffectStack(QString(), nullptr, QSize(), false);
// Display black bg in clip monitor
emit openClip(std::shared_ptr());
}
}
std::vector Bin::selectedClipsIds(bool excludeFolders)
{
const QModelIndexList indexes = m_proxyModel->selectionModel()->selectedIndexes();
std::vector ids;
// We define the lambda that will be executed on each item of the subset of nodes of the tree that are selected
auto itemAdder = [excludeFolders, &ids](std::vector &ids_vec, std::shared_ptr item) {
auto binItem = std::static_pointer_cast(item);
if (!excludeFolders || (binItem->itemType() != AbstractProjectItem::FolderItem && binItem->itemType() != AbstractProjectItem::FolderUpItem)) {
ids.push_back(binItem->clipId());
}
return ids_vec;
};
for (const QModelIndex &ix : indexes) {
if (!ix.isValid() || ix.column() != 0) {
continue;
}
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
item->accumulate(ids, itemAdder);
}
return ids;
}
QList> Bin::selectedClips()
{
auto ids = selectedClipsIds(true);
QList> ret;
for (const auto &id : ids) {
ret.push_back(m_itemModel->getClipByBinID(id));
}
return ret;
}
void Bin::slotInitView(QAction *action)
{
if (action) {
m_proxyModel->selectionModel()->clearSelection();
int viewType = action->data().toInt();
KdenliveSettings::setBinMode(viewType);
if (viewType == m_listType) {
return;
}
if (m_listType == BinTreeView) {
// save current treeview state (column width)
auto *view = static_cast(m_itemView);
m_headerInfo = view->header()->saveState();
m_showDate->setEnabled(true);
m_showDesc->setEnabled(true);
} else {
// remove the current folderUp item if any
if (m_folderUp) {
if (m_folderUp->parent()) {
m_folderUp->parent()->removeChild(m_folderUp);
}
m_folderUp.reset();
}
}
m_listType = static_cast(viewType);
}
if (m_itemView) {
delete m_itemView;
}
switch (m_listType) {
case BinIconView:
m_itemView = new MyListView(this);
m_folderUp = ProjectFolderUp::construct(m_itemModel);
m_showDate->setEnabled(false);
m_showDesc->setEnabled(false);
break;
default:
m_itemView = new MyTreeView(this);
m_showDate->setEnabled(true);
m_showDesc->setEnabled(true);
break;
}
m_itemView->setMouseTracking(true);
m_itemView->viewport()->installEventFilter(this);
QSize zoom = m_iconSize * (m_slider->value() / 4.0);
m_itemView->setIconSize(zoom);
QPixmap pix(zoom);
pix.fill(Qt::lightGray);
m_blankThumb.addPixmap(pix);
m_binTreeViewDelegate->setDar(pCore->getCurrentDar());
m_itemView->setModel(m_proxyModel);
m_itemView->setSelectionModel(m_proxyModel->selectionModel());
m_layout->insertWidget(1, m_itemView);
// setup some default view specific parameters
if (m_listType == BinTreeView) {
m_itemView->setItemDelegate(m_binTreeViewDelegate);
auto *view = static_cast(m_itemView);
view->setSortingEnabled(true);
view->setWordWrap(true);
connect(m_proxyModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &Bin::slotSetSorting);
connect(view, &MyTreeView::updateDragMode, m_itemModel.get(), &ProjectItemModel::setDragType, Qt::DirectConnection);
m_proxyModel->setDynamicSortFilter(true);
if (!m_headerInfo.isEmpty()) {
view->header()->restoreState(m_headerInfo);
} else {
view->header()->resizeSections(QHeaderView::ResizeToContents);
view->resizeColumnToContents(0);
view->setColumnHidden(1, true);
view->setColumnHidden(2, true);
}
m_showDate->setChecked(!view->isColumnHidden(1));
m_showDesc->setChecked(!view->isColumnHidden(2));
connect(view->header(), &QHeaderView::sectionResized, this, &Bin::slotSaveHeaders);
connect(view->header(), &QHeaderView::sectionClicked, this, &Bin::slotSaveHeaders);
connect(view, &MyTreeView::focusView, this, &Bin::slotGotFocus);
} else if (m_listType == BinIconView) {
auto *view = static_cast(m_itemView);
connect(view, &MyListView::focusView, this, &Bin::slotGotFocus);
}
m_itemView->setEditTriggers(QAbstractItemView::NoEditTriggers); // DoubleClicked);
m_itemView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_itemView->setDragDropMode(QAbstractItemView::DragDrop);
m_itemView->setAlternatingRowColors(true);
m_itemView->setAcceptDrops(true);
m_itemView->setFocus();
}
void Bin::slotSetIconSize(int size)
{
if (!m_itemView) {
return;
}
KdenliveSettings::setBin_zoom(size);
QSize zoom = m_iconSize;
zoom = zoom * (size / 4.0);
m_itemView->setIconSize(zoom);
QPixmap pix(zoom);
pix.fill(Qt::lightGray);
m_blankThumb.addPixmap(pix);
}
void Bin::rebuildMenu()
{
m_transcodeAction = static_cast(pCore->window()->factory()->container(QStringLiteral("transcoders"), pCore->window()));
m_extractAudioAction = static_cast(pCore->window()->factory()->container(QStringLiteral("extract_audio"), pCore->window()));
m_clipsActionsMenu = static_cast(pCore->window()->factory()->container(QStringLiteral("clip_actions"), pCore->window()));
m_menu->insertMenu(m_reloadAction, m_extractAudioAction);
m_menu->insertMenu(m_reloadAction, m_transcodeAction);
m_menu->insertMenu(m_reloadAction, m_clipsActionsMenu);
m_inTimelineAction =
m_menu->insertMenu(m_reloadAction, static_cast(pCore->window()->factory()->container(QStringLiteral("clip_in_timeline"), pCore->window())));
}
void Bin::contextMenuEvent(QContextMenuEvent *event)
{
bool enableClipActions = false;
ClipType::ProducerType type = ClipType::Unknown;
bool isFolder = false;
bool isImported = false;
AbstractProjectItem::PROJECTITEMTYPE itemType = AbstractProjectItem::FolderItem;
QString clipService;
QString audioCodec;
if (m_itemView) {
QModelIndex idx = m_itemView->indexAt(m_itemView->viewport()->mapFromGlobal(event->globalPos()));
if (idx.isValid()) {
// User right clicked on a clip
std::shared_ptr currentItem = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(idx));
itemType = currentItem->itemType();
if (currentItem) {
enableClipActions = true;
if (itemType == AbstractProjectItem::ClipItem) {
auto clip = std::static_pointer_cast(currentItem);
if (clip) {
m_proxyAction->blockSignals(true);
emit findInTimeline(clip->clipId(), clip->timelineInstances());
clipService = clip->getProducerProperty(QStringLiteral("mlt_service"));
m_proxyAction->setChecked(clip->hasProxy());
QList transcodeActions;
if (m_transcodeAction) {
transcodeActions = m_transcodeAction->actions();
}
QStringList dataList;
QString condition;
audioCodec = clip->codec(true);
QString videoCodec = clip->codec(false);
type = clip->clipType();
if (clip->hasUrl()) {
isImported = true;
}
bool noCodecInfo = false;
if (audioCodec.isEmpty() && videoCodec.isEmpty()) {
noCodecInfo = true;
}
for (int i = 0; i < transcodeActions.count(); ++i) {
dataList = transcodeActions.at(i)->data().toStringList();
if (dataList.count() > 4) {
condition = dataList.at(4);
if (condition.isEmpty()) {
transcodeActions.at(i)->setEnabled(true);
continue;
}
if (noCodecInfo) {
// No audio / video codec, this is an MLT clip, disable conditionnal transcoding
transcodeActions.at(i)->setEnabled(false);
continue;
}
if (condition.startsWith(QLatin1String("vcodec"))) {
transcodeActions.at(i)->setEnabled(condition.section(QLatin1Char('='), 1, 1) == videoCodec);
} else if (condition.startsWith(QLatin1String("acodec"))) {
transcodeActions.at(i)->setEnabled(condition.section(QLatin1Char('='), 1, 1) == audioCodec);
}
}
}
}
m_proxyAction->blockSignals(false);
} else if (itemType == AbstractProjectItem::SubClipItem) {
}
}
}
}
// Enable / disable clip actions
m_proxyAction->setEnabled((m_doc->getDocumentProperty(QStringLiteral("enableproxy")).toInt() != 0) && enableClipActions);
m_openAction->setEnabled(type == ClipType::Image || type == ClipType::Audio || type == ClipType::TextTemplate || type == ClipType::Text);
m_reloadAction->setEnabled(enableClipActions);
m_locateAction->setEnabled(enableClipActions);
m_duplicateAction->setEnabled(enableClipActions);
m_editAction->setVisible(!isFolder);
m_clipsActionsMenu->setEnabled(enableClipActions);
m_extractAudioAction->setEnabled(enableClipActions);
m_openAction->setVisible(itemType != AbstractProjectItem::FolderItem);
m_reloadAction->setVisible(itemType != AbstractProjectItem::FolderItem);
m_duplicateAction->setVisible(itemType != AbstractProjectItem::FolderItem);
m_inTimelineAction->setVisible(itemType != AbstractProjectItem::FolderItem);
if (m_transcodeAction) {
m_transcodeAction->setEnabled(enableClipActions);
m_transcodeAction->menuAction()->setVisible(itemType != AbstractProjectItem::FolderItem && clipService.contains(QStringLiteral("avformat")));
}
m_clipsActionsMenu->menuAction()->setVisible(
itemType != AbstractProjectItem::FolderItem &&
(clipService.contains(QStringLiteral("avformat")) || clipService.contains(QStringLiteral("xml")) || clipService.contains(QStringLiteral("consumer"))));
m_extractAudioAction->menuAction()->setVisible(!isFolder && !audioCodec.isEmpty());
m_locateAction->setVisible(itemType != AbstractProjectItem::FolderItem && (isImported));
// Show menu
event->setAccepted(true);
if (enableClipActions) {
m_menu->exec(event->globalPos());
} else {
// Clicked in empty area
m_addButton->menu()->exec(event->globalPos());
}
}
-void Bin::slotItemDoubleClicked(const QModelIndex &ix, const QPoint pos)
+void Bin::slotItemDoubleClicked(const QModelIndex &ix, const QPoint &pos)
{
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(ix));
if (m_listType == BinIconView) {
if (item->childCount() > 0 || item->itemType() == AbstractProjectItem::FolderItem) {
m_folderUp->changeParent(std::static_pointer_cast(item));
m_itemView->setRootIndex(ix);
return;
}
if (item == m_folderUp) {
std::shared_ptr parentItem = item->parent();
QModelIndex parent = getIndexForId(parentItem->parent()->clipId(), parentItem->parent()->itemType() == AbstractProjectItem::FolderItem);
if (parentItem->parent() != m_itemModel->getRootFolder()) {
// We are entering a parent folder
m_folderUp->changeParent(std::static_pointer_cast(parentItem->parent()));
} else {
m_folderUp->changeParent(std::shared_ptr());
}
m_itemView->setRootIndex(m_proxyModel->mapFromSource(parent));
return;
}
} else {
if (ix.column() == 0 && item->childCount() > 0) {
QRect IconRect = m_itemView->visualRect(ix);
IconRect.setWidth((double)IconRect.height() / m_itemView->iconSize().height() * m_itemView->iconSize().width());
if (!pos.isNull() && (IconRect.contains(pos) || pos.y() > (IconRect.y() + IconRect.height() / 2))) {
auto *view = static_cast(m_itemView);
view->setExpanded(ix, !view->isExpanded(ix));
return;
}
}
}
if (ix.isValid()) {
QRect IconRect = m_itemView->visualRect(ix);
IconRect.setWidth((double)IconRect.height() / m_itemView->iconSize().height() * m_itemView->iconSize().width());
if (!pos.isNull() && ((ix.column() == 2 && item->itemType() == AbstractProjectItem::ClipItem) ||
(!IconRect.contains(pos) && pos.y() < (IconRect.y() + IconRect.height() / 2)))) {
// User clicked outside icon, trigger rename
m_itemView->edit(ix);
return;
}
if (item->itemType() == AbstractProjectItem::ClipItem) {
std::shared_ptr clip = std::static_pointer_cast(item);
if (clip) {
if (clip->clipType() == ClipType::Text || clip->clipType() == ClipType::TextTemplate) {
// m_propertiesPanel->setEnabled(false);
showTitleWidget(clip);
} else {
slotSwitchClipProperties(clip);
}
}
}
}
}
void Bin::slotEditClip()
{
QString panelId = m_propertiesPanel->property("clipId").toString();
QModelIndex current = m_proxyModel->selectionModel()->currentIndex();
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current));
if (item->clipId() != panelId) {
// wrong clip
return;
}
auto clip = std::static_pointer_cast(item);
QString parentFolder = getCurrentFolder();
switch (clip->clipType()) {
case ClipType::Text:
case ClipType::TextTemplate:
showTitleWidget(clip);
break;
case ClipType::SlideShow:
showSlideshowWidget(clip);
break;
case ClipType::QText:
ClipCreationDialog::createQTextClip(m_doc, parentFolder, this, clip.get());
break;
default:
break;
}
}
void Bin::slotSwitchClipProperties()
{
QModelIndex current = m_proxyModel->selectionModel()->currentIndex();
if (current.isValid()) {
// User clicked in the icon, open clip properties
std::shared_ptr item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current));
std::shared_ptr currentItem = nullptr;
if (item->itemType() == AbstractProjectItem::ClipItem) {
currentItem = std::static_pointer_cast(item);
} else if (item->itemType() == AbstractProjectItem::SubClipItem) {
currentItem = std::static_pointer_cast(item)->getMasterClip();
}
if (currentItem) {
slotSwitchClipProperties(currentItem);
return;
}
}
slotSwitchClipProperties(nullptr);
}
void Bin::slotSwitchClipProperties(const std::shared_ptr &clip)
{
if (clip == nullptr) {
m_propertiesPanel->setEnabled(false);
return;
}
if (clip->clipType() == ClipType::SlideShow) {
m_propertiesPanel->setEnabled(false);
showSlideshowWidget(clip);
} else if (clip->clipType() == ClipType::QText) {
m_propertiesPanel->setEnabled(false);
QString parentFolder = getCurrentFolder();
ClipCreationDialog::createQTextClip(m_doc, parentFolder, this, clip.get());
} else {
m_propertiesPanel->setEnabled(true);
showClipProperties(clip);
m_propertiesDock->show();
m_propertiesDock->raise();
}
// Check if properties panel is not tabbed under Bin
// if (!pCore->window()->isTabbedWith(m_propertiesDock, QStringLiteral("project_bin"))) {
}
void Bin::doRefreshPanel(const QString &id)
{
std::shared_ptr currentItem = getFirstSelectedClip();
if ((currentItem != nullptr) && currentItem->AbstractProjectItem::clipId() == id) {
showClipProperties(currentItem, true);
}
}
void Bin::showClipProperties(const std::shared_ptr &clip, bool forceRefresh)
{
if ((clip == nullptr) || !clip->isReady()) {
for (QWidget *w : m_propertiesPanel->findChildren()) {
delete w;
}
m_propertiesPanel->setProperty("clipId", QString());
m_propertiesPanel->setEnabled(false);
return;
}
m_propertiesPanel->setEnabled(true);
QString panelId = m_propertiesPanel->property("clipId").toString();
if (!forceRefresh && panelId == clip->AbstractProjectItem::clipId()) {
// the properties panel is already displaying current clip, do nothing
return;
}
// Cleanup widget for new content
for (QWidget *w : m_propertiesPanel->findChildren()) {
delete w;
}
m_propertiesPanel->setProperty("clipId", clip->AbstractProjectItem::clipId());
auto *lay = static_cast(m_propertiesPanel->layout());
if (lay == nullptr) {
lay = new QVBoxLayout(m_propertiesPanel);
m_propertiesPanel->setLayout(lay);
}
ClipPropertiesController *panel = clip->buildProperties(m_propertiesPanel);
connect(this, &Bin::refreshTimeCode, panel, &ClipPropertiesController::slotRefreshTimeCode);
connect(panel, &ClipPropertiesController::updateClipProperties, this, &Bin::slotEditClipCommand);
connect(panel, &ClipPropertiesController::seekToFrame, m_monitor, static_cast(&Monitor::slotSeek));
connect(panel, &ClipPropertiesController::editClip, this, &Bin::slotEditClip);
connect(panel, SIGNAL(editAnalysis(QString, QString, QString)), this, SLOT(slotAddClipExtraData(QString, QString, QString)));
lay->addWidget(panel);
}
void Bin::slotEditClipCommand(const QString &id, const QMap &oldProps, const QMap &newProps)
{
auto *command = new EditClipCommand(this, id, oldProps, newProps, true);
m_doc->commandStack()->push(command);
}
void Bin::reloadClip(const QString &id)
{
std::shared_ptr clip = m_itemModel->getClipByBinID(id);
if (!clip) {
return;
}
clip->reloadProducer();
}
void Bin::reloadMonitorIfActive(const QString &id)
{
if (m_monitor->activeClipId() == id) {
slotOpenCurrent();
}
}
QStringList Bin::getBinFolderClipIds(const QString &id) const
{
QStringList ids;
std::shared_ptr folder = m_itemModel->getFolderByBinId(id);
if (folder) {
for (int i = 0; i < folder->childCount(); i++) {
std::shared_ptr child = std::static_pointer_cast(folder->child(i));
if (child->itemType() == AbstractProjectItem::ClipItem) {
ids << child->clipId();
}
}
}
return ids;
}
std::shared_ptr Bin::getBinClip(const QString &id)
{
std::shared_ptr clip = nullptr;
if (id.contains(QLatin1Char('_'))) {
clip = m_itemModel->getClipByBinID(id.section(QLatin1Char('_'), 0, 0));
} else if (!id.isEmpty()) {
clip = m_itemModel->getClipByBinID(id);
}
return clip;
}
void Bin::setWaitingStatus(const QString &id)
{
std::shared_ptr clip = m_itemModel->getClipByBinID(id);
if (clip) {
clip->setClipStatus(AbstractProjectItem::StatusWaiting);
}
}
void Bin::slotRemoveInvalidClip(const QString &id, bool replace, const QString &errorMessage)
{
Q_UNUSED(replace);
std::shared_ptr clip = m_itemModel->getClipByBinID(id);
if (!clip) {
return;
}
emit requesteInvalidRemoval(id, clip->url(), errorMessage);
}
void Bin::selectClip(const std::shared_ptr &clip)
{
QModelIndex ix = m_itemModel->getIndexFromItem(clip);
int row = ix.row();
const QModelIndex id = m_itemModel->index(row, 0, ix.parent());
const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, ix.parent());
if (id.isValid() && id2.isValid()) {
m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)), QItemSelectionModel::Select);
}
m_itemView->scrollTo(m_proxyModel->mapFromSource(ix));
}
void Bin::slotOpenCurrent()
{
std::shared_ptr currentItem = getFirstSelectedClip();
if (currentItem) {
emit openClip(currentItem);
}
}
void Bin::openProducer(std::shared_ptr controller)
{
emit openClip(std::move(controller));
}
void Bin::openProducer(std::shared_ptr controller, int in, int out)
{
emit openClip(std::move(controller), in, out);
}
void Bin::emitItemUpdated(std::shared_ptr item)
{
emit itemUpdated(std::move(item));
}
void Bin::emitRefreshPanel(const QString &id)
{
emit refreshPanel(id);
}
void Bin::setupGeneratorMenu()
{
if (!m_menu) {
qCDebug(KDENLIVE_LOG) << "Warning, menu was not created, something is wrong";
return;
}
auto *addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("generators"), pCore->window()));
if (addMenu) {
QMenu *menu = m_addButton->menu();
menu->addMenu(addMenu);
addMenu->setEnabled(!addMenu->isEmpty());
m_addButton->setMenu(menu);
}
addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("extract_audio"), pCore->window()));
if (addMenu) {
m_menu->addMenu(addMenu);
addMenu->setEnabled(!addMenu->isEmpty());
m_extractAudioAction = addMenu;
}
addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("transcoders"), pCore->window()));
if (addMenu) {
m_menu->addMenu(addMenu);
addMenu->setEnabled(!addMenu->isEmpty());
m_transcodeAction = addMenu;
}
addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_actions"), pCore->window()));
if (addMenu) {
m_menu->addMenu(addMenu);
addMenu->setEnabled(!addMenu->isEmpty());
m_clipsActionsMenu = addMenu;
}
addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_in_timeline"), pCore->window()));
if (addMenu) {
m_inTimelineAction = m_menu->addMenu(addMenu);
m_inTimelineAction->setEnabled(!addMenu->isEmpty());
}
if (m_locateAction) {
m_menu->addAction(m_locateAction);
}
if (m_reloadAction) {
m_menu->addAction(m_reloadAction);
}
if (m_duplicateAction) {
m_menu->addAction(m_duplicateAction);
}
if (m_proxyAction) {
m_menu->addAction(m_proxyAction);
}
addMenu = qobject_cast(pCore->window()->factory()->container(QStringLiteral("clip_timeline"), pCore->window()));
if (addMenu) {
m_menu->addMenu(addMenu);
addMenu->setEnabled(false);
}
m_menu->addAction(m_editAction);
m_menu->addAction(m_openAction);
m_menu->addAction(m_renameAction);
m_menu->addAction(m_deleteAction);
m_menu->insertSeparator(m_deleteAction);
}
void Bin::setupMenu(QMenu *addMenu, QAction *defaultAction, const QHash &actions)
{
// Setup actions
QAction *first = m_toolbar->actions().at(0);
m_deleteAction = actions.value(QStringLiteral("delete"));
m_toolbar->insertAction(first, m_deleteAction);
QAction *folder = actions.value(QStringLiteral("folder"));
m_toolbar->insertAction(m_deleteAction, folder);
m_editAction = actions.value(QStringLiteral("properties"));
m_openAction = actions.value(QStringLiteral("open"));
m_reloadAction = actions.value(QStringLiteral("reload"));
m_duplicateAction = actions.value(QStringLiteral("duplicate"));
m_locateAction = actions.value(QStringLiteral("locate"));
m_proxyAction = actions.value(QStringLiteral("proxy"));
auto *m = new QMenu(this);
m->addActions(addMenu->actions());
m_addButton = new QToolButton(this);
m_addButton->setMenu(m);
m_addButton->setDefaultAction(defaultAction);
m_addButton->setPopupMode(QToolButton::MenuButtonPopup);
m_toolbar->insertWidget(folder, m_addButton);
m_menu = new QMenu(this);
m_propertiesDock = pCore->window()->addDock(i18n("Clip Properties"), QStringLiteral("clip_properties"), m_propertiesPanel);
m_propertiesDock->close();
// m_menu->addActions(addMenu->actions());
}
const QString Bin::getDocumentProperty(const QString &key)
{
return m_doc->getDocumentProperty(key);
}
void Bin::slotUpdateJobStatus(const QString &id, int jobType, int status, const QString &label, const QString &actionName, const QString &details)
{
Q_UNUSED(id)
Q_UNUSED(jobType)
Q_UNUSED(status)
Q_UNUSED(label)
Q_UNUSED(actionName)
Q_UNUSED(details)
// TODO refac
/*
std::shared_ptr clip = m_itemModel->getClipByBinID(id);
if (clip) {
clip->setJobStatus((AbstractClipJob::JOBTYPE)jobType, (ClipJobStatus)status);
}
if (status == JobCrashed) {
QList actions = m_infoMessage->actions();
if (m_infoMessage->isHidden()) {
if (!details.isEmpty()) {
m_infoMessage->setText(label + QStringLiteral(" ") + i18n("Show log") + QStringLiteral(""));
} else {
m_infoMessage->setText(label);
}
m_infoMessage->setWordWrap(m_infoMessage->text().length() > 35);
m_infoMessage->setMessageType(KMessageWidget::Warning);
}
if (!actionName.isEmpty()) {
QAction *action = nullptr;
QList collections = KActionCollection::allCollections();
for (int i = 0; i < collections.count(); ++i) {
KActionCollection *coll = collections.at(i);
action = coll->action(actionName);
if (action) {
break;
}
}
if ((action != nullptr) && !actions.contains(action)) {
m_infoMessage->addAction(action);
}
}
if (!details.isEmpty()) {
m_errorLog.append(details);
}
m_infoMessage->setCloseButtonVisible(true);
m_infoMessage->animatedShow();
}
*/
}
void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QList &actions)
{
// Remove existing actions if any
QList acts = m_infoMessage->actions();
while (!acts.isEmpty()) {
QAction *a = acts.takeFirst();
m_infoMessage->removeAction(a);
delete a;
}
m_infoMessage->setText(text);
m_infoMessage->setWordWrap(text.length() > 35);
for (QAction *action : actions) {
m_infoMessage->addAction(action);
connect(action, &QAction::triggered, this, &Bin::slotMessageActionTriggered);
}
m_infoMessage->setCloseButtonVisible(actions.isEmpty());
m_infoMessage->setMessageType(type);
m_infoMessage->animatedShow();
}
void Bin::doDisplayMessage(const QString &text, KMessageWidget::MessageType type, const QString &logInfo)
{
// Remove existing actions if any
QList acts = m_infoMessage->actions();
while (!acts.isEmpty()) {
QAction *a = acts.takeFirst();
m_infoMessage->removeAction(a);
delete a;
}
m_infoMessage->setText(text);
m_infoMessage->setWordWrap(text.length() > 35);
QAction *ac = new QAction(i18n("Show log"), this);
m_infoMessage->addAction(ac);
connect(ac, &QAction::triggered, [this, logInfo](bool) {
KMessageBox::sorry(this, logInfo, i18n("Detailed log"));
slotMessageActionTriggered();
});
m_infoMessage->setCloseButtonVisible(false);
m_infoMessage->setMessageType(type);
m_infoMessage->animatedShow();
}
void Bin::refreshClip(const QString &id)
{
if (m_monitor->activeClipId() == id) {
m_monitor->refreshMonitorIfActive();
}
}
void Bin::doRefreshAudioThumbs(const QString &id)
{
if (m_monitor->activeClipId() == id) {
slotSendAudioThumb(id);
}
}
void Bin::slotCreateProjectClip()
{
auto *act = qobject_cast(sender());
if (act == nullptr) {
// Cannot access triggering action, something is wrong
qCDebug(KDENLIVE_LOG) << "// Error in clip creation action";
return;
}
ClipType::ProducerType type = (ClipType::ProducerType)act->data().toInt();
QStringList folderInfo = getFolderInfo();
QString parentFolder = getCurrentFolder();
switch (type) {
case ClipType::Color:
ClipCreationDialog::createColorClip(m_doc, parentFolder, m_itemModel);
break;
case ClipType::SlideShow:
ClipCreationDialog::createSlideshowClip(m_doc, parentFolder, m_itemModel);
break;
case ClipType::Text:
ClipCreationDialog::createTitleClip(m_doc, parentFolder, QString(), m_itemModel);
break;
case ClipType::TextTemplate:
ClipCreationDialog::createTitleTemplateClip(m_doc, parentFolder, m_itemModel);
break;
case ClipType::QText:
ClipCreationDialog::createQTextClip(m_doc, parentFolder, this);
break;
default:
break;
}
}
void Bin::slotItemDropped(const QStringList &ids, const QModelIndex &parent)
{
std::shared_ptr parentItem;
if (parent.isValid()) {
parentItem = m_itemModel->getBinItemByIndex(parent);
parentItem = parentItem->getEnclosingFolder(false);
} else {
parentItem = m_itemModel->getRootFolder();
}
auto *moveCommand = new QUndoCommand();
moveCommand->setText(i18np("Move Clip", "Move Clips", ids.count()));
QStringList folderIds;
for (const QString &id : ids) {
if (id.contains(QLatin1Char('/'))) {
// trying to move clip zone, not allowed. Ignore
continue;
}
if (id.startsWith(QLatin1Char('#'))) {
// moving a folder, keep it for later
folderIds << id;
continue;
}
std::shared_ptr currentItem = m_itemModel->getClipByBinID(id);
if (!currentItem) {
continue;
}
std::shared_ptr currentParent = currentItem->parent();
if (currentParent != parentItem) {
// Item was dropped on a different folder
new MoveBinClipCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand);
}
}
if (!folderIds.isEmpty()) {
for (QString id : folderIds) {
id.remove(0, 1);
std::shared_ptr currentItem = m_itemModel->getFolderByBinId(id);
if (!currentItem) {
continue;
}
std::shared_ptr currentParent = currentItem->parent();
if (currentParent != parentItem) {
// Item was dropped on a different folder
new MoveBinFolderCommand(this, id, currentParent->clipId(), parentItem->clipId(), moveCommand);
}
}
}
if (moveCommand->childCount() == 0) {
pCore->displayMessage(i18n("No valid clip to insert"), InformationMessage, 500);
} else {
m_doc->commandStack()->push(moveCommand);
}
}
void Bin::slotAddEffect(QString id, const QStringList &effectData)
{
if (id.isEmpty()) {
id = m_monitor->activeClipId();
}
if (!id.isEmpty()) {
std::shared_ptr clip = m_itemModel->getClipByBinID(id);
if (clip) {
if (effectData.count() == 4) {
// Paste effect from another stack
std::shared_ptr sourceStack = pCore->getItemEffectStack(effectData.at(1).toInt(), effectData.at(2).toInt());
clip->copyEffect(sourceStack, effectData.at(3).toInt());
} else {
clip->addEffect(effectData.constFirst());
}
return;
}
}
pCore->displayMessage(i18n("Select a clip to apply an effect"), InformationMessage, 500);
}
void Bin::slotEffectDropped(const QStringList &effectData, const QModelIndex &parent)
{
if (parent.isValid()) {
std::shared_ptr parentItem = m_itemModel->getBinItemByIndex(parent);
if (parentItem->itemType() != AbstractProjectItem::ClipItem) {
// effect only supported on clip items
return;
}
m_proxyModel->selectionModel()->clearSelection();
int row = parent.row();
const QModelIndex id = m_itemModel->index(row, 0, parent.parent());
const QModelIndex id2 = m_itemModel->index(row, m_itemModel->columnCount() - 1, parent.parent());
if (id.isValid() && id2.isValid()) {
m_proxyModel->selectionModel()->select(QItemSelection(m_proxyModel->mapFromSource(id), m_proxyModel->mapFromSource(id2)),
QItemSelectionModel::Select);
}
setCurrent(parentItem);
if (effectData.count() == 4) {
// Paste effect from another stack
std::shared_ptr sourceStack = pCore->getItemEffectStack(effectData.at(1).toInt(), effectData.at(2).toInt());
std::static_pointer_cast(parentItem)->copyEffect(sourceStack, effectData.at(3).toInt());
} else {
std::static_pointer_cast(parentItem)->addEffect(effectData.constFirst());
}
}
}
void Bin::editMasterEffect(const std::shared_ptr &clip)
{
if (m_gainedFocus) {
// Widget just gained focus, updating stack is managed in the eventfilter event, not from item
return;
}
if (clip) {
if (clip->itemType() == AbstractProjectItem::ClipItem) {
std::shared_ptr clp = std::static_pointer_cast(clip);
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false);
return;
}
if (clip->itemType() == AbstractProjectItem::SubClipItem) {
if (auto ptr = clip->parentItem().lock()) {
std::shared_ptr clp = std::static_pointer_cast(ptr);
emit requestShowEffectStack(clp->clipName(), clp->m_effectStack, clp->getFrameSize(), false);
}
return;
}
}
emit requestShowEffectStack(QString(), nullptr, QSize(), false);
}
void Bin::slotGotFocus()
{
m_gainedFocus = true;
}
void Bin::doMoveClip(const QString &id, const QString &newParentId)
{
std::shared_ptr currentItem = m_itemModel->getClipByBinID(id);
if (!currentItem) {
return;
}
std::shared_ptr currentParent = currentItem->parent();
std::shared_ptr newParent = m_itemModel->getFolderByBinId(newParentId);
currentItem->changeParent(newParent);
}
void Bin::doMoveFolder(const QString &id, const QString &newParentId)
{
std::shared_ptr currentItem = m_itemModel->getFolderByBinId(id);
std::shared_ptr