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"