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 }