File indexing completed on 2024-05-12 04:52:55

0001 /*
0002     SPDX-FileCopyrightText: 2021 Julius Künzel <jk.kdedev@smartlab.uber.space>
0003     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 */
0005 
0006 #include "urllistparamwidget.h"
0007 #include "assets/model/assetparametermodel.hpp"
0008 #include "core.h"
0009 #include "kdenlivesettings.h"
0010 #include "mainwindow.h"
0011 #include "mltconnection.h"
0012 
0013 #include <QDirIterator>
0014 #include <QFileDialog>
0015 #include <QtConcurrent>
0016 
0017 UrlListParamWidget::UrlListParamWidget(std::shared_ptr<AssetParameterModel> model, QModelIndex index, QWidget *parent)
0018     : AbstractParamWidget(std::move(model), index, parent)
0019     , m_isLumaList(false)
0020 {
0021     setupUi(this);
0022 
0023     // Get data from model
0024     QString comment = m_model->data(m_index, AssetParameterModel::CommentRole).toString();
0025     const QString configFile = m_model->data(m_index, AssetParameterModel::NewStuffRole).toString();
0026 
0027     // setup the comment
0028     setToolTip(comment);
0029     m_labelComment->setText(comment);
0030     m_widgetComment->setHidden(true);
0031     setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
0032     m_list->setIconSize(QSize(50, 30));
0033     setMinimumHeight(m_list->sizeHint().height());
0034     // setup download
0035     if (!configFile.isEmpty()) {
0036         m_knsbutton->setConfigFile(configFile);
0037         connect(m_knsbutton, &KNSWidgets::Button::dialogFinished, this, [this, configFile](const QList<KNSCore::Entry> &changedEntries) {
0038             if (changedEntries.count() > 0) {
0039                 if (configFile.contains(QStringLiteral("kdenlive_wipes.knsrc"))) {
0040                     MltConnection::refreshLumas();
0041                 }
0042                 slotRefresh();
0043             }
0044         });
0045     } else {
0046         m_knsbutton->hide();
0047     }
0048     // setup the name
0049     m_labelName->setText(m_model->data(m_index, Qt::DisplayRole).toString());
0050     m_isLutList = m_model->getAssetId().startsWith(QLatin1String("avfilter.lut3d"));
0051     UrlListParamWidget::slotRefresh();
0052 
0053     // Q_EMIT the signal of the base class when appropriate
0054     // The connection is ugly because the signal "currentIndexChanged" is overloaded in QComboBox
0055     connect(this->m_list, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, [this](int index) {
0056         if (m_list->currentData() == QStringLiteral("custom_file")) {
0057             openFile();
0058         } else {
0059             m_currentIndex = index;
0060             Q_EMIT valueChanged(m_index, m_list->currentData().toString(), true);
0061         }
0062     });
0063 }
0064 
0065 UrlListParamWidget::~UrlListParamWidget()
0066 {
0067     if (m_watcher.isRunning()) {
0068         m_abortJobs = true;
0069         m_watcher.waitForFinished();
0070     }
0071 }
0072 
0073 void UrlListParamWidget::setCurrentIndex(int index)
0074 {
0075     m_list->setCurrentIndex(index);
0076 }
0077 
0078 void UrlListParamWidget::setCurrentText(const QString &text)
0079 {
0080     m_list->setCurrentText(text);
0081 }
0082 
0083 void UrlListParamWidget::addItem(const QString &text, const QVariant &value)
0084 {
0085     m_list->addItem(text, value);
0086 }
0087 
0088 void UrlListParamWidget::setItemIcon(int index, const QIcon &icon)
0089 {
0090     m_list->setItemIcon(index, icon);
0091 }
0092 
0093 void UrlListParamWidget::setIconSize(const QSize &size)
0094 {
0095     m_list->setIconSize(size);
0096 }
0097 
0098 void UrlListParamWidget::slotShowComment(bool show)
0099 {
0100     if (!m_labelComment->text().isEmpty()) {
0101         m_widgetComment->setVisible(show);
0102     }
0103 }
0104 
0105 QString UrlListParamWidget::getValue()
0106 {
0107     return m_list->currentData().toString();
0108 }
0109 
0110 void UrlListParamWidget::slotRefresh()
0111 {
0112     const QSignalBlocker bk(m_list);
0113     m_list->clear();
0114     QStringList names = m_model->data(m_index, AssetParameterModel::ListNamesRole).toStringList();
0115     QStringList values = m_model->data(m_index, AssetParameterModel::ListValuesRole).toStringList();
0116     QString currentValue = m_model->data(m_index, AssetParameterModel::ValueRole).toString();
0117     QString filter = m_model->data(m_index, AssetParameterModel::FilterRole).toString();
0118     filter.remove(0, filter.indexOf("(") + 1);
0119     filter.remove(filter.indexOf(")") - 1, -1);
0120     m_fileExt = filter.split(" ");
0121     if (!values.isEmpty() && values.first() == QLatin1String("%lumaPaths")) {
0122         // special case: Luma files
0123         m_isLumaList = true;
0124         values.clear();
0125         names.clear();
0126         if (pCore->getCurrentFrameSize().width() > 1000) {
0127             // HD project
0128             values = MainWindow::m_lumaFiles.value(QStringLiteral("16_9"));
0129         } else if (pCore->getCurrentFrameSize().height() > 1000) {
0130             values = MainWindow::m_lumaFiles.value(QStringLiteral("9_16"));
0131         } else if (pCore->getCurrentFrameSize().height() == pCore->getCurrentFrameSize().width()) {
0132             values = MainWindow::m_lumaFiles.value(QStringLiteral("square"));
0133         } else if (pCore->getCurrentFrameSize().height() == 480) {
0134             values = MainWindow::m_lumaFiles.value(QStringLiteral("NTSC"));
0135         } else {
0136             values = MainWindow::m_lumaFiles.value(QStringLiteral("PAL"));
0137         }
0138         m_list->addItem(i18n("None (Dissolve)"));
0139     }
0140     if (!values.isEmpty() && values.first() == QLatin1String("%lutPaths")) {
0141         // special case: LUT files
0142         values.clear();
0143         names.clear();
0144 
0145         // check for Kdenlive installed luts files
0146         QStringList customLuts = QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, QStringLiteral("luts"), QStandardPaths::LocateDirectory);
0147         for (const QString &folderpath : qAsConst(customLuts)) {
0148             QDir dir(folderpath);
0149             QDirIterator it(dir.absolutePath(), m_fileExt, QDir::Files, QDirIterator::Subdirectories);
0150             while (it.hasNext()) {
0151                 values.append(it.next());
0152             }
0153         }
0154     }
0155     // add all matching files in the location of the current item too
0156     if (!currentValue.isEmpty()) {
0157         const QString path = QUrl(currentValue).adjusted(QUrl::RemoveFilename).toString();
0158         QDir dir(path);
0159         if (dir.exists()) {
0160             QStringList entrys = dir.entryList(m_fileExt, QDir::Files);
0161             for (const auto &filename : qAsConst(entrys)) {
0162                 values.append(dir.filePath(filename));
0163             }
0164             // make sure the current value is added. If it is a duplicate we remove it later
0165             if (QFileInfo::exists(currentValue)) {
0166                 values << currentValue;
0167             }
0168         }
0169     }
0170 
0171     values.removeDuplicates();
0172 
0173     // build ui list
0174     QMap<QString, QString> entryMap;
0175     int ix = 0;
0176     // Put all name/value combinations in a map
0177     for (const QString &value : qAsConst(values)) {
0178         if (m_isLutList) {
0179             if (value.toLower().endsWith(QLatin1String(".cube")) && !KdenliveSettings::validated_luts().contains(value)) {
0180                 // Open LUT file and check validity
0181                 if (isValidCubeFile(value)) {
0182                     entryMap.insert(QFileInfo(value).baseName(), value);
0183                     QStringList validated = KdenliveSettings::validated_luts();
0184                     validated << value;
0185                     KdenliveSettings::setValidated_luts(validated);
0186                 } else {
0187                     qDebug() << ":::: FOUND INVALID LUT FILE: " << value;
0188                 }
0189             } else {
0190                 entryMap.insert(QFileInfo(value).baseName(), value);
0191             }
0192         } else if (m_isLumaList) {
0193             QString lumaName = pCore->nameForLumaFile(QFileInfo(value).fileName());
0194             if (entryMap.contains(lumaName)) {
0195                 // Duplicate name, add a suffix
0196                 const QString baseName = lumaName;
0197                 int i = 2;
0198                 lumaName = baseName + QString(" / %1").arg(i);
0199                 while (entryMap.contains(lumaName)) {
0200                     i++;
0201                     lumaName = baseName + QString(" / %1").arg(i);
0202                 }
0203             }
0204             entryMap.insert(lumaName, value);
0205         } else if (ix < names.count()) {
0206             entryMap.insert(names.at(ix), value);
0207         }
0208         ix++;
0209     }
0210     QMapIterator<QString, QString> i(entryMap);
0211     QStringList thumbnailsToBuild;
0212     while (i.hasNext()) {
0213         i.next();
0214         const QString &entry = i.value();
0215         m_list->addItem(i.key(), entry);
0216         int ix = m_list->findData(entry);
0217         // Create thumbnails
0218         if (!entry.isEmpty() && (entry.toLower().endsWith(QLatin1String(".png")) || entry.toLower().endsWith(QLatin1String(".pgm")))) {
0219             if (MainWindow::m_lumacache.contains(entry)) {
0220                 m_list->setItemIcon(ix, QPixmap::fromImage(MainWindow::m_lumacache.value(entry)));
0221             } else {
0222                 // render thumbnails in another thread
0223                 thumbnailsToBuild << entry;
0224             }
0225         }
0226     }
0227     m_list->addItem(i18n("Custom…"), QStringLiteral("custom_file"));
0228 
0229     // select current value
0230     if (!currentValue.isEmpty()) {
0231         int ix = m_list->findData(currentValue);
0232         if (ix > -1) {
0233             m_list->setCurrentIndex(ix);
0234             m_currentIndex = ix;
0235         } else {
0236             // If the project file references a luma file in the Appimage, but the widget lists system installed luma files, try harder to find a match
0237             if (currentValue.startsWith(QStringLiteral("/tmp/.mount_"))) {
0238                 const QString endPath = currentValue.section(QLatin1Char('/'), -2);
0239                 // Parse all entries to see if we have a matching filename
0240                 for (int j = 0; j < m_list->count(); j++) {
0241                     if (m_list->itemData(j, Qt::UserRole).toString().endsWith(endPath)) {
0242                         m_list->setCurrentIndex(j);
0243                         m_currentIndex = j;
0244                         break;
0245                     }
0246                 }
0247             }
0248         }
0249     }
0250     if (!thumbnailsToBuild.isEmpty() && !m_watcher.isRunning()) {
0251 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0252         m_thumbJob = QtConcurrent::run(this, &UrlListParamWidget::buildThumbnails, thumbnailsToBuild);
0253 #else
0254         m_thumbJob = QtConcurrent::run(&UrlListParamWidget::buildThumbnails, this, thumbnailsToBuild);
0255 #endif
0256         m_watcher.setFuture(m_thumbJob);
0257     }
0258 }
0259 
0260 void UrlListParamWidget::buildThumbnails(const QStringList files)
0261 {
0262     for (auto &f : files) {
0263         QImage pix(f);
0264         if (m_abortJobs) {
0265             break;
0266         }
0267         if (!pix.isNull()) {
0268             MainWindow::m_lumacache.insert(f, pix.scaled(50, 30, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0269             QMetaObject::invokeMethod(this, "updateItemThumb", Q_ARG(QString, f));
0270         }
0271     }
0272 }
0273 
0274 void UrlListParamWidget::updateItemThumb(const QString &path)
0275 {
0276     int ix = m_list->findData(path);
0277     if (ix > -1) {
0278         m_list->setItemIcon(ix, QPixmap::fromImage(MainWindow::m_lumacache.value(path)));
0279     }
0280 }
0281 
0282 bool UrlListParamWidget::isValidCubeFile(const QString &path)
0283 {
0284     QFile f(path);
0285     if (f.open(QFile::ReadOnly | QFile::Text)) {
0286         int lineCt = 0;
0287         QTextStream in(&f);
0288         while (!in.atEnd() && lineCt < 30) {
0289             QString line = in.readLine();
0290             if (line.contains(QStringLiteral("LUT_3D_SIZE"))) {
0291                 f.close();
0292                 return true;
0293             }
0294         }
0295         f.close();
0296     }
0297     return false;
0298 }
0299 
0300 void UrlListParamWidget::openFile()
0301 {
0302     QString path = KRecentDirs::dir(QStringLiteral(":KdenliveUrlListParamFolder"));
0303     QString filter = m_model->data(m_index, AssetParameterModel::FilterRole).toString();
0304 
0305     if (path.isEmpty()) {
0306         path = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);
0307     }
0308 
0309     QString urlString = QFileDialog::getOpenFileName(this, QString(), path, filter);
0310 
0311     if (!urlString.isEmpty()) {
0312         KRecentDirs::add(QStringLiteral(":KdenliveUrlListParamFolder"), QUrl(urlString).adjusted(QUrl::RemoveFilename).toString());
0313         if (m_isLutList && urlString.toLower().endsWith(QLatin1String(".cube"))) {
0314             if (isValidCubeFile(urlString)) {
0315                 Q_EMIT valueChanged(m_index, urlString, true);
0316                 slotRefresh();
0317                 return;
0318             } else {
0319                 pCore->displayMessage(i18n("Invalid LUT file %1", urlString), ErrorMessage);
0320             }
0321         } else {
0322             Q_EMIT valueChanged(m_index, urlString, true);
0323             slotRefresh();
0324             return;
0325         }
0326     }
0327     m_list->setCurrentIndex(m_currentIndex);
0328 }