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 }