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 }