File indexing completed on 2024-05-19 04:54:22

0001 /*
0002     SPDX-FileCopyrightText: 2017 Nicolas Carion
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "effecttreemodel.hpp"
0007 #include "abstractmodel/treeitem.hpp"
0008 #include "effects/effectsrepository.hpp"
0009 #include "kdenlivesettings.h"
0010 
0011 #include <QApplication>
0012 #include <QDomDocument>
0013 #include <QFile>
0014 #include <array>
0015 #include <vector>
0016 
0017 #include <KActionCategory>
0018 #include <KLocalizedString>
0019 #include <KMessageBox>
0020 #include <QDebug>
0021 #include <QMenu>
0022 #include <QMessageBox>
0023 #include <QMimeData>
0024 
0025 EffectTreeModel::EffectTreeModel(QObject *parent)
0026     : AssetTreeModel(parent)
0027     , m_customCategory(nullptr)
0028     , m_templateCategory(nullptr)
0029 {
0030     m_assetIconProvider = new AssetIconProvider(true, this);
0031 }
0032 
0033 std::shared_ptr<EffectTreeModel> EffectTreeModel::construct(const QString &categoryFile, QObject *parent)
0034 {
0035     std::shared_ptr<EffectTreeModel> self(new EffectTreeModel(parent));
0036     QList<QVariant> rootData{"Name", "ID", "Type", "isFav"};
0037     self->rootItem = TreeItem::construct(rootData, self, true);
0038 
0039     QHash<QString, std::shared_ptr<TreeItem>> effectCategory; // category in which each effect should land.
0040 
0041     std::shared_ptr<TreeItem> miscCategory = nullptr;
0042     std::shared_ptr<TreeItem> audioCategory = nullptr;
0043     // We parse category file
0044     QDomDocument doc;
0045     if (!categoryFile.isEmpty() && Xml::docContentFromFile(doc, categoryFile, false)) {
0046         QDomNodeList groups = doc.documentElement().elementsByTagName(QStringLiteral("group"));
0047         auto groupLegacy = self->rootItem->appendChild(QList<QVariant>{i18n("Legacy"), QStringLiteral("root")});
0048 
0049         for (int i = 0; i < groups.count(); i++) {
0050             QString groupName = i18n(groups.at(i).firstChild().firstChild().nodeValue().toUtf8().constData());
0051             if (!KdenliveSettings::gpu_accel() && groupName == i18n("GPU effects")) {
0052                 continue;
0053             }
0054             QStringList list = groups.at(i).toElement().attribute(QStringLiteral("list")).split(QLatin1Char(','), Qt::SkipEmptyParts);
0055             auto groupItem = self->rootItem->appendChild(QList<QVariant>{groupName, QStringLiteral("root")});
0056             for (const QString &effect : qAsConst(list)) {
0057                 effectCategory[effect] = groupItem;
0058             }
0059         }
0060         // We also create "Misc", "Audio" and "Custom" categories
0061         miscCategory = self->rootItem->appendChild(QList<QVariant>{i18n("Misc"), QStringLiteral("root")});
0062         audioCategory = self->rootItem->appendChild(QList<QVariant>{i18n("Audio"), QStringLiteral("root")});
0063         self->m_customCategory = self->rootItem->appendChild(QList<QVariant>{i18n("Custom"), QStringLiteral("root")});
0064         self->m_templateCategory = self->rootItem->appendChild(QList<QVariant>{i18n("Templates"), QStringLiteral("root")});
0065     } else {
0066         // Flat view
0067         miscCategory = self->rootItem;
0068         audioCategory = self->rootItem;
0069         self->m_customCategory = self->rootItem;
0070         self->m_templateCategory = self->rootItem;
0071     }
0072 
0073     // We parse effects
0074     auto allEffects = EffectsRepository::get()->getNames();
0075     QString favCategory = QStringLiteral("kdenlive:favorites");
0076     for (const auto &effect : qAsConst(allEffects)) {
0077         auto targetCategory = miscCategory;
0078         AssetListType::AssetType type = EffectsRepository::get()->getType(effect.first);
0079         if (effectCategory.contains(effect.first)) {
0080             targetCategory = effectCategory[effect.first];
0081         } else if (type == AssetListType::AssetType::Audio) {
0082             targetCategory = audioCategory;
0083         }
0084 
0085         if (type == AssetListType::AssetType::Custom || type == AssetListType::AssetType::CustomAudio) {
0086             targetCategory = self->m_customCategory;
0087         } else if (type == AssetListType::AssetType::Template || type == AssetListType::AssetType::TemplateAudio) {
0088             targetCategory = self->m_templateCategory;
0089         }
0090 
0091         // we create the data list corresponding to this profile
0092         bool isFav = KdenliveSettings::favorite_effects().contains(effect.first);
0093         bool isPreferred = EffectsRepository::get()->isPreferred(effect.first);
0094         QList<QVariant> data;
0095         if (targetCategory->dataColumn(0).toString() == i18n("Deprecated")) {
0096             QString updatedName = effect.second + i18n(" - deprecated");
0097             data = {updatedName, effect.first, QVariant::fromValue(type), isFav, targetCategory->row(), isPreferred};
0098         } else {
0099             // qDebug() << effect.second << effect.first << "in " << targetCategory->dataColumn(0).toString();
0100             data = {effect.second, effect.first, QVariant::fromValue(type), isFav, targetCategory->row(), isPreferred};
0101         }
0102         if (KdenliveSettings::favorite_effects().contains(effect.first) && effectCategory.contains(favCategory)) {
0103             targetCategory = effectCategory[favCategory];
0104         }
0105         targetCategory->appendChild(data);
0106     }
0107     return self;
0108 }
0109 
0110 void EffectTreeModel::reloadEffectFromIndex(const QModelIndex &index)
0111 {
0112     if (!index.isValid()) {
0113         return;
0114     }
0115     std::shared_ptr<TreeItem> item = getItemById(int(index.internalId()));
0116     const QString path = EffectsRepository::get()->getCustomPath(item->dataColumn(IdCol).toString());
0117     reloadEffect(path);
0118 }
0119 
0120 void EffectTreeModel::reloadEffect(const QString &path)
0121 {
0122     QPair<QString, QString> asset = EffectsRepository::get()->reloadCustom(path);
0123     AssetListType::AssetType type = EffectsRepository::get()->getType(asset.first);
0124     std::shared_ptr<TreeItem> targetCategory = nullptr;
0125     if (type == AssetListType::AssetType::Custom || type == AssetListType::AssetType::CustomAudio) {
0126         targetCategory = m_customCategory;
0127     } else if (type == AssetListType::AssetType::Template || type == AssetListType::AssetType::TemplateAudio) {
0128         targetCategory = m_templateCategory;
0129     }
0130     if (asset.first.isEmpty() || targetCategory == nullptr) {
0131         return;
0132     }
0133     // Check if item already existed, and remove
0134     for (int i = 0; i < targetCategory->childCount(); i++) {
0135         std::shared_ptr<TreeItem> item = targetCategory->child(i);
0136         if (item->dataColumn(IdCol).toString() == asset.first) {
0137             targetCategory->removeChild(item);
0138             break;
0139         }
0140     }
0141     bool isFav = KdenliveSettings::favorite_effects().contains(asset.first);
0142     QString effectName = EffectsRepository::get()->getName(asset.first);
0143     QList<QVariant> data{effectName, asset.first, QVariant::fromValue(type), isFav};
0144     targetCategory->appendChild(data);
0145 }
0146 
0147 void EffectTreeModel::deleteEffect(const QModelIndex &index)
0148 {
0149     if (!index.isValid()) {
0150         return;
0151     }
0152     std::shared_ptr<TreeItem> item = getItemById(int(index.internalId()));
0153     const QString id = item->dataColumn(IdCol).toString();
0154     m_customCategory->removeChild(item);
0155     EffectsRepository::get()->deleteEffect(id);
0156 }
0157 
0158 void EffectTreeModel::reloadTemplates()
0159 {
0160     // First remove all templates effects
0161     if (!m_templateCategory) {
0162         return;
0163     }
0164     // Check if item already existed, and remove
0165     while (m_templateCategory->childCount() > 0) {
0166         std::shared_ptr<TreeItem> item = m_templateCategory->child(0);
0167         m_templateCategory->removeChild(item);
0168     }
0169     QStringList asset_dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("effect-templates"), QStandardPaths::LocateDirectory);
0170     QListIterator<QString> dirs_it(asset_dirs);
0171     for (dirs_it.toBack(); dirs_it.hasPrevious();) {
0172         auto dir = dirs_it.previous();
0173         QDir current_dir(dir);
0174         QStringList filter{QStringLiteral("*.xml")};
0175         QStringList fileList = current_dir.entryList(filter, QDir::Files);
0176         for (const auto &file : qAsConst(fileList)) {
0177             QString path = current_dir.absoluteFilePath(file);
0178             reloadEffect(path);
0179         }
0180     }
0181 }
0182 
0183 void EffectTreeModel::reloadAssetMenu(QMenu *effectsMenu, KActionCategory *effectActions)
0184 {
0185     for (int i = 0; i < rowCount(); i++) {
0186         std::shared_ptr<TreeItem> item = rootItem->child(i);
0187         if (item->childCount() > 0) {
0188             QMenu *catMenu = new QMenu(item->dataColumn(AssetTreeModel::NameCol).toString(), effectsMenu);
0189             effectsMenu->addMenu(catMenu);
0190             for (int j = 0; j < item->childCount(); j++) {
0191                 std::shared_ptr<TreeItem> child = item->child(j);
0192                 QAction *a = new QAction(i18n(child->dataColumn(AssetTreeModel::NameCol).toString().toUtf8().data()), catMenu);
0193                 const QString id = child->dataColumn(AssetTreeModel::IdCol).toString();
0194                 a->setData(id);
0195                 catMenu->addAction(a);
0196                 effectActions->addAction("transition_" + id, a);
0197             }
0198         }
0199     }
0200 }
0201 
0202 void EffectTreeModel::setFavorite(const QModelIndex &index, bool favorite, bool isEffect)
0203 {
0204     if (!index.isValid()) {
0205         return;
0206     }
0207     std::shared_ptr<TreeItem> item = getItemById(int(index.internalId()));
0208     if (isEffect && item->depth() == 1) {
0209         return;
0210     }
0211     item->setData(AssetTreeModel::FavCol, favorite);
0212     auto id = item->dataColumn(AssetTreeModel::IdCol).toString();
0213     if (!EffectsRepository::get()->exists(id)) {
0214         qDebug() << "Trying to reparent unknown asset: " << id;
0215         return;
0216     }
0217     QStringList favs = KdenliveSettings::favorite_effects();
0218     if (!favorite) {
0219         favs.removeAll(id);
0220     } else {
0221         favs << id;
0222     }
0223     KdenliveSettings::setFavorite_effects(favs);
0224 }
0225 
0226 void EffectTreeModel::editCustomAsset(const QString &newName, const QString &newDescription, const QModelIndex &index)
0227 {
0228 
0229     std::shared_ptr<TreeItem> item = getItemById(int(index.internalId()));
0230     QString currentName = item->dataColumn(AssetTreeModel::NameCol).toString();
0231 
0232     QDomDocument doc;
0233 
0234     QDomElement effect = EffectsRepository::get()->getXml(currentName);
0235     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/"));
0236     QString oldpath = dir.absoluteFilePath(currentName + QStringLiteral(".xml"));
0237 
0238     doc.appendChild(doc.importNode(effect, true));
0239 
0240     if (!newDescription.trimmed().isEmpty()) {
0241         QDomElement root = doc.documentElement();
0242         QDomElement nodelist = root.firstChildElement("description");
0243         QDomElement newNodeTag = doc.createElement(QString("description"));
0244         QDomText text = doc.createTextNode(newDescription);
0245         newNodeTag.appendChild(text);
0246         if (!nodelist.isNull()) {
0247             root.replaceChild(newNodeTag, nodelist);
0248         } else {
0249             root.appendChild(newNodeTag);
0250         }
0251     }
0252 
0253     if (!newName.trimmed().isEmpty() && newName != currentName) {
0254         if (!dir.exists()) {
0255             dir.mkpath(QStringLiteral("."));
0256         }
0257 
0258         if (dir.exists(newName + QStringLiteral(".xml"))) {
0259             QMessageBox message;
0260             message.critical(nullptr, i18n("Error"), i18n("Effect name %1 already exists.\n Try another name?", newName));
0261             message.setFixedSize(400, 200);
0262             return;
0263         }
0264         QFile file(dir.absoluteFilePath(newName + QStringLiteral(".xml")));
0265 
0266         QDomElement root = doc.documentElement();
0267         QDomElement nodelist = root.firstChildElement("name");
0268         QDomElement newNodeTag = doc.createElement(QString("name"));
0269         QDomText text = doc.createTextNode(newName);
0270         newNodeTag.appendChild(text);
0271         root.replaceChild(newNodeTag, nodelist);
0272 
0273         QDomElement e = doc.documentElement();
0274         e.setAttribute("id", newName);
0275 
0276         if (file.open(QFile::WriteOnly | QFile::Truncate)) {
0277             QTextStream out(&file);
0278 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0279             out.setCodec("UTF-8");
0280 #endif
0281             out << doc.toString();
0282         } else {
0283             KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", file.fileName()));
0284         }
0285         file.close();
0286 
0287         deleteEffect(index);
0288         reloadEffect(dir.absoluteFilePath(newName + QStringLiteral(".xml")));
0289 
0290     } else {
0291         QFile file(dir.absoluteFilePath(currentName + QStringLiteral(".xml")));
0292         if (file.open(QFile::WriteOnly | QFile::Truncate)) {
0293             QTextStream out(&file);
0294 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0295             out.setCodec("UTF-8");
0296 #endif
0297             out << doc.toString();
0298         } else {
0299             KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", file.fileName()));
0300         }
0301         file.close();
0302         reloadEffect(oldpath);
0303     }
0304 }
0305 
0306 QMimeData *EffectTreeModel::mimeData(const QModelIndexList &indexes) const
0307 {
0308     QMimeData *mimeData = new QMimeData;
0309     std::shared_ptr<TreeItem> item = getItemById(int(indexes.first().internalId()));
0310     if (item) {
0311         const QString assetId = item->dataColumn(AssetTreeModel::IdCol).toString();
0312         mimeData->setData(QStringLiteral("kdenlive/effect"), assetId.toUtf8());
0313         if (EffectsRepository::get()->isAudioEffect(assetId)) {
0314             qDebug() << "::::: ASSET IS AUDIO!!!";
0315             mimeData->setData(QStringLiteral("type"), QByteArray("audio"));
0316         }
0317     }
0318     return mimeData;
0319 }