File indexing completed on 2024-05-05 17:45:01

0001 /*
0002     SPDX-FileCopyrightText: 2008 David Edmundson <kde@davidedmundson.co.uk>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "placesrunner.h"
0008 
0009 #include <QCoreApplication>
0010 #include <QThread>
0011 #include <QTimer>
0012 
0013 #include <QDebug>
0014 #include <QIcon>
0015 #include <QMimeData>
0016 #include <QUrl>
0017 
0018 #include <KIO/OpenUrlJob>
0019 #include <KLocalizedString>
0020 #include <KNotificationJobUiDelegate>
0021 
0022 K_PLUGIN_CLASS_WITH_JSON(PlacesRunner, "plasma-runner-places.json")
0023 
0024 // Q_DECLARE_METATYPE(Plasma::RunnerContext)
0025 PlacesRunner::PlacesRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args)
0026     : Plasma::AbstractRunner(parent, metaData, args)
0027 {
0028     setObjectName(QStringLiteral("Places"));
0029     Plasma::RunnerSyntax defaultSyntax(i18n("places"), i18n("Lists all file manager locations"));
0030     addSyntax(defaultSyntax);
0031     addSyntax(Plasma::RunnerSyntax(QStringLiteral(":q:"), i18n("Finds file manager locations that match :q:")));
0032 
0033     // ensure the bookmarkmanager, etc. in the places model gets creates created in the main thread
0034     // otherwise crashes ensue
0035     m_helper = new PlacesRunnerHelper(this);
0036     setMinLetterCount(3);
0037 }
0038 
0039 PlacesRunner::~PlacesRunner()
0040 {
0041 }
0042 
0043 void PlacesRunner::match(Plasma::RunnerContext &context)
0044 {
0045     if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
0046         // from the main thread
0047         // qDebug() << "calling";
0048         m_helper->match(&context);
0049     } else {
0050         // from the non-gui thread
0051         // qDebug() << "emitting";
0052         Q_EMIT doMatch(&context);
0053     }
0054     // m_helper->match(c);
0055 }
0056 
0057 PlacesRunnerHelper::PlacesRunnerHelper(PlacesRunner *runner)
0058     : QObject(runner)
0059 {
0060     Q_ASSERT(QThread::currentThread() == QCoreApplication::instance()->thread());
0061     connect(runner, &PlacesRunner::doMatch, this, &PlacesRunnerHelper::match, Qt::BlockingQueuedConnection);
0062 
0063     connect(&m_places, &KFilePlacesModel::setupDone, this, [this](const QModelIndex &index, bool success) {
0064         if (success && m_pendingUdi == m_places.deviceForIndex(index).udi()) {
0065             auto *job = new KIO::OpenUrlJob(m_places.url(index));
0066             job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
0067             job->setRunExecutables(false);
0068             job->start();
0069         }
0070         m_pendingUdi.clear();
0071     });
0072 }
0073 
0074 void PlacesRunnerHelper::match(Plasma::RunnerContext *c)
0075 {
0076     Plasma::RunnerContext &context = *c;
0077     if (!context.isValid()) {
0078         return;
0079     }
0080 
0081     const QString term = context.query();
0082     QList<Plasma::QueryMatch> matches;
0083     const bool all = term.compare(i18n("places"), Qt::CaseInsensitive) == 0;
0084     for (int i = 0; i <= m_places.rowCount(); i++) {
0085         QModelIndex current_index = m_places.index(i, 0);
0086         Plasma::QueryMatch::Type type = Plasma::QueryMatch::NoMatch;
0087         qreal relevance = 0;
0088 
0089         const QString text = m_places.text(current_index);
0090         if ((all && !text.isEmpty()) || text.compare(term, Qt::CaseInsensitive) == 0) {
0091             type = Plasma::QueryMatch::ExactMatch;
0092             relevance = all ? 0.9 : 1.0;
0093         } else if (text.contains(term, Qt::CaseInsensitive)) {
0094             type = Plasma::QueryMatch::PossibleMatch;
0095             relevance = 0.7;
0096         }
0097 
0098         if (type != Plasma::QueryMatch::NoMatch) {
0099             Plasma::QueryMatch match(static_cast<PlacesRunner *>(parent()));
0100             match.setType(type);
0101             match.setRelevance(relevance);
0102             match.setIcon(m_places.icon(current_index));
0103             match.setText(text);
0104 
0105             // Add category as subtext so one can tell "Pictures" folder from "Search for Pictures"
0106             // Don't add it if it would match the category ("Places") of the runner to avoid "Places: Pictures (Places)"
0107             const QString groupName = m_places.data(current_index, KFilePlacesModel::GroupRole).toString();
0108             if (!groupName.isEmpty() && static_cast<PlacesRunner *>(parent())->name() != groupName) {
0109                 match.setSubtext(groupName);
0110             }
0111 
0112             // if we have to mount it set the device udi instead of the URL, as we can't open it directly
0113             if (m_places.isDevice(current_index) && m_places.setupNeeded(current_index)) {
0114                 const QString udi = m_places.deviceForIndex(current_index).udi();
0115                 match.setId(udi);
0116                 match.setData(udi);
0117             } else {
0118                 const QUrl url = KFilePlacesModel::convertedUrl(m_places.url(current_index));
0119                 match.setData(url);
0120                 match.setUrls({url});
0121                 match.setId(url.toDisplayString());
0122             }
0123 
0124             matches << match;
0125         }
0126     }
0127 
0128     context.addMatches(matches);
0129 }
0130 
0131 void PlacesRunnerHelper::openDevice(const QString &udi)
0132 {
0133     m_pendingUdi.clear();
0134 
0135     for (int i = 0; i < m_places.rowCount(); ++i) {
0136         const QModelIndex idx = m_places.index(i, 0);
0137         if (m_places.isDevice(idx) && m_places.deviceForIndex(idx).udi() == udi) {
0138             m_pendingUdi = udi;
0139             m_places.requestSetup(idx);
0140             break;
0141         }
0142     }
0143 }
0144 
0145 void PlacesRunner::run(const Plasma::RunnerContext &context, const Plasma::QueryMatch &action)
0146 {
0147     Q_UNUSED(context);
0148     // I don't just pass the model index because the list could change before the user clicks on it, which would make everything go wrong. Ideally we don't want
0149     // things to go wrong.
0150     if (action.data().type() == QVariant::Url) {
0151         auto *job = new KIO::OpenUrlJob(action.data().toUrl());
0152         job->setUiDelegate(new KNotificationJobUiDelegate(KJobUiDelegate::AutoErrorHandlingEnabled));
0153         job->setRunExecutables(false);
0154         job->start();
0155     } else if (action.data().canConvert<QString>()) {
0156         m_helper->openDevice(action.data().toString());
0157     }
0158 }
0159 
0160 #include "placesrunner.moc"