File indexing completed on 2024-11-24 05:01:58
0001 /* 0002 SPDX-FileCopyrightText: 2007 Ivan Cukic <ivan.cukic+kde@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "plasmaappletitemmodel_p.h" 0008 0009 #include <QFileInfo> 0010 #include <QMimeData> 0011 #include <QStandardPaths> 0012 #include <QVersionNumber> 0013 0014 #include "config-workspace.h" 0015 #include <KAboutData> 0016 #include <KConfig> 0017 #include <KJsonUtils> 0018 #include <KLocalizedString> 0019 #include <KPackage/PackageLoader> 0020 #include <KRuntimePlatform> 0021 0022 PlasmaAppletItem::PlasmaAppletItem(const KPluginMetaData &info) 0023 : AbstractItem() 0024 , m_info(info) 0025 , m_runningCount(0) 0026 , m_local(false) 0027 { 0028 const QString _f = PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/" + info.pluginId() + '/'; 0029 QFileInfo dir(QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, _f, QStandardPaths::LocateDirectory)); 0030 m_local = dir.exists() && dir.isWritable(); 0031 0032 setText(m_info.name() + " - " + m_info.category().toLower()); 0033 0034 if (QIcon::hasThemeIcon(info.pluginId())) { 0035 setIcon(QIcon::fromTheme(info.pluginId())); 0036 } else if (!m_info.iconName().isEmpty()) { 0037 setIcon(QIcon::fromTheme(info.iconName())); 0038 } else { 0039 setIcon(QIcon::fromTheme(QStringLiteral("application-x-plasma"))); 0040 } 0041 0042 // set plugininfo parts as roles in the model, only way qml can understand it 0043 setData(name(), PlasmaAppletItemModel::NameRole); 0044 setData(pluginName(), PlasmaAppletItemModel::PluginNameRole); 0045 setData(description(), PlasmaAppletItemModel::DescriptionRole); 0046 setData(category().toLower(), PlasmaAppletItemModel::CategoryRole); 0047 setData(license(), PlasmaAppletItemModel::LicenseRole); 0048 setData(website(), PlasmaAppletItemModel::WebsiteRole); 0049 setData(version(), PlasmaAppletItemModel::VersionRole); 0050 setData(author(), PlasmaAppletItemModel::AuthorRole); 0051 setData(email(), PlasmaAppletItemModel::EmailRole); 0052 setData(apiVersion(), PlasmaAppletItemModel::ApiVersionRole); 0053 setData(isSupported(), PlasmaAppletItemModel::IsSupportedRole); 0054 setData(unsupportedMessage(), PlasmaAppletItemModel::UnsupportedMessageRole); 0055 setData(0, PlasmaAppletItemModel::RunningRole); 0056 setData(m_local, PlasmaAppletItemModel::LocalRole); 0057 } 0058 0059 QString PlasmaAppletItem::pluginName() const 0060 { 0061 return m_info.pluginId(); 0062 } 0063 0064 QString PlasmaAppletItem::name() const 0065 { 0066 return m_info.name(); 0067 } 0068 0069 QString PlasmaAppletItem::description() const 0070 { 0071 return m_info.description(); 0072 } 0073 0074 QString PlasmaAppletItem::license() const 0075 { 0076 return m_info.license(); 0077 } 0078 0079 QString PlasmaAppletItem::category() const 0080 { 0081 return m_info.category(); 0082 } 0083 0084 QString PlasmaAppletItem::website() const 0085 { 0086 return m_info.website(); 0087 } 0088 0089 QString PlasmaAppletItem::version() const 0090 { 0091 return m_info.version(); 0092 } 0093 0094 QString PlasmaAppletItem::author() const 0095 { 0096 if (m_info.authors().isEmpty()) { 0097 return QString(); 0098 } 0099 0100 return m_info.authors().constFirst().name(); 0101 } 0102 0103 QString PlasmaAppletItem::email() const 0104 { 0105 if (m_info.authors().isEmpty()) { 0106 return QString(); 0107 } 0108 0109 return m_info.authors().constFirst().emailAddress(); 0110 } 0111 0112 int PlasmaAppletItem::running() const 0113 { 0114 return m_runningCount; 0115 } 0116 0117 void PlasmaAppletItem::setRunning(int count) 0118 { 0119 m_runningCount = count; 0120 setData(count, PlasmaAppletItemModel::RunningRole); 0121 emitDataChanged(); 0122 } 0123 0124 QString PlasmaAppletItem::apiVersion() const 0125 { 0126 return m_info.value(QStringLiteral("X-Plasma-API-Minimum-Version")); 0127 } 0128 0129 bool PlasmaAppletItem::isSupported() const 0130 { 0131 QVersionNumber version = QVersionNumber::fromString(apiVersion()); 0132 if (version.majorVersion() != 6 /*PROJECT_VERSION_MAJOR*/) { 0133 return false; 0134 } else if (version.minorVersion() > 6 /*PROJECT_VERSION_MINOR*/) { 0135 return false; 0136 } 0137 return true; 0138 } 0139 0140 QString PlasmaAppletItem::unsupportedMessage() const 0141 { 0142 const QString versionString = apiVersion(); 0143 QVersionNumber version = QVersionNumber::fromString(versionString); 0144 0145 if (version.isNull()) { 0146 // TODO: We have to hardcode 6 for now as PROJECT_VERSION_MAJOR is still 5, change it back to PROJECT_VERSION_MAJOR with 6.0 0147 return i18n( 0148 "This Widget was written for an unknown older version of Plasma and is not compatible with Plasma %1. Please contact the widget's author for an " 0149 "updated version.", 0150 6 /*PROJECT_VERSION_MAJOR*/); 0151 } else if (version.majorVersion() < 6 /*PROJECT_VERSION_MAJOR*/) { 0152 return i18n("This Widget was written for Plasma %1 and is not compatible with Plasma %2. Please contact the widget's author for an updated version.", 0153 version.majorVersion(), 0154 6 /*PROJECT_VERSION_MAJOR*/); 0155 } else if (version.majorVersion() > 6 /*PROJECT_VERSION_MAJOR*/) { 0156 return i18n("This Widget was written for Plasma %1 and is not compatible with Plasma %2. Please update Plasma in order to use the widget.", 0157 version.majorVersion(), 0158 6 /*PROJECT_VERSION_MAJOR*/); 0159 } else if (version.minorVersion() > PROJECT_VERSION_MINOR) { 0160 return i18n( 0161 "This Widget was written for Plasma %1 and is not compatible with the latest version of Plasma. Please update Plasma in order to use the widget.", 0162 versionString); 0163 } 0164 0165 return QString(); 0166 } 0167 0168 static bool matchesKeywords(QStringView keywords, const QString &pattern) 0169 { 0170 const auto l = keywords.split(QLatin1Char(';'), Qt::SkipEmptyParts); 0171 for (const auto &keyword : l) { 0172 if (keyword.startsWith(pattern, Qt::CaseInsensitive)) { 0173 return true; 0174 } 0175 } 0176 return false; 0177 } 0178 0179 bool PlasmaAppletItem::matches(const QString &pattern) const 0180 { 0181 const QJsonObject rawData = m_info.rawData(); 0182 if (matchesKeywords(KJsonUtils::readTranslatedString(rawData, QStringLiteral("Keywords")), pattern)) { 0183 return true; 0184 } 0185 0186 // Add English name and keywords so users in other languages won't have to switch IME when searching. 0187 if (!QLocale().name().startsWith(QLatin1String("en_"))) { 0188 const QString name(rawData[QStringLiteral("KPlugin")][QStringLiteral("Name")].toString()); 0189 const QString keywords(rawData[QStringLiteral("KPlugin")][QStringLiteral("Name")].toString()); 0190 if (name.startsWith(pattern, Qt::CaseInsensitive) || matchesKeywords(keywords, pattern)) { 0191 return true; 0192 } 0193 } 0194 0195 return AbstractItem::matches(pattern); 0196 } 0197 0198 QStringList PlasmaAppletItem::keywords() const 0199 { 0200 const static QString keywordsJsonKey = QStringLiteral("X-KDE-Keywords"); 0201 constexpr QLatin1Char separator(','); 0202 0203 const QJsonObject rawData = m_info.rawData(); 0204 if (rawData.contains(keywordsJsonKey)) { 0205 QStringList keywords = m_info.value(keywordsJsonKey).split(separator); 0206 keywords << KJsonUtils::readTranslatedString(rawData, keywordsJsonKey).split(separator); 0207 keywords.removeDuplicates(); 0208 return keywords; 0209 } 0210 return {}; 0211 } 0212 0213 bool PlasmaAppletItem::isLocal() const 0214 { 0215 return m_local; 0216 } 0217 0218 bool PlasmaAppletItem::passesFiltering(const KCategorizedItemsViewModels::Filter &filter) const 0219 { 0220 if (filter.first == QLatin1String("running")) { 0221 return running(); 0222 } else if (filter.first == QLatin1String("local")) { 0223 return isLocal(); 0224 } else if (filter.first == QLatin1String("category")) { 0225 return m_info.category().toLower() == filter.second; 0226 } else { 0227 return false; 0228 } 0229 } 0230 0231 QMimeData *PlasmaAppletItem::mimeData() const 0232 { 0233 QMimeData *data = new QMimeData(); 0234 QByteArray appletName; 0235 appletName += pluginName().toUtf8(); 0236 data->setData(mimeTypes().at(0), appletName); 0237 return data; 0238 } 0239 0240 QStringList PlasmaAppletItem::mimeTypes() const 0241 { 0242 QStringList types; 0243 types << QStringLiteral("text/x-plasmoidservicename"); 0244 return types; 0245 } 0246 0247 QVariant PlasmaAppletItem::data(int role) const 0248 { 0249 switch (role) { 0250 case PlasmaAppletItemModel::ScreenshotRole: 0251 // null = not yet done, empty = tried and failed 0252 if (m_screenshot.isNull()) { 0253 KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet")); 0254 pkg.setDefaultPackageRoot(QStringLiteral("plasma/plasmoids")); 0255 pkg.setPath(m_info.pluginId()); 0256 if (pkg.isValid()) { 0257 const_cast<PlasmaAppletItem *>(this)->m_screenshot = pkg.filePath("screenshot"); 0258 } else { 0259 const_cast<PlasmaAppletItem *>(this)->m_screenshot = QString(); 0260 } 0261 } else if (m_screenshot.isEmpty()) { 0262 return QVariant(); 0263 } 0264 return m_screenshot; 0265 0266 case Qt::DecorationRole: { 0267 // null = not yet done, empty = tried and failed 0268 if (m_icon.isNull()) { 0269 KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/Applet")); 0270 pkg.setDefaultPackageRoot(QStringLiteral("plasma/plasmoids")); 0271 pkg.setPath(m_info.pluginId()); 0272 if (pkg.isValid() && pkg.metadata().iconName().startsWith(QLatin1String("/"))) { 0273 const_cast<PlasmaAppletItem *>(this)->m_icon = pkg.filePath("", pkg.metadata().iconName().toUtf8()); 0274 } else { 0275 const_cast<PlasmaAppletItem *>(this)->m_icon = QString(); 0276 return AbstractItem::data(role); 0277 } 0278 } 0279 if (m_icon.isEmpty()) { 0280 return AbstractItem::data(role); 0281 } 0282 return QIcon(m_icon); 0283 } 0284 0285 default: 0286 return AbstractItem::data(role); 0287 } 0288 } 0289 0290 // PlasmaAppletItemModel 0291 0292 PlasmaAppletItemModel::PlasmaAppletItemModel(QObject *parent) 0293 : QStandardItemModel(parent) 0294 , m_startupCompleted(false) 0295 { 0296 setSortRole(Qt::DisplayRole); 0297 } 0298 0299 QHash<int, QByteArray> PlasmaAppletItemModel::roleNames() const 0300 { 0301 QHash<int, QByteArray> newRoleNames = QAbstractItemModel::roleNames(); 0302 newRoleNames[NameRole] = "name"; 0303 newRoleNames[PluginNameRole] = "pluginName"; 0304 newRoleNames[DescriptionRole] = "description"; 0305 newRoleNames[CategoryRole] = "category"; 0306 newRoleNames[LicenseRole] = "license"; 0307 newRoleNames[WebsiteRole] = "website"; 0308 newRoleNames[VersionRole] = "version"; 0309 newRoleNames[AuthorRole] = "author"; 0310 newRoleNames[EmailRole] = "email"; 0311 newRoleNames[RunningRole] = "running"; 0312 newRoleNames[LocalRole] = "local"; 0313 newRoleNames[ScreenshotRole] = "screenshot"; 0314 newRoleNames[ApiVersionRole] = "apiVersion"; 0315 newRoleNames[IsSupportedRole] = "isSupported"; 0316 newRoleNames[UnsupportedMessageRole] = "unsupportedMessage"; 0317 return newRoleNames; 0318 } 0319 0320 void PlasmaAppletItemModel::populateModel() 0321 { 0322 clear(); 0323 0324 auto filter = [this](const KPluginMetaData &plugin) -> bool { 0325 const QStringList provides = plugin.value(QStringLiteral("X-Plasma-Provides"), QStringList()); 0326 0327 if (!m_provides.isEmpty()) { 0328 const bool providesFulfilled = std::any_of(m_provides.cbegin(), m_provides.cend(), [&provides](const QString &p) { 0329 return provides.contains(p); 0330 }); 0331 0332 if (!providesFulfilled) { 0333 return false; 0334 } 0335 } 0336 0337 if (!plugin.isValid() || plugin.rawData().value(QStringLiteral("NoDisplay")).toBool() || plugin.category() == QLatin1String("Containments")) { 0338 // we don't want to show the hidden category 0339 return false; 0340 } 0341 0342 static const auto formFactors = KRuntimePlatform::runtimePlatform(); 0343 // If runtimePlatformis not defined, accept everything 0344 bool inFormFactor = formFactors.isEmpty(); 0345 0346 for (const QString &formFactor : formFactors) { 0347 if (plugin.formFactors().isEmpty() || plugin.formFactors().contains(formFactor)) { 0348 inFormFactor = true; 0349 break; 0350 } 0351 } 0352 0353 if (!inFormFactor) { 0354 return false; 0355 } 0356 0357 return true; 0358 }; 0359 0360 QList<KPluginMetaData> packages = 0361 KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Applet"), QStringLiteral("plasma/plasmoids"), filter); 0362 0363 // NOTE: Those 2 extra searches are for pure retrocompatibility, to list old plasmoids 0364 // Just to give the user the possibility to remove them. 0365 // Eventually after a year or two, this code can be removed to drop this transition support 0366 0367 // Search all of those that have a desktop file metadata, those are plasma 5 plasmoids 0368 QList<KPackage::Package> kPackages = KPackage::PackageLoader::self()->listKPackages(QStringLiteral("Plasma/Applet"), QStringLiteral("plasma/plasmoids")); 0369 for (const KPackage::Package &package : kPackages) { 0370 KPluginMetaData data = package.metadata(); 0371 if (package.filePath("metadata").endsWith(QStringLiteral("metadata.desktop")) && filter(data)) { 0372 appendRow(new PlasmaAppletItem(data)); 0373 } 0374 } 0375 // Search all packages that have json metadata but not a correct Plasma/Applet and put them at the end: assume they are plasma5 plasmoids 0376 packages.append( 0377 KPackage::PackageLoader::self()->findPackages(QString(), QStringLiteral("plasma/plasmoids"), [&filter](const KPluginMetaData &plugin) -> bool { 0378 return plugin.value(QStringLiteral("KPackageStructure")) != QStringLiteral("Plasma/Applet") && filter(plugin); 0379 })); 0380 0381 for (const KPluginMetaData &plugin : packages) { 0382 appendRow(new PlasmaAppletItem(plugin)); 0383 } 0384 0385 Q_EMIT modelPopulated(); 0386 } 0387 0388 void PlasmaAppletItemModel::setRunningApplets(const QHash<QString, int> &apps) 0389 { 0390 // for each item, find that string and set the count 0391 for (int r = 0; r < rowCount(); ++r) { 0392 QStandardItem *i = item(r); 0393 PlasmaAppletItem *p = dynamic_cast<PlasmaAppletItem *>(i); 0394 0395 if (p) { 0396 const int running = apps.value(p->pluginName()); 0397 p->setRunning(running); 0398 } 0399 } 0400 } 0401 0402 void PlasmaAppletItemModel::setRunningApplets(const QString &name, int count) 0403 { 0404 for (int r = 0; r < rowCount(); ++r) { 0405 QStandardItem *i = item(r); 0406 PlasmaAppletItem *p = dynamic_cast<PlasmaAppletItem *>(i); 0407 if (p && p->pluginName() == name) { 0408 p->setRunning(count); 0409 } 0410 } 0411 } 0412 0413 QStringList PlasmaAppletItemModel::mimeTypes() const 0414 { 0415 QStringList types; 0416 types << QStringLiteral("text/x-plasmoidservicename"); 0417 return types; 0418 } 0419 0420 QMimeData *PlasmaAppletItemModel::mimeData(const QModelIndexList &indexes) const 0421 { 0422 if (indexes.count() <= 0) { 0423 return nullptr; 0424 } 0425 0426 QStringList types = mimeTypes(); 0427 0428 if (types.isEmpty()) { 0429 return nullptr; 0430 } 0431 0432 QMimeData *data = new QMimeData(); 0433 0434 QString format = types.at(0); 0435 0436 QByteArray appletNames; 0437 int lastRow = -1; 0438 for (const QModelIndex &index : indexes) { 0439 if (index.row() == lastRow) { 0440 continue; 0441 } 0442 0443 lastRow = index.row(); 0444 PlasmaAppletItem *selectedItem = (PlasmaAppletItem *)itemFromIndex(index); 0445 appletNames += '\n' + selectedItem->pluginName().toUtf8(); 0446 // qDebug() << selectedItem->pluginName() << index.column() << index.row(); 0447 } 0448 0449 data->setData(format, appletNames); 0450 return data; 0451 } 0452 0453 QStringList PlasmaAppletItemModel::provides() const 0454 { 0455 return m_provides; 0456 } 0457 0458 void PlasmaAppletItemModel::setProvides(const QStringList &provides) 0459 { 0460 if (m_provides == provides) { 0461 return; 0462 } 0463 0464 m_provides = provides; 0465 if (m_startupCompleted) { 0466 populateModel(); 0467 } 0468 } 0469 0470 void PlasmaAppletItemModel::setApplication(const QString &app) 0471 { 0472 m_application = app; 0473 if (m_startupCompleted) { 0474 populateModel(); 0475 } 0476 } 0477 0478 bool PlasmaAppletItemModel::startupCompleted() const 0479 { 0480 return m_startupCompleted; 0481 } 0482 0483 void PlasmaAppletItemModel::setStartupCompleted(bool complete) 0484 { 0485 m_startupCompleted = complete; 0486 } 0487 0488 QString &PlasmaAppletItemModel::Application() 0489 { 0490 return m_application; 0491 } 0492 0493 // #include <plasmaappletitemmodel_p.moc>