File indexing completed on 2024-05-05 04:53:16
0001 /* 0002 SPDX-FileCopyrightText: 2023 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 "dcresolvedialog.h" 0007 0008 #include <KUrlRequester> 0009 #include <KUrlRequesterDialog> 0010 #include <QDialog> 0011 #include <QFontComboBox> 0012 #include <QFontDatabase> 0013 #include <QPointer> 0014 0015 DCResolveDialog::DCResolveDialog(std::vector<DocumentChecker::DocumentResource> items, const QUrl &projectUrl, QWidget *parent) 0016 : QDialog(parent) 0017 , m_url(projectUrl) 0018 { 0019 setupUi(this); 0020 setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); 0021 0022 m_model = DocumentCheckerTreeModel::construct(items, this); 0023 removeSelected->setEnabled(false); 0024 manualSearch->setEnabled(false); 0025 m_sortModel = std::make_unique<QSortFilterProxyModel>(new QSortFilterProxyModel(this)); 0026 m_sortModel->setSourceModel(m_model.get()); 0027 treeView->setModel(m_sortModel.get()); 0028 treeView->setAlternatingRowColors(true); 0029 treeView->setSortingEnabled(true); 0030 // treeView->header()->resizeSections(QHeaderView::ResizeToContents); 0031 treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0032 bool showTreeView = !m_model.get()->isEmpty(); 0033 treeView->setVisible(showTreeView); 0034 actionButtonBox->setVisible(showTreeView); 0035 0036 connect(removeSelected, &QPushButton::clicked, this, [&]() { 0037 QItemSelectionModel *selectionModel = treeView->selectionModel(); 0038 QModelIndexList selection = selectionModel->selectedRows(); 0039 for (auto &i : selection) { 0040 m_model->removeItem(m_sortModel->mapToSource(i)); 0041 } 0042 checkStatus(); 0043 }); 0044 connect(manualSearch, &QPushButton::clicked, this, &DCResolveDialog::slotEditCurrentItem); 0045 connect(usePlaceholders, &QPushButton::clicked, this, [&]() { 0046 m_model->usePlaceholdersForMissing(); 0047 checkStatus(); 0048 }); 0049 connect(recursiveSearch, &QPushButton::clicked, this, [&]() { 0050 m_searchTimer.start(); 0051 slotRecursiveSearch(); 0052 }); 0053 0054 connect(m_model.get(), &DocumentCheckerTreeModel::searchProgress, this, [&](int current, int total) { 0055 setEnableChangeItems(false); 0056 progressBox->setVisible(true); 0057 progressLabel->setText(i18n("Recursive search: processing clips")); 0058 progressBar->setMinimum(0); 0059 progressBar->setMaximum(total); 0060 progressBar->setValue(current); 0061 }); 0062 0063 connect(m_model.get(), &DocumentCheckerTreeModel::searchDone, this, [&]() { 0064 setEnableChangeItems(true); 0065 progressBox->hide(); 0066 infoLabel->setText(i18n("Recursive search: done in %1 s", QString::number(m_searchTimer.elapsed() / 1000., 'f', 2))); 0067 infoLabel->setMessageType(KMessageWidget::MessageType::Positive); 0068 infoLabel->animatedShow(); 0069 infoLabel->setCloseButtonVisible(true); 0070 }); 0071 0072 QItemSelectionModel *selectionModel = treeView->selectionModel(); 0073 connect(selectionModel, &QItemSelectionModel::selectionChanged, this, &DCResolveDialog::newSelection); 0074 0075 progressBox->setVisible(false); 0076 infoLabel->setVisible(false); 0077 recreateProxies->setEnabled(false); 0078 initProxyPanel(items); 0079 0080 checkStatus(); 0081 adjustSize(); 0082 } 0083 0084 void DCResolveDialog::newSelection(const QItemSelection &, const QItemSelection &) 0085 { 0086 QItemSelectionModel *selectionModel = treeView->selectionModel(); 0087 QModelIndexList selection = selectionModel->selectedRows(); 0088 bool notRemovable = false; 0089 for (auto &i : selection) { 0090 DocumentChecker::DocumentResource resource = m_model->getDocumentResource(m_sortModel->mapToSource(i)); 0091 if (notRemovable == false && (resource.type == DocumentChecker::MissingType::TitleFont || resource.type == DocumentChecker::MissingType::TitleImage || 0092 resource.type == DocumentChecker::MissingType::Effect || resource.type == DocumentChecker::MissingType::Transition)) { 0093 notRemovable = true; 0094 } 0095 } 0096 if (notRemovable) { 0097 removeSelected->setEnabled(false); 0098 } else { 0099 removeSelected->setEnabled(!selection.isEmpty()); 0100 } 0101 usePlaceholders->setEnabled(!selection.isEmpty()); 0102 manualSearch->setEnabled(!selection.isEmpty()); 0103 } 0104 0105 QList<DocumentChecker::DocumentResource> DCResolveDialog::getItems() 0106 { 0107 QList<DocumentChecker::DocumentResource> items = m_model.get()->getDocumentResources(); 0108 for (auto &proxy : m_proxies) { 0109 if (recreateProxies->isChecked()) { 0110 proxy.status = DocumentChecker::MissingStatus::Reload; 0111 } else { 0112 proxy.status = DocumentChecker::MissingStatus::Remove; 0113 } 0114 items.append(proxy); 0115 } 0116 return items; 0117 } 0118 0119 void DCResolveDialog::slotEditCurrentItem() 0120 { 0121 QItemSelectionModel *selectionModel = treeView->selectionModel(); 0122 QModelIndex index = m_sortModel->mapToSource(selectionModel->currentIndex()); 0123 DocumentChecker::DocumentResource resource = m_model->getDocumentResource(index); 0124 0125 if (resource.type == DocumentChecker::MissingType::TitleFont) { 0126 QScopedPointer<QDialog> d(new QDialog(this)); 0127 auto *l = new QVBoxLayout; 0128 auto *fontcombo = new QFontComboBox; 0129 QString selectedFont = resource.newFilePath.isEmpty() ? resource.originalFilePath : resource.newFilePath; 0130 fontcombo->setCurrentFont(QFontInfo(selectedFont).family()); 0131 l->addWidget(fontcombo); 0132 QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); 0133 l->addWidget(box); 0134 connect(box, &QDialogButtonBox::accepted, d.data(), &QDialog::accept); 0135 connect(box, &QDialogButtonBox::rejected, d.data(), &QDialog::reject); 0136 d->setLayout(l); 0137 if (d->exec() == QDialog::Accepted) { 0138 m_model->setItemsNewFilePath(index, fontcombo->currentFont().family(), DocumentChecker::MissingStatus::Fixed); 0139 } 0140 checkStatus(); 0141 return; 0142 } 0143 0144 ClipType::ProducerType type = resource.clipType; 0145 QUrl url; 0146 QString path = resource.newFilePath.isEmpty() ? resource.originalFilePath : resource.newFilePath; 0147 if (type == ClipType::SlideShow) { 0148 path = QFileInfo(path).dir().absolutePath(); 0149 QPointer<KUrlRequesterDialog> dlg(new KUrlRequesterDialog(QUrl::fromLocalFile(path), i18n("Enter new location for folder"), this)); 0150 dlg->urlRequester()->setMode(KFile::Directory | KFile::ExistingOnly); 0151 if (dlg->exec() != QDialog::Accepted) { 0152 delete dlg; 0153 return; 0154 } 0155 url = QUrl::fromLocalFile(QDir(dlg->selectedUrl().path()).absoluteFilePath(QFileInfo(path).fileName())); 0156 // Reset hash to ensure we find it next time 0157 m_model->setItemsFileHash(index, QString()); 0158 delete dlg; 0159 } else { 0160 url = KUrlRequesterDialog::getUrl(QUrl::fromLocalFile(path), this, i18n("Enter new location for file")); 0161 } 0162 if (!url.isValid()) { 0163 return; 0164 } 0165 bool fixed = false; 0166 if (type == ClipType::SlideShow && QFile::exists(url.adjusted(QUrl::RemoveFilename).toLocalFile())) { 0167 fixed = true; 0168 } 0169 if (fixed || QFile::exists(url.toLocalFile())) { 0170 m_model->setItemsNewFilePath(index, url.toLocalFile(), DocumentChecker::MissingStatus::Fixed); 0171 } else { 0172 m_model->setItemsNewFilePath(index, url.toLocalFile(), DocumentChecker::MissingStatus::Missing); 0173 } 0174 checkStatus(); 0175 } 0176 0177 void DCResolveDialog::initProxyPanel(const std::vector<DocumentChecker::DocumentResource> &items) 0178 { 0179 m_proxies.clear(); 0180 for (const auto &item : items) { 0181 if (item.type == DocumentChecker::MissingType::Proxy) { 0182 m_proxies.push_back(item); 0183 } 0184 } 0185 } 0186 0187 void DCResolveDialog::updateStatusLabel(int missingClips, int missingClipsWithProxy, int removedClips, int placeholderClips, int missingProxies, 0188 int recoverableProxies) 0189 { 0190 if (missingClips + removedClips + missingClipsWithProxy + missingProxies == 0) { 0191 statusLabel->hide(); 0192 return; 0193 } 0194 QString statusMessage = i18n("The project contains:"); 0195 statusMessage.append(QStringLiteral("<ul>")); 0196 QStringList missingMessage; 0197 if (missingClips + placeholderClips + removedClips + missingClipsWithProxy > 0) { 0198 statusMessage.append(QStringLiteral("<li>")); 0199 if (missingClips > 0) { 0200 missingMessage << i18np("One missing clip", "%1 missing clips", missingClips); 0201 } 0202 if (missingClipsWithProxy > 0) { 0203 missingMessage << i18np("One missing source with available proxy", "%1 missing sources with available proxy", missingClipsWithProxy); 0204 } 0205 if (placeholderClips > 0) { 0206 missingMessage << i18np("One placeholder clip", "%1 placeholder clips", placeholderClips); 0207 } 0208 if (removedClips > 0) { 0209 missingMessage << i18np("One removed clip", "%1 removed clips", removedClips); 0210 } 0211 statusMessage.append(missingMessage.join(QLatin1String(", "))); 0212 statusMessage.append(QStringLiteral("</li>")); 0213 } 0214 if (missingProxies > 0 || recoverableProxies > 0) { 0215 statusMessage.append(QStringLiteral("<li>")); 0216 QStringList proxyMessage; 0217 if (missingProxies > 0) { 0218 proxyMessage << i18np("One missing proxy", "%1 missing proxies", missingProxies); 0219 } 0220 if (recoverableProxies > 0) { 0221 proxyMessage << i18np("One proxy can be recovered", "%1 proxies can be recovered", recoverableProxies); 0222 } 0223 statusMessage.append(proxyMessage.join(QLatin1String(", "))); 0224 statusMessage.append(QStringLiteral("</li>")); 0225 } 0226 statusMessage.append(QStringLiteral("</ul>")); 0227 statusLabel->setText(statusMessage); 0228 if (recoverableProxies > 0) { 0229 recreateProxies->setEnabled(true); 0230 } else { 0231 recreateProxies->setEnabled(false); 0232 } 0233 } 0234 0235 void DCResolveDialog::slotRecursiveSearch() 0236 { 0237 QString clipFolder = m_url.adjusted(QUrl::RemoveFilename).toLocalFile(); 0238 const QString newpath = QFileDialog::getExistingDirectory(qApp->activeWindow(), i18nc("@title:window", "Clips Folder"), clipFolder); 0239 if (newpath.isEmpty()) { 0240 return; 0241 } 0242 m_model->slotSearchRecursively(newpath); 0243 checkStatus(); 0244 } 0245 0246 void DCResolveDialog::checkStatus() 0247 { 0248 bool status = true; 0249 int missingClips = 0; 0250 int removedClips = 0; 0251 int placeholderClips = 0; 0252 int missingProxies = 0; 0253 int missingWithProxies = 0; 0254 // Proxy clips that cannot be recovered (missing, removed or placeholder clip) 0255 int lostProxies = 0; 0256 QStringList idsToRemove; 0257 QStringList idsNotRecovered; 0258 for (const auto &item : m_model->getDocumentResources()) { 0259 if (item.status == DocumentChecker::MissingStatus::Missing && item.type != DocumentChecker::MissingType::Proxy) { 0260 missingClips++; 0261 idsNotRecovered << item.clipId; 0262 status = false; 0263 } else if (item.status == DocumentChecker::MissingStatus::MissingButProxy) { 0264 missingWithProxies++; 0265 } else if (item.status == DocumentChecker::MissingStatus::Remove) { 0266 idsToRemove << item.clipId; 0267 removedClips++; 0268 } else if (item.status == DocumentChecker::MissingStatus::Placeholder) { 0269 placeholderClips++; 0270 idsNotRecovered << item.clipId; 0271 } 0272 } 0273 for (auto &proxy : m_proxies) { 0274 if (idsToRemove.contains(proxy.clipId)) { 0275 lostProxies++; 0276 } else { 0277 missingProxies++; 0278 if (idsNotRecovered.contains(proxy.clipId)) { 0279 lostProxies++; 0280 } 0281 } 0282 } 0283 updateStatusLabel(missingClips, missingWithProxies, removedClips, placeholderClips, missingProxies, m_proxies.size() - lostProxies); 0284 recursiveSearch->setEnabled(!status || missingWithProxies > 0); 0285 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status); 0286 } 0287 0288 void DCResolveDialog::setEnableChangeItems(bool enabled) 0289 { 0290 recursiveSearch->setEnabled(enabled); 0291 manualSearch->setEnabled(enabled); 0292 removeSelected->setEnabled(enabled); 0293 usePlaceholders->setEnabled(enabled); 0294 }