diff --git a/src/assets/abstractassetsrepository.hpp b/src/assets/abstractassetsrepository.hpp index afbc60dd0..fdc704eb7 100644 --- a/src/assets/abstractassetsrepository.hpp +++ b/src/assets/abstractassetsrepository.hpp @@ -1,121 +1,116 @@ /*************************************************************************** * 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{}; 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 &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 9ecb6bf78..b10ec84ab 100644 --- a/src/assets/abstractassetsrepository.ipp +++ b/src/assets/abstractassetsrepository.ipp @@ -1,317 +1,316 @@ /*************************************************************************** * 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) { QString id = metadata->get("identifier"); res.name = metadata->get("title"); res.name[0] = res.name[0].toUpper(); res.description = metadata->get("description"); res.description.append(QString(" (%1)").arg(id)); 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")); 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") == 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; res.description.append(QString(" (%1)").arg(tag)); } // 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/assetlist/model/assetfilter.cpp b/src/assets/assetlist/model/assetfilter.cpp index 52bb300a7..2c0abcdfa 100644 --- a/src/assets/assetlist/model/assetfilter.cpp +++ b/src/assets/assetlist/model/assetfilter.cpp @@ -1,176 +1,188 @@ /*************************************************************************** * 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 "assetfilter.hpp" #include "abstractmodel/abstracttreemodel.hpp" #include "abstractmodel/treeitem.hpp" #include "assettreemodel.hpp" #include +#include AssetFilter::AssetFilter(QObject *parent) : QSortFilterProxyModel(parent) { setFilterRole(Qt::DisplayRole); setSortRole(Qt::DisplayRole); setDynamicSortFilter(false); } void AssetFilter::setFilterName(bool enabled, const QString &pattern) { m_name_enabled = enabled; m_name_value = pattern; invalidateFilter(); if (rowCount() > 1) { sort(0); } } +bool AssetFilter::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + QString leftData = sourceModel()->data(left).toString(); + QString rightData = sourceModel()->data(right).toString(); + if (rightData == i18n("Favorites")) { + return false; + } + return QString::localeAwareCompare(leftData, rightData) < 0; +} + bool AssetFilter::filterName(const std::shared_ptr &item) const { if (!m_name_enabled) { return true; } QString itemText = item->dataColumn(AssetTreeModel::nameCol).toString(); itemText = itemText.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]"))); QString patt = m_name_value.normalized(QString::NormalizationForm_D).remove(QRegExp(QStringLiteral("[^a-zA-Z0-9\\s]"))); return itemText.contains(patt, Qt::CaseInsensitive); } bool AssetFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { QModelIndex row = sourceModel()->index(sourceRow, 0, sourceParent); auto *model = static_cast(sourceModel()); std::shared_ptr item = model->getItemById((int)row.internalId()); if (item->dataColumn(AssetTreeModel::idCol) == QStringLiteral("root")) { // In that case, we have a category. We hide it if it does not have children. QModelIndex category = sourceModel()->index(sourceRow, 0, sourceParent); if (!category.isValid()) { return false; } bool accepted = false; for (int i = 0; i < sourceModel()->rowCount(category) && !accepted; ++i) { accepted = filterAcceptsRow(i, category); } return accepted; } return applyAll(item); } bool AssetFilter::isVisible(const QModelIndex &sourceIndex) { auto parent = sourceModel()->parent(sourceIndex); return filterAcceptsRow(sourceIndex.row(), parent); } bool AssetFilter::applyAll(std::shared_ptr item) const { return filterName(item); } QModelIndex AssetFilter::getNextChild(const QModelIndex ¤t) { QModelIndex nextItem = current.sibling(current.row() + 1, current.column()); if (!nextItem.isValid()) { QModelIndex folder = index(current.parent().row() + 1, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = folder.sibling(folder.row() + 1, folder.column()); } if (folder.isValid() && rowCount(folder) > 0) { return index(0, current.column(), folder); } nextItem = current; } return nextItem; } QModelIndex AssetFilter::getPreviousChild(const QModelIndex ¤t) { QModelIndex nextItem = current.sibling(current.row() - 1, current.column()); if (!nextItem.isValid()) { QModelIndex folder = index(current.parent().row() - 1, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = folder.sibling(folder.row() - 1, folder.column()); } if (folder.isValid() && rowCount(folder) > 0) { return index(rowCount(folder) - 1, current.column(), folder); } nextItem = current; } return nextItem; } QModelIndex AssetFilter::firstVisibleItem(const QModelIndex ¤t) { if (current.isValid() && isVisible(mapToSource(current))) { return current; } QModelIndex folder = index(0, 0, QModelIndex()); if (!folder.isValid()) { return current; } while (folder.isValid() && rowCount(folder) == 0) { folder = index(folder.row() + 1, 0, QModelIndex()); } if (rowCount(folder) > 0) { return index(0, 0, folder); } return current; } QModelIndex AssetFilter::getCategory(int catRow) { QModelIndex cat = index(catRow, 0, QModelIndex()); return cat; } QVariantList AssetFilter::getCategories() { QVariantList list; for (int i = 0; i < sourceModel()->rowCount(); i++) { QModelIndex cat = getCategory(i); if (cat.isValid()) { list << cat; } } return list; } QModelIndex AssetFilter::getModelIndex(QModelIndex current) { QModelIndex sourceIndex = mapToSource(current); return sourceIndex; // this returns an integer } QModelIndex AssetFilter::getProxyIndex(QModelIndex current) { QModelIndex sourceIndex = mapFromSource(current); return sourceIndex; // this returns an integer } diff --git a/src/assets/assetlist/model/assetfilter.hpp b/src/assets/assetlist/model/assetfilter.hpp index 9c556a202..22aa93351 100644 --- a/src/assets/assetlist/model/assetfilter.hpp +++ b/src/assets/assetlist/model/assetfilter.hpp @@ -1,70 +1,70 @@ /*************************************************************************** * 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 ASSETFILTER_H #define ASSETFILTER_H #include #include /* @brief This class is used as a proxy model to filter an asset list based on given criterion (name, ...) */ class TreeItem; class AssetFilter : public QSortFilterProxyModel { Q_OBJECT public: AssetFilter(QObject *parent = nullptr); /* @brief Manage the name filter @param enabled whether to enable this filter @param pattern to match against effects' names */ void setFilterName(bool enabled, const QString &pattern); /** @brief Returns true if the ModelIndex in the source model is visible after filtering */ bool isVisible(const QModelIndex &sourceIndex); /** @brief If we are in favorite view, invalidate filter to refresh. Call this after a favorite has changed */ virtual void reloadFilterOnFavorite() = 0; QVariantList getCategories(); Q_INVOKABLE QModelIndex getNextChild(const QModelIndex ¤t); Q_INVOKABLE QModelIndex getPreviousChild(const QModelIndex ¤t); Q_INVOKABLE QModelIndex firstVisibleItem(const QModelIndex ¤t); Q_INVOKABLE QModelIndex getCategory(int catRow); Q_INVOKABLE QModelIndex getModelIndex(QModelIndex current); Q_INVOKABLE QModelIndex getProxyIndex(QModelIndex current); protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; - + bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; bool filterName(const std::shared_ptr &item) const; /* @brief Apply all filter and returns true if the object should be kept after filtering */ virtual bool applyAll(std::shared_ptr item) const; bool m_name_enabled{false}; QString m_name_value; }; #endif diff --git a/src/assets/assetlist/model/assettreemodel.cpp b/src/assets/assetlist/model/assettreemodel.cpp index e4b12b1ca..36c57ef0b 100644 --- a/src/assets/assetlist/model/assettreemodel.cpp +++ b/src/assets/assetlist/model/assettreemodel.cpp @@ -1,128 +1,107 @@ /*************************************************************************** * 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 "assettreemodel.hpp" #include "abstractmodel/treeitem.hpp" #include "effects/effectsrepository.hpp" #include "transitions/transitionsrepository.hpp" int AssetTreeModel::nameCol = 0; int AssetTreeModel::idCol = 1; int AssetTreeModel::typeCol = 2; int AssetTreeModel::favCol = 3; AssetTreeModel::AssetTreeModel(QObject *parent) : AbstractTreeModel(parent) { } QHash AssetTreeModel::roleNames() const { QHash roles; roles[IdRole] = "identifier"; roles[NameRole] = "name"; roles[FavoriteRole] = "favorite"; return roles; } QString AssetTreeModel::getName(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } std::shared_ptr item = getItemById((int)index.internalId()); if (item->depth() == 1) { return item->dataColumn(0).toString(); } return item->dataColumn(AssetTreeModel::nameCol).toString(); } bool AssetTreeModel::isFavorite(const QModelIndex &index) const { if (!index.isValid()) { return false; } std::shared_ptr item = getItemById((int)index.internalId()); if (item->depth() == 1) { return false; } return item->dataColumn(AssetTreeModel::favCol).toBool(); } -void AssetTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool isEffect) -{ - if (!index.isValid()) { - return; - } - std::shared_ptr item = getItemById((int)index.internalId()); - if (isEffect && item->depth() == 1) { - return; - } - item->setData(AssetTreeModel::favCol, favorite); - auto id = item->dataColumn(AssetTreeModel::idCol).toString(); - if (isEffect) { - if (EffectsRepository::get()->exists(id)) { - EffectsRepository::get()->setFavorite(id, favorite); - } - } else { - if (TransitionsRepository::get()->exists(id)) { - TransitionsRepository::get()->setFavorite(id, favorite); - } - } -} - QString AssetTreeModel::getDescription(const QModelIndex &index) const { if (!index.isValid()) { return QString(); } std::shared_ptr item = getItemById((int)index.internalId()); if (item->depth() == 1) { return QString(); } auto id = item->dataColumn(AssetTreeModel::idCol).toString(); if (EffectsRepository::get()->exists(id)) { return EffectsRepository::get()->getDescription(id); } if (TransitionsRepository::get()->exists(id)) { return TransitionsRepository::get()->getDescription(id); } return QString(); } QVariant AssetTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } std::shared_ptr item = getItemById((int)index.internalId()); switch (role) { case IdRole: return item->dataColumn(AssetTreeModel::idCol); case FavoriteRole: return item->dataColumn(AssetTreeModel::favCol); case NameRole: + case Qt::DisplayRole: return item->dataColumn(index.column()); default: return QVariant(); } } diff --git a/src/assets/assetlist/model/assettreemodel.hpp b/src/assets/assetlist/model/assettreemodel.hpp index 42a4137c5..455d888d6 100644 --- a/src/assets/assetlist/model/assettreemodel.hpp +++ b/src/assets/assetlist/model/assettreemodel.hpp @@ -1,58 +1,58 @@ /*************************************************************************** * 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 ASSETTREEMODEL_H #define ASSETTREEMODEL_H #include "abstractmodel/abstracttreemodel.hpp" /* @brief This class represents an effect hierarchy to be displayed as a tree */ class TreeItem; class QMenu; class KActionCategory; class AssetTreeModel : public AbstractTreeModel { public: explicit AssetTreeModel(QObject *parent = nullptr); enum { IdRole = Qt::UserRole + 1, NameRole, FavoriteRole }; // Helper function to retrieve name QString getName(const QModelIndex &index) const; // Helper function to retrieve description QString getDescription(const QModelIndex &index) const; // Helper function to retrieve if an effect is categorized as favorite bool isFavorite(const QModelIndex &index) const; - void setFavorite(const QModelIndex &index, bool favorite, bool isEffect); QHash roleNames() const override; QVariant data(const QModelIndex &index, int role) const override; virtual void reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions) = 0; + virtual void setFavorite(const QModelIndex &index, bool favorite, bool isEffect) = 0; // for convenience, we store the column of each data field static int nameCol, idCol, favCol, typeCol; protected: }; #endif diff --git a/src/assets/assetlist/view/assetlistwidget.cpp b/src/assets/assetlist/view/assetlistwidget.cpp index 918bacc90..4d6769d8d 100644 --- a/src/assets/assetlist/view/assetlistwidget.cpp +++ b/src/assets/assetlist/view/assetlistwidget.cpp @@ -1,109 +1,110 @@ /*************************************************************************** * 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 "assetlistwidget.hpp" #include "assets/assetlist/model/assetfilter.hpp" #include "assets/assetlist/model/assettreemodel.hpp" #include "assets/assetlist/view/qmltypes/asseticonprovider.hpp" #include #include #include #include #include AssetListWidget::AssetListWidget(QWidget *parent) : QQuickWidget(parent) { KDeclarative::KDeclarative kdeclarative; kdeclarative.setDeclarativeEngine(engine()); #if KDECLARATIVE_VERSION >= QT_VERSION_CHECK(5, 45, 0) kdeclarative.setupEngine(engine()); kdeclarative.setupContext(); #else kdeclarative.setupBindings(); #endif } AssetListWidget::~AssetListWidget() { // clear source setSource(QUrl()); } void AssetListWidget::setup() { setResizeMode(QQuickWidget::SizeRootObjectToView); engine()->addImageProvider(QStringLiteral("asseticon"), m_assetIconProvider); setSource(QUrl(QStringLiteral("qrc:/qml/assetList.qml"))); setFocusPolicy(Qt::StrongFocus); } void AssetListWidget::reset() { setSource(QUrl(QStringLiteral("qrc:/qml/assetList.qml"))); } QString AssetListWidget::getName(const QModelIndex &index) const { return m_model->getName(m_proxyModel->mapToSource(index)); } bool AssetListWidget::isFavorite(const QModelIndex &index) const { return m_model->isFavorite(m_proxyModel->mapToSource(index)); } void AssetListWidget::setFavorite(const QModelIndex &index, bool favorite, bool isEffect) { m_model->setFavorite(m_proxyModel->mapToSource(index), favorite, isEffect); + m_proxyModel->sort(0); } QString AssetListWidget::getDescription(const QModelIndex &index) const { return m_model->getDescription(m_proxyModel->mapToSource(index)); } void AssetListWidget::setFilterName(const QString &pattern) { m_proxyModel->setFilterName(!pattern.isEmpty(), pattern); if (!pattern.isEmpty()) { QVariantList mapped = m_proxyModel->getCategories(); QMetaObject::invokeMethod(rootObject(), "expandNodes", Qt::DirectConnection, Q_ARG(QVariant, mapped)); } } QVariantMap AssetListWidget::getMimeData(const QString &assetId) const { QVariantMap mimeData; mimeData.insert(getMimeType(assetId), assetId); return mimeData; } void AssetListWidget::activate(const QModelIndex &ix) { if (!ix.isValid()) { return; } const QString assetId = m_model->data(m_proxyModel->mapToSource(ix), AssetTreeModel::IdRole).toString(); emit activateAsset(getMimeData(assetId)); } diff --git a/src/effects/effectlist/model/effecttreemodel.cpp b/src/effects/effectlist/model/effecttreemodel.cpp index 96ea0e187..5cd0de709 100644 --- a/src/effects/effectlist/model/effecttreemodel.cpp +++ b/src/effects/effectlist/model/effecttreemodel.cpp @@ -1,151 +1,181 @@ /*************************************************************************** * 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 "effecttreemodel.hpp" #include "abstractmodel/treeitem.hpp" #include "effects/effectsrepository.hpp" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include #include EffectTreeModel::EffectTreeModel(QObject *parent) : AssetTreeModel(parent) , m_customCategory(nullptr) { } std::shared_ptr EffectTreeModel::construct(const QString &categoryFile, QObject *parent) { std::shared_ptr self(new EffectTreeModel(parent)); - QList rootData; - rootData << "Name" - << "ID" - << "Type" - << "isFav"; + QList rootData {"Name", "ID", "Type", "isFav"}; self->rootItem = TreeItem::construct(rootData, self, true); QHash> effectCategory; // category in which each effect should land. std::shared_ptr miscCategory = nullptr; std::shared_ptr audioCategory = nullptr; // We parse category file if (!categoryFile.isEmpty()) { QDomDocument doc; QFile file(categoryFile); doc.setContent(&file, false); file.close(); QDomNodeList groups = doc.documentElement().elementsByTagName(QStringLiteral("group")); // Create favorite group - auto groupFav = self->rootItem->appendChild(QList{i18n("Favorites"), QStringLiteral("root")}); + auto groupFav = self->rootItem->appendChild(QList{i18n("Favorites"), QStringLiteral("root"), -1}); + + effectCategory[QStringLiteral("kdenlive:favorites")] = groupFav; auto groupLegacy = self->rootItem->appendChild(QList{i18n("Legacy"), QStringLiteral("root")}); for (int i = 0; i < groups.count(); i++) { QString groupName = i18n(groups.at(i).firstChild().firstChild().nodeValue().toUtf8().constData()); if (!KdenliveSettings::gpu_accel() && groupName == i18n("GPU effects")) { continue; } QStringList list = groups.at(i).toElement().attribute(QStringLiteral("list")).split(QLatin1Char(','), QString::SkipEmptyParts); - auto groupItem = self->rootItem->appendChild(QList{groupName, QStringLiteral("root")}); for (const QString &effect : list) { - if (KdenliveSettings::favorite_effects().contains(effect)) { - effectCategory[effect] = groupFav; - } else { - effectCategory[effect] = groupItem; - } + effectCategory[effect] = groupItem; } } // We also create "Misc", "Audio" and "Custom" categories miscCategory = self->rootItem->appendChild(QList{i18n("Misc"), QStringLiteral("root")}); audioCategory = self->rootItem->appendChild(QList{i18n("Audio"), QStringLiteral("root")}); self->m_customCategory = self->rootItem->appendChild(QList{i18n("Custom"), QStringLiteral("root")}); } else { // Flat view miscCategory = self->rootItem; audioCategory = self->rootItem; self->m_customCategory = self->rootItem; } // We parse effects auto allEffects = EffectsRepository::get()->getNames(); + QString favCategory = QStringLiteral("kdenlive:favorites"); for (const auto &effect : allEffects) { if (!KdenliveSettings::gpu_accel() && effect.first.contains(QLatin1String("movit."))) { continue; } auto targetCategory = miscCategory; EffectType type = EffectsRepository::get()->getType(effect.first); if (effectCategory.contains(effect.first)) { targetCategory = effectCategory[effect.first]; } else if (type == EffectType::Audio) { targetCategory = audioCategory; } if (type == EffectType::Custom) { targetCategory = self->m_customCategory; } // we create the data list corresponding to this profile bool isFav = KdenliveSettings::favorite_effects().contains(effect.first); //qDebug() << effect.second << effect.first << "in " << targetCategory->dataColumn(0).toString(); - QList data {effect.second, effect.first, QVariant::fromValue(type), isFav}; + QList data {effect.second, effect.first, QVariant::fromValue(type), isFav, targetCategory->row()}; + if (KdenliveSettings::favorite_effects().contains(effect.first) && effectCategory.contains(favCategory)) { + targetCategory = effectCategory[favCategory]; + } targetCategory->appendChild(data); } return self; } void EffectTreeModel::reloadEffect(const QString &path) { QPair asset = EffectsRepository::get()->reloadCustom(path); if (asset.first.isEmpty() || m_customCategory == nullptr) { return; } bool isFav = KdenliveSettings::favorite_effects().contains(asset.first); QList data {asset.second, asset.first, QVariant::fromValue(EffectType::Custom), isFav}; m_customCategory->appendChild(data); } void EffectTreeModel::reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions) { for (int i = 0; i < rowCount(); i++) { std::shared_ptr item = rootItem->child(i); if (item->childCount() > 0) { QMenu *catMenu = new QMenu(item->dataColumn(nameCol).toString(), effectsMenu); effectsMenu->addMenu(catMenu); for (int j = 0; j < item->childCount(); j++) { std::shared_ptr child = item->child(j); QAction *a = new QAction(child->dataColumn(nameCol).toString(), catMenu); const QString id = child->dataColumn(idCol).toString(); a->setData(id); catMenu->addAction(a); effectActions->addAction("transition_" + id, a); } } } } + +void EffectTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool isEffect) +{ + if (!index.isValid()) { + return; + } + std::shared_ptr item = getItemById((int)index.internalId()); + if (isEffect && item->depth() == 1) { + return; + } + item->setData(AssetTreeModel::favCol, favorite); + auto id = item->dataColumn(AssetTreeModel::idCol).toString(); + if (!EffectsRepository::get()->exists(id)) { + qDebug()<<"Trying to reparent unknown asset: "<childCount(); + QStringList favs = KdenliveSettings::favorite_effects(); + if (!favorite) { + int ix = item->dataColumn(4).toInt(); + item->changeParent(rootItem->child(ix)); + favs.removeAll(id); + } else { + for (int i = 0; i < max; i++) { + if (rootItem->child(i)->dataColumn(2).toInt() == -1) { + item->changeParent(rootItem->child(i)); + break; + } + } + favs << id; + } + KdenliveSettings::setFavorite_effects(favs); +} diff --git a/src/effects/effectlist/model/effecttreemodel.hpp b/src/effects/effectlist/model/effecttreemodel.hpp index 347f4bdfe..b46a8233f 100644 --- a/src/effects/effectlist/model/effecttreemodel.hpp +++ b/src/effects/effectlist/model/effecttreemodel.hpp @@ -1,47 +1,47 @@ /*************************************************************************** * 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 EFFECTTREEMODEL_H #define EFFECTTREEMODEL_H #include "abstractmodel/abstracttreemodel.hpp" #include "assets/assetlist/model/assettreemodel.hpp" /* @brief This class represents an effect hierarchy to be displayed as a tree */ class TreeItem; class EffectTreeModel : public AssetTreeModel { protected: explicit EffectTreeModel(QObject *parent = nullptr); public: static std::shared_ptr construct(const QString &categoryFile, QObject *parent); void reloadEffect(const QString &path); void reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions) override; - + void setFavorite(const QModelIndex &index, bool favorite, bool isEffect) override; protected: std::shared_ptr m_customCategory; }; #endif diff --git a/src/effects/effectsrepository.cpp b/src/effects/effectsrepository.cpp index b5cbb63b4..d58bab35c 100644 --- a/src/effects/effectsrepository.cpp +++ b/src/effects/effectsrepository.cpp @@ -1,212 +1,196 @@ /*************************************************************************** * 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 "effectsrepository.hpp" #include "core.h" #include "kdenlivesettings.h" #include "profiles/profilemodel.hpp" #include "xml/xml.hpp" #include #include #include #include #include #include std::unique_ptr EffectsRepository::instance; std::once_flag EffectsRepository::m_onceFlag; EffectsRepository::EffectsRepository() : AbstractAssetsRepository() { init(); // Check that our favorite effects are valid QStringList invalidEffect; for (const QString &effect : KdenliveSettings::favorite_effects()) { if (!exists(effect)) { invalidEffect << effect; } } if (!invalidEffect.isEmpty()) { pCore->displayMessage(i18n("Some of your favorite effects are invalid and were removed: %1", invalidEffect.join(QLatin1Char(','))), ErrorMessage); QStringList newFavorites = KdenliveSettings::favorite_effects(); for (const QString &effect : invalidEffect) { newFavorites.removeAll(effect); } KdenliveSettings::setFavorite_effects(newFavorites); } } Mlt::Properties *EffectsRepository::retrieveListFromMlt() const { return pCore->getMltRepository()->filters(); } -void EffectsRepository::parseFavorites() -{ - m_favorites = KdenliveSettings::favorite_effects().toSet(); -} - -void EffectsRepository::setFavorite(const QString &id, bool favorite) -{ - Q_ASSERT(exists(id)); - if (favorite) { - m_favorites << id; - } else { - m_favorites.remove(id); - } - KdenliveSettings::setFavorite_effects(QStringList::fromSet(m_favorites)); -} - Mlt::Properties *EffectsRepository::getMetadata(const QString &effectId) { return pCore->getMltRepository()->metadata(filter_type, effectId.toLatin1().data()); } void EffectsRepository::parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const { QFile file(file_name); QDomDocument doc; doc.setContent(&file, false); file.close(); QDomElement base = doc.documentElement(); if (base.tagName() == QLatin1String("effectgroup")) { // in that case we have a custom effect Info info; info.xml = base; info.type = EffectType::Custom; QString tag = base.attribute(QStringLiteral("tag"), QString()); QString id = base.hasAttribute(QStringLiteral("id")) ? base.attribute(QStringLiteral("id")) : tag; QString name = base.attribute(QStringLiteral("name"), QString()); info.name = name; info.id = id; info.mltId = tag; if (customAssets.count(id) > 0) { qDebug() << "Error: conflicting effect name" << id; } else { customAssets[id] = info; } return; } QDomNodeList effects = doc.elementsByTagName(QStringLiteral("effect")); int nbr_effect = effects.count(); if (nbr_effect == 0) { qDebug() << "+++++++++++++\nEffect broken: " << file_name << "\n+++++++++++"; return; } for (int i = 0; i < nbr_effect; ++i) { QDomNode currentNode = effects.item(i); if (currentNode.isNull()) { continue; } QDomElement currentEffect = currentNode.toElement(); Info result; bool ok = parseInfoFromXml(currentEffect, result); if (!ok) { continue; } if (customAssets.count(result.id) > 0) { qDebug() << "Warning: duplicate custom definition of effect" << result.id << "found. Only last one will be considered. Duplicate found in" << file_name; } result.xml = currentEffect; // Parse type information. QString type = currentEffect.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("audio")) { result.type = EffectType::Audio; } else if (type == QLatin1String("custom")) { result.type = EffectType::Custom; } else if (type == QLatin1String("hidden")) { result.type = EffectType::Hidden; } else { result.type = EffectType::Video; } customAssets[result.id] = result; } } std::unique_ptr &EffectsRepository::get() { std::call_once(m_onceFlag, [] { instance.reset(new EffectsRepository()); }); return instance; } QStringList EffectsRepository::assetDirs() const { return QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("effects"), QStandardPaths::LocateDirectory); } void EffectsRepository::parseType(QScopedPointer &metadata, Info &res) { res.type = EffectType::Video; Mlt::Properties tags((mlt_properties)metadata->get_data("tags")); if (QString(tags.get(0)) == QLatin1String("Audio")) { res.type = EffectType::Audio; } } QString EffectsRepository::assetBlackListPath() const { return QStringLiteral(":data/blacklisted_effects.txt"); } std::unique_ptr EffectsRepository::getEffect(const QString &effectId) const { Q_ASSERT(exists(effectId)); QString service_name = m_assets.at(effectId).mltId; // We create the Mlt element from its name auto filter = std::make_unique(pCore->getCurrentProfile()->profile(), service_name.toLatin1().constData(), nullptr); return filter; } bool EffectsRepository::hasInternalEffect(const QString &effectId) const { // Retrieve the list of MLT's available assets. QScopedPointer assets(retrieveListFromMlt()); int max = assets->count(); for (int i = 0; i < max; ++i) { if (assets->get_name(i) == effectId) { return true; } } return false; } QPair EffectsRepository::reloadCustom(const QString &path) { std::unordered_map customAssets; parseCustomAssetFile(path, customAssets); QPair result; // TODO: handle files with several effects for (const auto &custom : customAssets) { // Custom assets should override default ones m_assets[custom.first] = custom.second; result.first = custom.first; result.second = custom.second.mltId; } return result; } diff --git a/src/effects/effectsrepository.hpp b/src/effects/effectsrepository.hpp index 954d770a9..df7767f10 100644 --- a/src/effects/effectsrepository.hpp +++ b/src/effects/effectsrepository.hpp @@ -1,84 +1,80 @@ /*************************************************************************** * 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 EFFECTSREPOSITORY_H #define EFFECTSREPOSITORY_H #include "assets/abstractassetsrepository.hpp" #include "definitions.h" #include #include #include #include #include /** @brief This class stores all the effects that can be added by the user. * You can query any effect based on its name. * Note that this class is a Singleton */ enum class EffectType { Video, Audio, Custom, Favorites, Hidden }; Q_DECLARE_METATYPE(EffectType) class EffectsRepository : public AbstractAssetsRepository { public: // Returns the instance of the Singleton static std::unique_ptr &get(); /* @brief returns a fresh instance of the given effect */ std::unique_ptr getEffect(const QString &effectId) const; /* @brief returns true if an effect exists in MLT (bypasses the blacklist/metadata parsing) */ bool hasInternalEffect(const QString &effectId) const; - void setFavorite(const QString &id, bool favorite) override; QPair reloadCustom(const QString &path); protected: // Constructor is protected because class is a Singleton EffectsRepository(); /* Retrieves the list of all available effects from Mlt*/ Mlt::Properties *retrieveListFromMlt() const override; - /* Retrieves the list of favorite effects */ - void parseFavorites() override; - /* @brief Retrieves additional info about effects from a custom XML file The resulting assets are stored in customAssets */ void parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const override; /* @brief Returns the path to the effects' blacklist*/ QString assetBlackListPath() const override; QStringList assetDirs() const override; void parseType(QScopedPointer &metadata, Info &res) override; /* @brief Returns the metadata associated with the given asset*/ Mlt::Properties *getMetadata(const QString &assetId) override; static std::unique_ptr instance; static std::once_flag m_onceFlag; // flag to create the repository only once; }; #endif diff --git a/src/transitions/transitionlist/model/transitiontreemodel.cpp b/src/transitions/transitionlist/model/transitiontreemodel.cpp index 00d7a5480..8a4293b5d 100644 --- a/src/transitions/transitionlist/model/transitiontreemodel.cpp +++ b/src/transitions/transitionlist/model/transitiontreemodel.cpp @@ -1,96 +1,115 @@ /*************************************************************************** * 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 "transitiontreemodel.hpp" #include "abstractmodel/treeitem.hpp" #include "kdenlivesettings.h" #include "transitions/transitionsrepository.hpp" #include #include #include #include TransitionTreeModel::TransitionTreeModel(QObject *parent) : AssetTreeModel(parent) { } std::shared_ptr TransitionTreeModel::construct(bool flat, QObject *parent) { std::shared_ptr self(new TransitionTreeModel(parent)); - QList rootData; - rootData << "Name" - << "ID" - << "Type" - << "isFav"; + QList rootData {"Name", "ID", "Type", "isFav"}; self->rootItem = TreeItem::construct(rootData, self, true); // We create categories, if requested std::shared_ptr compoCategory, transCategory; if (!flat) { compoCategory = self->rootItem->appendChild(QList{i18n("Compositions"), QStringLiteral("root")}); transCategory = self->rootItem->appendChild(QList{i18n("Transitions"), QStringLiteral("root")}); } // We parse transitions auto allTransitions = TransitionsRepository::get()->getNames(); for (const auto &transition : allTransitions) { if (!KdenliveSettings::gpu_accel() && transition.first.contains(QLatin1String("movit."))) { // Hide GPU compositions when movit disabled continue; } std::shared_ptr targetCategory = compoCategory; TransitionType type = TransitionsRepository::get()->getType(transition.first); if (type == TransitionType::AudioTransition || type == TransitionType::VideoTransition) { targetCategory = transCategory; } if (flat) { targetCategory = self->rootItem; } // we create the data list corresponding to this transition bool isFav = KdenliveSettings::favorite_transitions().contains(transition.first); //qDebug() << transition.second << transition.first << "in " << targetCategory->dataColumn(0).toString(); QList data {transition.second, transition.first, QVariant::fromValue(type), isFav}; targetCategory->appendChild(data); } return self; } void TransitionTreeModel::reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions) { for (int i = 0; i < rowCount(); i++) { std::shared_ptr item = rootItem->child(i); if (item->childCount() > 0) { QMenu *catMenu = new QMenu(item->dataColumn(nameCol).toString(), effectsMenu); effectsMenu->addMenu(catMenu); for (int j = 0; j < item->childCount(); j++) { std::shared_ptr child = item->child(j); QAction *a = new QAction(child->dataColumn(nameCol).toString(), catMenu); const QString id = child->dataColumn(idCol).toString(); a->setData(id); catMenu->addAction(a); effectActions->addAction("transition_" + id, a); } } } } + +void TransitionTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool isEffect) +{ + if (!index.isValid()) { + return; + } + std::shared_ptr item = getItemById((int)index.internalId()); + if (isEffect && item->depth() == 1) { + return; + } + item->setData(AssetTreeModel::favCol, favorite); + auto id = item->dataColumn(AssetTreeModel::idCol).toString(); + QStringList favs = KdenliveSettings::favorite_effects(); + if (favorite) { + favs << id; + } else { + favs.removeAll(id); + } + KdenliveSettings::setFavorite_effects(favs); + /*if (TransitionsRepository::get()->exists(id)) { + TransitionsRepository::get()->setFavorite(id, favorite); + }*/ +} diff --git a/src/transitions/transitionlist/model/transitiontreemodel.hpp b/src/transitions/transitionlist/model/transitiontreemodel.hpp index 082706e7a..ad150f12f 100644 --- a/src/transitions/transitionlist/model/transitiontreemodel.hpp +++ b/src/transitions/transitionlist/model/transitiontreemodel.hpp @@ -1,45 +1,46 @@ /*************************************************************************** * 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 TRANSITIONTREEMODEL_H #define TRANSITIONTREEMODEL_H #include "abstractmodel/abstracttreemodel.hpp" #include "assets/assetlist/model/assettreemodel.hpp" /* @brief This class represents a transition hierarchy to be displayed as a tree */ class TreeItem; class TransitionTreeModel : public AssetTreeModel { protected: explicit TransitionTreeModel(QObject *parent); public: // if flat = true, then the categories are not created static std::shared_ptr construct(bool flat = false, QObject *parent = nullptr); void reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions) override; + void setFavorite(const QModelIndex &index, bool favorite, bool isEffect) override; protected: }; #endif diff --git a/src/transitions/transitionsrepository.cpp b/src/transitions/transitionsrepository.cpp index 05623ad3d..27906e560 100644 --- a/src/transitions/transitionsrepository.cpp +++ b/src/transitions/transitionsrepository.cpp @@ -1,197 +1,181 @@ /*************************************************************************** * 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 "transitionsrepository.hpp" #include "core.h" #include "kdenlivesettings.h" #include "xml/xml.hpp" #include #include #include #include #include "profiles/profilemodel.hpp" #include std::unique_ptr TransitionsRepository::instance; std::once_flag TransitionsRepository::m_onceFlag; TransitionsRepository::TransitionsRepository() : AbstractAssetsRepository() { init(); QStringList invalidTransition; for (const QString &effect : KdenliveSettings::favorite_transitions()) { if (!exists(effect)) { invalidTransition << effect; } } if (!invalidTransition.isEmpty()) { pCore->displayMessage(i18n("Some of your favorite compositions are invalid and were removed: %1", invalidTransition.join(QLatin1Char(','))), ErrorMessage); QStringList newFavorites = KdenliveSettings::favorite_transitions(); for (const QString &effect : invalidTransition) { newFavorites.removeAll(effect); } KdenliveSettings::setFavorite_transitions(newFavorites); } } Mlt::Properties *TransitionsRepository::retrieveListFromMlt() const { return pCore->getMltRepository()->transitions(); } -void TransitionsRepository::parseFavorites() -{ - m_favorites = KdenliveSettings::favorite_transitions().toSet(); -} - -void TransitionsRepository::setFavorite(const QString &id, bool favorite) -{ - Q_ASSERT(exists(id)); - if (favorite) { - m_favorites << id; - } else { - m_favorites.remove(id); - } - KdenliveSettings::setFavorite_transitions(QStringList::fromSet(m_favorites)); -} - Mlt::Properties *TransitionsRepository::getMetadata(const QString &assetId) { return pCore->getMltRepository()->metadata(transition_type, assetId.toLatin1().data()); } void TransitionsRepository::parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const { QFile file(file_name); QDomDocument doc; doc.setContent(&file, false); file.close(); QDomElement base = doc.documentElement(); QDomNodeList transitions = doc.elementsByTagName(QStringLiteral("transition")); int nbr_transition = transitions.count(); if (nbr_transition == 0) { qDebug() << "+++++++++++++\n Transition broken: " << file_name << "\n+++++++++++"; return; } for (int i = 0; i < nbr_transition; ++i) { QDomNode currentNode = transitions.item(i); if (currentNode.isNull()) { continue; } Info result; bool ok = parseInfoFromXml(currentNode.toElement(), result); if (!ok) { continue; } if (customAssets.count(result.id) > 0) { qDebug() << "Warning: duplicate custom definition of transition" << result.id << "found. Only last one will be considered"; } result.xml = currentNode.toElement(); QString type = result.xml.attribute(QStringLiteral("type"), QString()); if (type == QLatin1String("hidden")) { result.type = TransitionType::Hidden; } customAssets[result.id] = result; } } std::unique_ptr &TransitionsRepository::get() { std::call_once(m_onceFlag, [] { instance.reset(new TransitionsRepository()); }); return instance; } QStringList TransitionsRepository::assetDirs() const { return QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("transitions"), QStandardPaths::LocateDirectory); } void TransitionsRepository::parseType(QScopedPointer &metadata, Info &res) { Mlt::Properties tags((mlt_properties)metadata->get_data("tags")); bool audio = QString(tags.get(0)) == QLatin1String("Audio"); if (getSingleTrackTransitions().contains(res.id)) { if (audio) { res.type = TransitionType::AudioTransition; } else { res.type = TransitionType::VideoTransition; } } else { if (audio) { res.type = TransitionType::AudioComposition; } else { res.type = TransitionType::VideoComposition; } } } QSet TransitionsRepository::getSingleTrackTransitions() { // Disabled until same track transitions is implemented return {/*QStringLiteral("composite"), QStringLiteral("dissolve")*/}; } QString TransitionsRepository::assetBlackListPath() const { return QStringLiteral(":data/blacklisted_transitions.txt"); } std::unique_ptr TransitionsRepository::getTransition(const QString &transitionId) const { Q_ASSERT(exists(transitionId)); QString service_name = m_assets.at(transitionId).mltId; // We create the Mlt element from its name auto transition = std::make_unique(pCore->getCurrentProfile()->profile(), service_name.toLatin1().constData(), nullptr); transition->set("kdenlive_id", transitionId.toUtf8().constData()); return transition; } bool TransitionsRepository::isComposition(const QString &transitionId) const { auto type = getType(transitionId); return type == TransitionType::AudioComposition || type == TransitionType::VideoComposition; } const QString TransitionsRepository::getCompositingTransition() { if (KdenliveSettings::gpu_accel()) { return QStringLiteral("movit.overlay"); } if (exists(QStringLiteral("qtblend"))) { return QStringLiteral("qtblend"); } if (exists(QStringLiteral("frei0r.cairoblend"))) { return QStringLiteral("frei0r.cairoblend"); } if (exists(QStringLiteral("composite"))) { return QStringLiteral("composite"); } qDebug() << "Warning: no compositing found"; return QString(); } diff --git a/src/transitions/transitionsrepository.hpp b/src/transitions/transitionsrepository.hpp index 384a2a760..27bc7c36d 100644 --- a/src/transitions/transitionsrepository.hpp +++ b/src/transitions/transitionsrepository.hpp @@ -1,90 +1,86 @@ /*************************************************************************** * 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 TRANSITIONSREPOSITORY_H #define TRANSITIONSREPOSITORY_H #include "assets/abstractassetsrepository.hpp" #include "assets/model/assetparametermodel.hpp" #include "definitions.h" #include #include /** @brief This class stores all the transitions that can be added by the user. * You can query any transitions based on its name. * Note that this class is a Singleton */ enum class TransitionType { AudioComposition, VideoComposition, AudioTransition, VideoTransition, Favorites, Hidden }; Q_DECLARE_METATYPE(TransitionType) class TransitionsRepository : public AbstractAssetsRepository { public: // Returns the instance of the Singleton static std::unique_ptr &get(); /* @brief Creates and return an instance of a transition given its id. */ std::unique_ptr getTransition(const QString &transitionId) const; /* @brief returns true if the transition corresponding to @transitionId is a composition*/ bool isComposition(const QString &transitionId) const; /* @brief Returns the id of the transition to be used for compositing */ const QString getCompositingTransition(); - void setFavorite(const QString &id, bool favorite) override; protected: // Constructor is protected because class is a Singleton TransitionsRepository(); /* Retrieves the list of all available effects from Mlt*/ Mlt::Properties *retrieveListFromMlt() const override; - /* Retrieves the list of favorite effects */ - void parseFavorites() override; - /* @brief Retrieves additional info about effects from a custom XML file The resulting assets are stored in customAssets */ void parseCustomAssetFile(const QString &file_name, std::unordered_map &customAssets) const override; /* @brief Returns the paths where the custom transitions' descriptions are stored */ QStringList assetDirs() const override; /* @brief Returns the path to the transitions' blacklist*/ QString assetBlackListPath() const override; void parseType(QScopedPointer &metadata, Info &res) override; /* @brief Returns the metadata associated with the given asset*/ Mlt::Properties *getMetadata(const QString &assetId) override; /* @brief Returns all transitions that can be represented as Single Track Transitions*/ static QSet getSingleTrackTransitions(); static std::unique_ptr instance; static std::once_flag m_onceFlag; // flag to create the repository only once; }; #endif