File indexing completed on 2024-05-12 05:38:20

0001 /*
0002     SPDX-FileCopyrightText: 2008 Sebastian Kügler <sebas@kde.org>
0003     SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "recentdocuments.h"
0009 
0010 #include <QApplication>
0011 #include <QDir>
0012 #include <QMimeData>
0013 #include <QMimeDatabase>
0014 #include <QMimeType>
0015 
0016 #include <KIO/Job>
0017 #include <KIO/JobUiDelegate>
0018 #include <KIO/JobUiDelegateFactory>
0019 #include <KIO/OpenFileManagerWindowJob>
0020 #include <KIO/OpenUrlJob>
0021 #include <KLocalizedString>
0022 #include <KNotificationJobUiDelegate>
0023 #include <KShell>
0024 
0025 #include <PlasmaActivities/Stats/Query>
0026 #include <PlasmaActivities/Stats/Terms>
0027 
0028 using namespace KActivities::Stats::Terms;
0029 
0030 K_PLUGIN_CLASS_WITH_JSON(RecentDocuments, "plasma-runner-recentdocuments.json")
0031 
0032 RecentDocuments::RecentDocuments(QObject *parent, const KPluginMetaData &metaData)
0033     : KRunner::AbstractRunner(parent, metaData)
0034     , m_actions({KRunner::Action(QStringLiteral("open-folder"), QStringLiteral("document-open-folder"), i18n("Open Containing Folder"))})
0035 {
0036     addSyntax(QStringLiteral(":q:"), i18n("Looks for documents recently used with names matching :q:."));
0037     setMinLetterCount(m_minLetterCount);
0038 }
0039 
0040 void RecentDocuments::match(KRunner::RunnerContext &context)
0041 {
0042     const QString term = context.query();
0043 
0044     if (!m_resultsModel || m_resultsModel->rowCount() == m_maxResults || m_lastLoadedQuery.size() < m_minLetterCount || !term.startsWith(m_lastLoadedQuery)) {
0045         const QLatin1String asterix("*");
0046         const QString termPattern = (term.size() < m_minLetterCount ? QLatin1String() : asterix) + term + asterix;
0047         auto query = UsedResources | Activity::current() | Order::RecentlyUsedFirst | Agent::any() //
0048             | Type::files() // Only show files and not folders
0049             | Limit(m_maxResults) // In case we are in single runner mode, we could get tons of results for one or two letter queries
0050             | Url("/*/*") // we search only for local files
0051             | Title({termPattern}); // check the title, because that is the filename
0052 
0053         // Reuse the model in case our query starts with the previous one. We filter out irrelevant results later on anyway
0054         m_resultsModel.reset(new ResultModel(query));
0055         m_lastLoadedQuery = term;
0056     }
0057 
0058     if (!context.isValid()) {
0059         return; // The initial fetching could take a moment, check the context validity afterward
0060     }
0061     float relevance = 0.75;
0062     QMimeDatabase db;
0063     QList<KRunner::QueryMatch> matches;
0064     for (int i = 0; i < m_resultsModel->rowCount(); ++i) {
0065         const QModelIndex index = m_resultsModel->index(i, 0);
0066 
0067         const auto fileName = m_resultsModel->data(index, ResultModel::TitleRole).toString();
0068         const int indexOfTerm = fileName.indexOf(term, Qt::CaseInsensitive);
0069         if (indexOfTerm == -1) {
0070             continue; // A previous result or a result where the path, but not filename matches
0071         }
0072 
0073         KRunner::QueryMatch match(this);
0074         // We know the term starts with the query, check size to see if it is an exact match
0075         if (term.size() >= 5 && indexOfTerm == 0 && (fileName.size() == term.size() || QFileInfo(fileName).baseName().size() == term.size())) {
0076             match.setRelevance(relevance + 0.1);
0077             match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Highest);
0078         } else if (indexOfTerm == 0 /*startswith, but not equals => smaller relevance boost*/) {
0079             match.setRelevance(relevance + 0.1);
0080             match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::High);
0081         } else {
0082             match.setRelevance(relevance);
0083             match.setCategoryRelevance(KRunner::QueryMatch::CategoryRelevance::Low);
0084         }
0085         const QMimeType mimeType = db.mimeTypeForName(m_resultsModel->data(index, ResultModel::MimeType).toString());
0086         match.setIconName(mimeType.iconName());
0087         const QUrl url = QUrl::fromLocalFile(m_resultsModel->data(index, ResultModel::ResourceRole).toString());
0088         match.setData(QVariant(url));
0089         match.setUrls({url});
0090         match.setId(url.toString());
0091         if (url.isLocalFile()) {
0092             match.setActions(m_actions);
0093         }
0094         match.setText(fileName);
0095         QString destUrlString = KShell::tildeCollapse(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).path());
0096         match.setSubtext(destUrlString);
0097 
0098         relevance -= 0.05;
0099         matches << match;
0100     }
0101     context.addMatches(matches);
0102 }
0103 
0104 void RecentDocuments::run(const KRunner::RunnerContext & /*context*/, const KRunner::QueryMatch &match)
0105 {
0106     const QUrl url = match.data().toUrl();
0107 
0108     if (match.selectedAction()) {
0109         KIO::highlightInFileManager({url});
0110         return;
0111     }
0112 
0113     auto *job = new KIO::OpenUrlJob(url);
0114     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, QApplication::activeWindow()));
0115     job->setShowOpenOrExecuteDialog(true);
0116     job->start();
0117 }
0118 
0119 #include "recentdocuments.moc"