File indexing completed on 2024-04-21 14:53:52
0001 /* 0002 This file is part of the KDE project 0003 SPDX-FileCopyrightText: 2007, 2006 Rafael Fernández López <ereslibre@kde.org> 0004 SPDX-FileCopyrightText: 2002-2003 Matthias Kretz <kretz@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.0-only 0007 */ 0008 0009 #include "kpluginselector.h" 0010 0011 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 90) 0012 0013 #include "kpluginselector_p.h" 0014 0015 #include <kcmutils_debug.h> 0016 0017 #include <QApplication> 0018 #include <QCheckBox> 0019 #include <QDialog> 0020 #include <QDialogButtonBox> 0021 #include <QDir> 0022 #include <QDirIterator> 0023 #include <QLabel> 0024 #include <QLineEdit> 0025 #include <QPainter> 0026 #include <QPushButton> 0027 #include <QStandardPaths> 0028 #include <QStyle> 0029 #include <QStyleOptionViewItem> 0030 #include <QVBoxLayout> 0031 0032 #include <KAboutPluginDialog> 0033 #include <KCategorizedSortFilterProxyModel> 0034 #include <KCategorizedView> 0035 #include <KCategoryDrawer> 0036 #include <KLocalizedString> 0037 #include <KMessageBox> 0038 #include <KPluginMetaData> 0039 #include <KStandardGuiItem> 0040 #include <KUrlLabel> 0041 #include <kcmoduleinfo.h> 0042 #include <kcmoduleproxy.h> 0043 0044 #define MARGIN 5 0045 0046 QT_WARNING_PUSH 0047 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0048 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0049 0050 KPluginSelector::Private::Private(KPluginSelector *parent) 0051 : QObject(parent) 0052 , parent(parent) 0053 , listView(nullptr) 0054 , categoryDrawer(nullptr) 0055 , pluginDelegate(nullptr) 0056 , showIcons(false) 0057 , showDefaultIndicator(false) 0058 { 0059 } 0060 0061 KPluginSelector::Private::~Private() 0062 { 0063 } 0064 0065 void KPluginSelector::Private::updateDependencies(PluginEntry *pluginEntry, bool added) 0066 { 0067 if (added) { 0068 QStringList dependencyList = pluginEntry->pluginInfo.dependencies(); 0069 0070 if (dependencyList.isEmpty()) { 0071 return; 0072 } 0073 0074 for (int i = 0; i < pluginModel->rowCount(); i++) { 0075 const QModelIndex index = pluginModel->index(i, 0); 0076 PluginEntry *pe = static_cast<PluginEntry *>(index.internalPointer()); 0077 0078 if ((pe->pluginInfo.pluginName() != pluginEntry->pluginInfo.pluginName()) && dependencyList.contains(pe->pluginInfo.pluginName()) && !pe->checked) { 0079 dependenciesWidget->addDependency(pe->pluginInfo.name(), pluginEntry->pluginInfo.name(), added); 0080 const_cast<QAbstractItemModel *>(index.model())->setData(index, added, Qt::CheckStateRole); 0081 updateDependencies(pe, added); 0082 } 0083 } 0084 } else { 0085 for (int i = 0; i < pluginModel->rowCount(); i++) { 0086 const QModelIndex index = pluginModel->index(i, 0); 0087 PluginEntry *pe = static_cast<PluginEntry *>(index.internalPointer()); 0088 0089 if ((pe->pluginInfo.pluginName() != pluginEntry->pluginInfo.pluginName()) 0090 && pe->pluginInfo.dependencies().contains(pluginEntry->pluginInfo.pluginName()) && pe->checked) { 0091 dependenciesWidget->addDependency(pe->pluginInfo.name(), pluginEntry->pluginInfo.name(), added); 0092 const_cast<QAbstractItemModel *>(index.model())->setData(index, added, Qt::CheckStateRole); 0093 updateDependencies(pe, added); 0094 } 0095 } 0096 } 0097 } 0098 0099 int KPluginSelector::Private::dependantLayoutValue(int value, int width, int totalWidth) const 0100 { 0101 if (listView->layoutDirection() == Qt::LeftToRight) { 0102 return value; 0103 } 0104 0105 return totalWidth - width - value; 0106 } 0107 0108 KPluginSelector::Private::DependenciesWidget::DependenciesWidget(QWidget *parent) 0109 : QWidget(parent) 0110 , addedByDependencies(0) 0111 , removedByDependencies(0) 0112 { 0113 setVisible(false); 0114 0115 details = new QLabel(); 0116 0117 QHBoxLayout *layout = new QHBoxLayout(this); 0118 0119 QVBoxLayout *dataLayout = new QVBoxLayout; 0120 dataLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); 0121 layout->setAlignment(Qt::AlignLeft); 0122 QLabel *label = new QLabel(); 0123 label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); 0124 label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize))); 0125 label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0126 layout->addWidget(label); 0127 KUrlLabel *link = new KUrlLabel(); 0128 link->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); 0129 link->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 0130 link->setGlowEnabled(false); 0131 link->setUnderline(false); 0132 link->setFloatEnabled(true); 0133 link->setUseCursor(true); 0134 link->setHighlightedColor(palette().color(QPalette::Link)); 0135 link->setSelectedColor(palette().color(QPalette::Link)); 0136 link->setText(i18n("Automatic changes have been performed due to plugin dependencies. Click here for further information")); 0137 dataLayout->addWidget(link); 0138 dataLayout->addWidget(details); 0139 layout->addLayout(dataLayout); 0140 0141 QObject::connect(link, &KUrlLabel::leftClickedUrl, this, &KPluginSelector::Private::DependenciesWidget::showDependencyDetails); 0142 } 0143 0144 KPluginSelector::Private::DependenciesWidget::~DependenciesWidget() 0145 { 0146 } 0147 0148 void KPluginSelector::Private::DependenciesWidget::addDependency(const QString &dependency, const QString &pluginCausant, bool added) 0149 { 0150 if (!isVisible()) { 0151 setVisible(true); 0152 } 0153 0154 struct FurtherInfo furtherInfo; 0155 furtherInfo.added = added; 0156 furtherInfo.pluginCausant = pluginCausant; 0157 0158 if (dependencyMap.contains(dependency)) { // The dependency moved from added to removed or vice-versa 0159 if (added && removedByDependencies) { 0160 removedByDependencies--; 0161 } else if (addedByDependencies) { 0162 addedByDependencies--; 0163 } 0164 0165 dependencyMap[dependency] = furtherInfo; 0166 } else { 0167 dependencyMap.insert(dependency, furtherInfo); 0168 } 0169 0170 if (added) { 0171 addedByDependencies++; 0172 } else { 0173 removedByDependencies++; 0174 } 0175 0176 updateDetails(); 0177 } 0178 0179 void KPluginSelector::Private::DependenciesWidget::userOverrideDependency(const QString &dependency) 0180 { 0181 if (dependencyMap.contains(dependency)) { 0182 if (addedByDependencies && dependencyMap[dependency].added) { 0183 addedByDependencies--; 0184 } else if (removedByDependencies) { 0185 removedByDependencies--; 0186 } 0187 0188 dependencyMap.remove(dependency); 0189 } 0190 0191 updateDetails(); 0192 } 0193 0194 void KPluginSelector::Private::DependenciesWidget::clearDependencies() 0195 { 0196 addedByDependencies = 0; 0197 removedByDependencies = 0; 0198 dependencyMap.clear(); 0199 updateDetails(); 0200 } 0201 0202 void KPluginSelector::Private::DependenciesWidget::showDependencyDetails() 0203 { 0204 QString message = i18n("Automatic changes have been performed in order to satisfy plugin dependencies:\n"); 0205 0206 for (auto it = dependencyMap.cbegin(); it != dependencyMap.cend(); ++it) { 0207 const QString &dependency = it.key(); 0208 const FurtherInfo &info = it.value(); 0209 if (info.added) { 0210 message += i18n("\n %1 plugin has been automatically checked because of the dependency of %2 plugin", dependency, info.pluginCausant); 0211 } else { 0212 message += i18n("\n %1 plugin has been automatically unchecked because of its dependency on %2 plugin", dependency, info.pluginCausant); 0213 } 0214 } 0215 0216 KMessageBox::information(this, message, i18n("Dependency Check")); 0217 0218 addedByDependencies = 0; 0219 removedByDependencies = 0; 0220 updateDetails(); 0221 } 0222 0223 void KPluginSelector::Private::DependenciesWidget::updateDetails() 0224 { 0225 if (dependencyMap.isEmpty()) { 0226 setVisible(false); 0227 return; 0228 } 0229 0230 QString message; 0231 0232 if (addedByDependencies) { 0233 message += 0234 i18np("%1 plugin automatically added due to plugin dependencies", "%1 plugins automatically added due to plugin dependencies", addedByDependencies); 0235 } 0236 0237 if (removedByDependencies && !message.isEmpty()) { 0238 message += i18n(", "); 0239 } 0240 0241 if (removedByDependencies) { 0242 message += i18np("%1 plugin automatically removed due to plugin dependencies", 0243 "%1 plugins automatically removed due to plugin dependencies", 0244 removedByDependencies); 0245 } 0246 0247 if (message.isEmpty()) { 0248 details->setVisible(false); 0249 } else { 0250 details->setVisible(true); 0251 details->setText(message); 0252 } 0253 } 0254 0255 KPluginSelector::KPluginSelector(QWidget *parent) 0256 : QWidget(parent) 0257 , d(new Private(this)) 0258 { 0259 QVBoxLayout *layout = new QVBoxLayout(this); 0260 layout->setContentsMargins(0, 0, 0, 0); 0261 0262 d->lineEdit = new QLineEdit(this); 0263 d->lineEdit->setClearButtonEnabled(true); 0264 d->lineEdit->setPlaceholderText(i18n("Search...")); 0265 d->listView = new KCategorizedView(this); 0266 d->categoryDrawer = new KCategoryDrawer(d->listView); 0267 d->listView->setVerticalScrollMode(QListView::ScrollPerPixel); 0268 d->listView->setAlternatingRowColors(true); 0269 d->listView->setCategoryDrawer(d->categoryDrawer); 0270 d->dependenciesWidget = new Private::DependenciesWidget(this); 0271 0272 d->pluginModel = new Private::PluginModel(d, this); 0273 d->proxyModel = new Private::ProxyModel(d, this); 0274 d->proxyModel->setCategorizedModel(true); 0275 d->proxyModel->setSourceModel(d->pluginModel); 0276 d->listView->setModel(d->proxyModel); 0277 d->listView->setAlternatingRowColors(true); 0278 0279 Private::PluginDelegate *pluginDelegate = new Private::PluginDelegate(d, this); 0280 d->listView->setItemDelegate(pluginDelegate); 0281 0282 d->listView->setMouseTracking(true); 0283 d->listView->viewport()->setAttribute(Qt::WA_Hover); 0284 0285 connect(d->lineEdit, &QLineEdit::textChanged, d->proxyModel, &QSortFilterProxyModel::invalidate); 0286 connect(pluginDelegate, &Private::PluginDelegate::changed, this, &KPluginSelector::changed); 0287 connect(pluginDelegate, &Private::PluginDelegate::configCommitted, this, &KPluginSelector::configCommitted); 0288 connect(this, &KPluginSelector::defaultsIndicatorsVisible, pluginDelegate, &Private::PluginDelegate::slotResetModel); 0289 0290 connect(this, &KPluginSelector::changed, [this] { 0291 Q_EMIT defaulted(isDefault()); 0292 }); 0293 0294 layout->addWidget(d->lineEdit); 0295 layout->addWidget(d->listView); 0296 layout->addWidget(d->dependenciesWidget); 0297 0298 // When a KPluginSelector instance gets focus, 0299 // it should pass over the focus to its child searchbar. 0300 setFocusProxy(d->lineEdit); 0301 } 0302 0303 KPluginSelector::~KPluginSelector() 0304 { 0305 delete d->listView->itemDelegate(); 0306 delete d->listView; // depends on some other things in d, make sure this dies first. 0307 delete d; 0308 } 0309 0310 void KPluginSelector::addPlugins(const QString &componentName, const QString &categoryName, const QString &categoryKey, KSharedConfig::Ptr config) 0311 { 0312 QStringList desktopFileNames; 0313 const QStringList dirs = 0314 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, componentName + QStringLiteral("/kpartplugins"), QStandardPaths::LocateDirectory); 0315 for (const QString &dir : dirs) { 0316 QDirIterator it(dir, QStringList() << QStringLiteral("*.desktop"), QDir::NoFilter, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); 0317 while (it.hasNext()) { 0318 desktopFileNames.append(it.next()); 0319 } 0320 } 0321 0322 QList<KPluginInfo> pluginInfoList = KPluginInfo::fromFiles(desktopFileNames); 0323 0324 if (pluginInfoList.isEmpty()) { 0325 return; 0326 } 0327 0328 if (!config) { 0329 config = KSharedConfig::openConfig(componentName + QStringLiteral("rc")); 0330 } 0331 Q_ASSERT(config); 0332 0333 KConfigGroup cfgGroup(config, "KParts Plugins"); 0334 // qDebug() << "cfgGroup = " << &cfgGroup; 0335 0336 d->pluginModel->addPlugins(pluginInfoList, categoryName, categoryKey, cfgGroup); 0337 d->proxyModel->sort(0); 0338 } 0339 0340 void KPluginSelector::addPlugins(const QList<KPluginInfo> &pluginInfoList, 0341 PluginLoadMethod pluginLoadMethod, 0342 const QString &categoryName, 0343 const QString &categoryKey, 0344 const KSharedConfig::Ptr &config) 0345 { 0346 if (pluginInfoList.isEmpty()) { 0347 return; 0348 } 0349 0350 KConfigGroup cfgGroup(config ? config : KSharedConfig::openConfig(), "Plugins"); 0351 // qDebug() << "cfgGroup = " << &cfgGroup; 0352 0353 d->pluginModel->addPlugins(pluginInfoList, categoryName, categoryKey, cfgGroup, pluginLoadMethod, true /* manually added */); 0354 d->proxyModel->sort(0); 0355 } 0356 0357 void KPluginSelector::clearPlugins() 0358 { 0359 d->pluginModel->clear(); 0360 } 0361 0362 void KPluginSelector::load() 0363 { 0364 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0365 const QModelIndex index = d->pluginModel->index(i, 0); 0366 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0367 pluginEntry->pluginInfo.load(pluginEntry->cfgGroup); 0368 d->pluginModel->setData(index, pluginEntry->pluginInfo.isPluginEnabled(), Qt::CheckStateRole); 0369 } 0370 0371 static_cast<KPluginSelector::Private::PluginDelegate *>(d->listView->itemDelegate())->clearChangedEntries(); 0372 Q_EMIT changed(false); 0373 } 0374 0375 void KPluginSelector::save() 0376 { 0377 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0378 const QModelIndex index = d->pluginModel->index(i, 0); 0379 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0380 pluginEntry->pluginInfo.setPluginEnabled(pluginEntry->checked); 0381 pluginEntry->pluginInfo.save(pluginEntry->cfgGroup); 0382 pluginEntry->cfgGroup.sync(); 0383 } 0384 0385 static_cast<KPluginSelector::Private::PluginDelegate *>(d->listView->itemDelegate())->clearChangedEntries(); 0386 Q_EMIT changed(false); 0387 } 0388 0389 bool KPluginSelector::isSaveNeeded() const 0390 { 0391 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0392 const QModelIndex index = d->pluginModel->index(i, 0); 0393 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0394 if (d->pluginModel->data(index, Qt::CheckStateRole).toBool() != pluginEntry->pluginInfo.isPluginEnabled()) { 0395 return true; 0396 } 0397 } 0398 0399 return false; 0400 } 0401 0402 void KPluginSelector::defaults() 0403 { 0404 bool isChanged = false; 0405 auto delegate = static_cast<KPluginSelector::Private::PluginDelegate *>(d->listView->itemDelegate()); 0406 delegate->clearChangedEntries(); 0407 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0408 const QModelIndex index = d->pluginModel->index(i, 0); 0409 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0410 bool entryChanged = pluginEntry->pluginInfo.isPluginEnabled() != pluginEntry->pluginInfo.isPluginEnabledByDefault(); 0411 isChanged |= entryChanged; 0412 d->pluginModel->setData(index, pluginEntry->pluginInfo.isPluginEnabledByDefault(), Qt::CheckStateRole); 0413 if (entryChanged) { 0414 delegate->addChangedEntry(pluginEntry); 0415 } 0416 } 0417 0418 Q_EMIT changed(isChanged); 0419 } 0420 0421 bool KPluginSelector::isDefault() const 0422 { 0423 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0424 const QModelIndex index = d->pluginModel->index(i, 0); 0425 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0426 if (d->pluginModel->data(index, Qt::CheckStateRole).toBool() != pluginEntry->pluginInfo.isPluginEnabledByDefault()) { 0427 return false; 0428 } 0429 } 0430 0431 return true; 0432 } 0433 0434 void KPluginSelector::updatePluginsState() 0435 { 0436 static_cast<KPluginSelector::Private::PluginDelegate *>(d->listView->itemDelegate())->clearChangedEntries(); 0437 for (int i = 0; i < d->pluginModel->rowCount(); i++) { 0438 const QModelIndex index = d->pluginModel->index(i, 0); 0439 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0440 if (pluginEntry->manuallyAdded) { 0441 pluginEntry->pluginInfo.setPluginEnabled(pluginEntry->checked); 0442 } 0443 } 0444 } 0445 0446 void KPluginSelector::setConfigurationArguments(const QStringList &arguments) 0447 { 0448 d->kcmArguments = arguments; 0449 } 0450 0451 QStringList KPluginSelector::configurationArguments() const 0452 { 0453 return d->kcmArguments; 0454 } 0455 0456 void KPluginSelector::showConfiguration(const QString &componentName) 0457 { 0458 QModelIndex idx; 0459 for (int i = 0, c = d->proxyModel->rowCount(); i < c; ++i) { 0460 const auto currentIndex = d->proxyModel->index(i, 0); 0461 const auto entry = currentIndex.data(KPluginSelector::Private::PluginEntryRole).value<PluginEntry *>(); 0462 if (entry->pluginInfo.pluginName() == componentName) { 0463 idx = currentIndex; 0464 break; 0465 } 0466 } 0467 0468 if (idx.isValid()) { 0469 auto delegate = static_cast<KPluginSelector::Private::PluginDelegate *>(d->listView->itemDelegate()); 0470 delegate->configure(idx); 0471 } else { 0472 qCWarning(KCMUTILS_LOG) << "Could not find plugin" << componentName; 0473 } 0474 } 0475 0476 void KPluginSelector::setAdditionalButtonHandler(std::function<QPushButton *(const KPluginInfo &)> handler) 0477 { 0478 static_cast<Private::PluginDelegate *>(d->listView->itemDelegate())->setHandler(handler); 0479 } 0480 0481 void KPluginSelector::setDefaultsIndicatorsVisible(bool isVisible) 0482 { 0483 if (isVisible != d->showDefaultIndicator) { 0484 d->showDefaultIndicator = isVisible; 0485 Q_EMIT defaultsIndicatorsVisible(); 0486 } 0487 } 0488 0489 KPluginSelector::Private::PluginModel::PluginModel(KPluginSelector::Private *pluginSelector_d, QObject *parent) 0490 : QAbstractListModel(parent) 0491 , pluginSelector_d(pluginSelector_d) 0492 { 0493 } 0494 0495 KPluginSelector::Private::PluginModel::~PluginModel() 0496 { 0497 } 0498 0499 static bool hasServiceNoDisplaySet(const KPluginInfo &pluginInfo) 0500 { 0501 QT_WARNING_PUSH 0502 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations") 0503 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations") 0504 return pluginInfo.service() && pluginInfo.service()->noDisplay(); 0505 QT_WARNING_POP 0506 } 0507 0508 void KPluginSelector::Private::PluginModel::addPlugins(const QList<KPluginInfo> &pluginList, 0509 const QString &categoryName, 0510 const QString &categoryKey, 0511 const KConfigGroup &cfgGroup, 0512 PluginLoadMethod pluginLoadMethod, 0513 bool manuallyAdded) 0514 { 0515 QList<PluginEntry> listToAdd; 0516 0517 for (const KPluginInfo &pluginInfo : pluginList) { 0518 PluginEntry pluginEntry; 0519 pluginEntry.category = categoryName; 0520 pluginEntry.pluginInfo = pluginInfo; 0521 if (pluginLoadMethod == ReadConfigFile) { 0522 pluginEntry.pluginInfo.load(cfgGroup); 0523 } 0524 pluginEntry.checked = pluginInfo.isPluginEnabled(); 0525 pluginEntry.manuallyAdded = manuallyAdded; 0526 if (cfgGroup.isValid()) { 0527 pluginEntry.cfgGroup = cfgGroup; 0528 } else { 0529 pluginEntry.cfgGroup = pluginInfo.config(); 0530 } 0531 0532 // this is where kiosk will set if a plugin is checkable or not (pluginName + "Enabled") 0533 pluginEntry.isCheckable = !pluginInfo.isValid() || !pluginEntry.cfgGroup.isEntryImmutable(pluginInfo.pluginName() + QLatin1String("Enabled")); 0534 0535 if (!pluginEntryList.contains(pluginEntry) && !listToAdd.contains(pluginEntry) 0536 && (categoryKey.isEmpty() || !pluginInfo.category().compare(categoryKey, Qt::CaseInsensitive)) && (!hasServiceNoDisplaySet(pluginInfo))) { 0537 listToAdd << pluginEntry; 0538 0539 if (!pluginSelector_d->showIcons && !pluginInfo.icon().isEmpty()) { 0540 pluginSelector_d->showIcons = true; 0541 } 0542 } 0543 } 0544 0545 if (!listToAdd.isEmpty()) { 0546 beginInsertRows(QModelIndex(), pluginEntryList.count(), pluginEntryList.count() + listToAdd.count() - 1); 0547 pluginEntryList << listToAdd; 0548 endInsertRows(); 0549 } 0550 } 0551 0552 void KPluginSelector::Private::PluginModel::clear() 0553 { 0554 beginResetModel(); 0555 pluginEntryList.clear(); 0556 endResetModel(); 0557 } 0558 0559 QModelIndex KPluginSelector::Private::PluginModel::index(int row, int column, const QModelIndex &parent) const 0560 { 0561 Q_UNUSED(parent) 0562 0563 return createIndex(row, column, (row < pluginEntryList.count()) ? (void *)&pluginEntryList.at(row) : nullptr); 0564 } 0565 0566 QVariant KPluginSelector::Private::PluginModel::data(const QModelIndex &index, int role) const 0567 { 0568 if (!index.isValid() || !index.internalPointer()) { 0569 return QVariant(); 0570 } 0571 0572 PluginEntry *pluginEntry = static_cast<PluginEntry *>(index.internalPointer()); 0573 0574 switch (role) { 0575 case Qt::DisplayRole: 0576 return pluginEntry->pluginInfo.name(); 0577 case PluginEntryRole: 0578 return QVariant::fromValue(pluginEntry); 0579 case ServicesCountRole: 0580 return pluginEntry->pluginInfo.kcmServices().count() + 0581 // if we have a X-KDE-ConfigModule key, we know that we have a config module 0582 (pluginEntry->pluginInfo.property(QStringLiteral("X-KDE-ConfigModule")).toString().isEmpty() ? 0 : 1); 0583 case NameRole: 0584 return pluginEntry->pluginInfo.name(); 0585 case CommentRole: 0586 return pluginEntry->pluginInfo.comment(); 0587 case AuthorRole: 0588 return pluginEntry->pluginInfo.author(); 0589 case EmailRole: 0590 return pluginEntry->pluginInfo.email(); 0591 case WebsiteRole: 0592 return pluginEntry->pluginInfo.website(); 0593 case VersionRole: 0594 return pluginEntry->pluginInfo.version(); 0595 case LicenseRole: 0596 return pluginEntry->pluginInfo.license(); 0597 case DependenciesRole: 0598 return pluginEntry->pluginInfo.dependencies(); 0599 case IsCheckableRole: 0600 return pluginEntry->isCheckable; 0601 case Qt::DecorationRole: 0602 return pluginEntry->pluginInfo.icon(); 0603 case Qt::CheckStateRole: 0604 return pluginEntry->checked; 0605 case KCategorizedSortFilterProxyModel::CategoryDisplayRole: // fall through 0606 case KCategorizedSortFilterProxyModel::CategorySortRole: 0607 return pluginEntry->category; 0608 default: 0609 return QVariant(); 0610 } 0611 } 0612 0613 bool KPluginSelector::Private::PluginModel::setData(const QModelIndex &index, const QVariant &value, int role) 0614 { 0615 if (!index.isValid()) { 0616 return false; 0617 } 0618 0619 bool ret = false; 0620 0621 if (role == Qt::CheckStateRole) { 0622 static_cast<PluginEntry *>(index.internalPointer())->checked = value.toBool(); 0623 ret = true; 0624 } 0625 0626 if (ret) { 0627 Q_EMIT dataChanged(index, index); 0628 } 0629 0630 return ret; 0631 } 0632 0633 int KPluginSelector::Private::PluginModel::rowCount(const QModelIndex &parent) const 0634 { 0635 if (parent.isValid()) { 0636 return 0; 0637 } 0638 0639 return pluginEntryList.count(); 0640 } 0641 0642 KPluginSelector::Private::ProxyModel::ProxyModel(KPluginSelector::Private *pluginSelector_d, QObject *parent) 0643 : KCategorizedSortFilterProxyModel(parent) 0644 , pluginSelector_d(pluginSelector_d) 0645 { 0646 sort(0); 0647 } 0648 0649 KPluginSelector::Private::ProxyModel::~ProxyModel() 0650 { 0651 } 0652 0653 bool KPluginSelector::Private::ProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 0654 { 0655 Q_UNUSED(sourceParent) 0656 0657 if (!pluginSelector_d->lineEdit->text().isEmpty()) { 0658 const QModelIndex index = sourceModel()->index(sourceRow, 0); 0659 const KPluginInfo pluginInfo = static_cast<PluginEntry *>(index.internalPointer())->pluginInfo; 0660 return pluginInfo.name().contains(pluginSelector_d->lineEdit->text(), Qt::CaseInsensitive) 0661 || pluginInfo.comment().contains(pluginSelector_d->lineEdit->text(), Qt::CaseInsensitive); 0662 } 0663 0664 return true; 0665 } 0666 0667 bool KPluginSelector::Private::ProxyModel::subSortLessThan(const QModelIndex &left, const QModelIndex &right) const 0668 { 0669 return static_cast<PluginEntry *>(left.internalPointer()) 0670 ->pluginInfo.name() 0671 .compare(static_cast<PluginEntry *>(right.internalPointer())->pluginInfo.name(), Qt::CaseInsensitive) 0672 < 0; 0673 } 0674 0675 KPluginSelector::Private::PluginDelegate::PluginDelegate(KPluginSelector::Private *pluginSelector_d, QObject *parent) 0676 : KWidgetItemDelegate(pluginSelector_d->listView, parent) 0677 , checkBox(new QCheckBox) 0678 , pushButton(new QPushButton) 0679 , pluginSelector_d(pluginSelector_d) 0680 { 0681 pushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); // only for getting size matters 0682 } 0683 0684 KPluginSelector::Private::PluginDelegate::~PluginDelegate() 0685 { 0686 delete checkBox; 0687 delete pushButton; 0688 } 0689 0690 void KPluginSelector::Private::PluginDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0691 { 0692 if (!index.isValid()) { 0693 return; 0694 } 0695 0696 int xOffset = checkBox->sizeHint().width(); 0697 bool disabled = !index.model()->data(index, IsCheckableRole).toBool(); 0698 0699 painter->save(); 0700 0701 QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr); 0702 0703 int iconSize = option.rect.height() - MARGIN * 2; 0704 if (pluginSelector_d->showIcons) { 0705 QIcon icon = QIcon::fromTheme(index.model()->data(index, Qt::DecorationRole).toString()); 0706 icon.paint(painter, 0707 QRect(pluginSelector_d->dependantLayoutValue(MARGIN + option.rect.left() + xOffset, iconSize, option.rect.width()), 0708 MARGIN + option.rect.top(), 0709 iconSize, 0710 iconSize)); 0711 } else { 0712 iconSize = -MARGIN; 0713 } 0714 0715 QRect contentsRect(pluginSelector_d->dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left() + xOffset, 0716 option.rect.width() - MARGIN * 3 - iconSize - xOffset, 0717 option.rect.width()), 0718 MARGIN + option.rect.top(), 0719 option.rect.width() - MARGIN * 3 - iconSize - xOffset, 0720 option.rect.height() - MARGIN * 2); 0721 0722 int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); 0723 if (index.model()->data(index, ServicesCountRole).toBool()) { 0724 lessHorizontalSpace += MARGIN + pushButton->sizeHint().width(); 0725 } 0726 // Reserve space for extra button 0727 if (handler) { 0728 lessHorizontalSpace += MARGIN + pushButton->sizeHint().width(); 0729 } 0730 0731 contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); 0732 0733 if (option.state & QStyle::State_Selected) { 0734 painter->setPen(option.palette.highlightedText().color()); 0735 } 0736 0737 if (pluginSelector_d->listView->layoutDirection() == Qt::RightToLeft) { 0738 contentsRect.translate(lessHorizontalSpace, 0); 0739 } 0740 0741 painter->save(); 0742 if (disabled) { 0743 QPalette pal(option.palette); 0744 pal.setCurrentColorGroup(QPalette::Disabled); 0745 painter->setPen(pal.text().color()); 0746 } 0747 0748 painter->save(); 0749 QFont font = titleFont(option.font); 0750 QFontMetrics fmTitle(font); 0751 painter->setFont(font); 0752 painter->drawText(contentsRect, 0753 Qt::AlignLeft | Qt::AlignTop, 0754 fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); 0755 painter->restore(); 0756 0757 painter->drawText(contentsRect, 0758 Qt::AlignLeft | Qt::AlignBottom, 0759 option.fontMetrics.elidedText(index.model()->data(index, CommentRole).toString(), Qt::ElideRight, contentsRect.width())); 0760 0761 painter->restore(); 0762 painter->restore(); 0763 } 0764 0765 QSize KPluginSelector::Private::PluginDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0766 { 0767 int i = 5; 0768 int j = 1; 0769 if (index.model()->data(index, ServicesCountRole).toBool()) { 0770 i = 6; 0771 j = 2; 0772 } 0773 // Reserve space for extra button 0774 if (handler) { 0775 ++j; 0776 } 0777 0778 if (!pluginSelector_d->showIcons) { 0779 i--; 0780 } 0781 0782 const QFont font = titleFont(option.font); 0783 const QFontMetrics fmTitle(font); 0784 const QString text = index.model()->data(index, Qt::DisplayRole).toString(); 0785 const QString comment = index.model()->data(index, CommentRole).toString(); 0786 const int maxTextWidth = qMax(fmTitle.boundingRect(text).width(), option.fontMetrics.boundingRect(comment).width()); 0787 0788 const auto iconSize = pluginSelector_d->listView->style()->pixelMetric(QStyle::PM_IconViewIconSize); 0789 return QSize(maxTextWidth + (pluginSelector_d->showIcons ? iconSize : 0) + MARGIN * i + pushButton->sizeHint().width() * j, 0790 qMax(iconSize + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); 0791 } 0792 0793 QList<QWidget *> KPluginSelector::Private::PluginDelegate::createItemWidgets(const QModelIndex &index) const 0794 { 0795 Q_UNUSED(index); 0796 QList<QWidget *> widgetList; 0797 0798 QCheckBox *enabledCheckBox = new QCheckBox; 0799 connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::slotStateChanged); 0800 connect(enabledCheckBox, &QAbstractButton::clicked, this, &PluginDelegate::emitChanged); 0801 0802 QPushButton *aboutPushButton = new QPushButton; 0803 aboutPushButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); 0804 aboutPushButton->setToolTip(i18n("About")); 0805 connect(aboutPushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotAboutClicked); 0806 0807 QPushButton *configurePushButton = new QPushButton; 0808 configurePushButton->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0809 configurePushButton->setToolTip(i18n("Configure")); 0810 connect(configurePushButton, &QAbstractButton::clicked, this, &PluginDelegate::slotConfigureClicked); 0811 0812 setBlockedEventTypes(enabledCheckBox, 0813 QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress 0814 << QEvent::KeyRelease); 0815 0816 setBlockedEventTypes(aboutPushButton, 0817 QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress 0818 << QEvent::KeyRelease); 0819 0820 setBlockedEventTypes(configurePushButton, 0821 QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick << QEvent::KeyPress 0822 << QEvent::KeyRelease); 0823 0824 widgetList << enabledCheckBox << configurePushButton << aboutPushButton; 0825 if (handler) { 0826 QPushButton *btn = handler(pluginSelector_d->pluginModel->pluginEntryList.at(index.row()).pluginInfo); 0827 if (btn) { 0828 widgetList << btn; 0829 } 0830 } 0831 0832 return widgetList; 0833 } 0834 0835 void KPluginSelector::Private::PluginDelegate::updateItemWidgets(const QList<QWidget *> widgets, 0836 const QStyleOptionViewItem &option, 0837 const QPersistentModelIndex &index) const 0838 { 0839 int extraButtonWidth = 0; 0840 QPushButton *extraButton = nullptr; 0841 if (widgets.count() == 4) { 0842 extraButton = static_cast<QPushButton *>(widgets[3]); 0843 extraButtonWidth = extraButton->sizeHint().width() + MARGIN; 0844 } 0845 QCheckBox *checkBox = static_cast<QCheckBox *>(widgets[0]); 0846 checkBox->resize(checkBox->sizeHint()); 0847 checkBox->move(pluginSelector_d->dependantLayoutValue(MARGIN, checkBox->sizeHint().width(), option.rect.width()), 0848 option.rect.height() / 2 - checkBox->sizeHint().height() / 2); 0849 0850 QPushButton *aboutPushButton = static_cast<QPushButton *>(widgets[2]); 0851 QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); 0852 aboutPushButton->resize(aboutPushButtonSizeHint); 0853 aboutPushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width() - extraButtonWidth, 0854 aboutPushButtonSizeHint.width(), 0855 option.rect.width()), 0856 option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); 0857 0858 QPushButton *configurePushButton = static_cast<QPushButton *>(widgets[1]); 0859 QSize configurePushButtonSizeHint = configurePushButton->sizeHint(); 0860 configurePushButton->resize(configurePushButtonSizeHint); 0861 configurePushButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - MARGIN * 2 - configurePushButtonSizeHint.width() 0862 - aboutPushButtonSizeHint.width() - extraButtonWidth, 0863 configurePushButtonSizeHint.width(), 0864 option.rect.width()), 0865 option.rect.height() / 2 - configurePushButtonSizeHint.height() / 2); 0866 if (extraButton) { 0867 QSize extraPushButtonSizeHint = extraButton->sizeHint(); 0868 extraButton->resize(extraPushButtonSizeHint); 0869 extraButton->move(pluginSelector_d->dependantLayoutValue(option.rect.width() - extraButtonWidth, extraPushButtonSizeHint.width(), option.rect.width()), 0870 option.rect.height() / 2 - extraPushButtonSizeHint.height() / 2); 0871 } 0872 0873 if (!index.isValid() || !index.internalPointer()) { 0874 checkBox->setVisible(false); 0875 aboutPushButton->setVisible(false); 0876 configurePushButton->setVisible(false); 0877 if (extraButton) { 0878 extraButton->setVisible(false); 0879 } 0880 } else { 0881 PluginEntry *pluginEntry = index.model()->data(index, PluginEntryRole).value<PluginEntry *>(); 0882 bool isDefault = pluginEntry->pluginInfo.isPluginEnabledByDefault() == index.model()->data(index, Qt::CheckStateRole).toBool(); 0883 checkBox->setProperty("_kde_highlight_neutral", pluginSelector_d->showDefaultIndicator && !isDefault); 0884 0885 checkBox->setChecked(index.model()->data(index, Qt::CheckStateRole).toBool()); 0886 checkBox->setEnabled(index.model()->data(index, IsCheckableRole).toBool()); 0887 configurePushButton->setVisible(index.model()->data(index, ServicesCountRole).toBool()); 0888 configurePushButton->setEnabled(index.model()->data(index, Qt::CheckStateRole).toBool()); 0889 } 0890 } 0891 0892 void KPluginSelector::Private::PluginDelegate::slotStateChanged(bool state) 0893 { 0894 if (!focusedIndex().isValid()) { 0895 return; 0896 } 0897 0898 const QModelIndex index = focusedIndex(); 0899 0900 pluginSelector_d->dependenciesWidget->clearDependencies(); 0901 0902 PluginEntry *pluginEntry = index.model()->data(index, PluginEntryRole).value<PluginEntry *>(); 0903 pluginSelector_d->updateDependencies(pluginEntry, state); 0904 0905 const_cast<QAbstractItemModel *>(index.model())->setData(index, state, Qt::CheckStateRole); 0906 } 0907 0908 void KPluginSelector::Private::PluginDelegate::emitChanged(bool state) 0909 { 0910 const QModelIndex index = focusedIndex(); 0911 PluginEntry *pluginEntry = index.model()->data(index, PluginEntryRole).value<PluginEntry *>(); 0912 0913 if (pluginEntry->pluginInfo.isPluginEnabled() != state) { 0914 changedEntries << pluginEntry; 0915 } else { 0916 changedEntries.remove(pluginEntry); 0917 } 0918 Q_EMIT changed(!changedEntries.isEmpty()); 0919 } 0920 0921 void KPluginSelector::Private::PluginDelegate::slotAboutClicked() 0922 { 0923 const QModelIndex index = focusedIndex(); 0924 const QAbstractItemModel *model = index.model(); 0925 0926 PluginEntry *pluginEntry = model->data(index, PluginEntryRole).value<PluginEntry *>(); 0927 KPluginMetaData pluginMetaData = pluginEntry->pluginInfo.toMetaData(); 0928 0929 auto *aboutPlugin = new KAboutPluginDialog(pluginMetaData, itemView()); 0930 aboutPlugin->setAttribute(Qt::WA_DeleteOnClose); 0931 aboutPlugin->show(); 0932 } 0933 0934 void KPluginSelector::Private::PluginDelegate::slotConfigureClicked() 0935 { 0936 configure(focusedIndex()); 0937 } 0938 0939 void KPluginSelector::Private::PluginDelegate::configure(const QModelIndex &index) 0940 { 0941 const QAbstractItemModel *model = index.model(); 0942 0943 PluginEntry *pluginEntry = model->data(index, PluginEntryRole).value<PluginEntry *>(); 0944 KPluginInfo pluginInfo = pluginEntry->pluginInfo; 0945 0946 QDialog configDialog(itemView()); 0947 configDialog.setWindowTitle(model->data(index, NameRole).toString()); 0948 // The number of KCModuleProxies in use determines whether to use a tabwidget 0949 QTabWidget *newTabWidget = nullptr; 0950 // Widget to use for the setting dialog's main widget, 0951 // either a QTabWidget or a KCModuleProxy 0952 QWidget *mainWidget = nullptr; 0953 // Widget to use as the KCModuleProxy's parent. 0954 // The first proxy is owned by the dialog itself 0955 QWidget *moduleProxyParentWidget = &configDialog; 0956 0957 QVector<KPluginMetaData> metaDataList; 0958 const auto lstServices = KPluginInfo::fromServices(pluginInfo.kcmServices()); 0959 for (const KPluginInfo &info : lstServices) { 0960 metaDataList << info.toMetaData(); 0961 } 0962 const QString configModule = pluginInfo.property(QStringLiteral("X-KDE-ConfigModule")).toString(); 0963 if (!configModule.isEmpty()) { 0964 const QString absoluteKCMPath = QPluginLoader(configModule).fileName(); 0965 // If we have a static plugin the filename does not exist 0966 if (absoluteKCMPath.isEmpty()) { 0967 const int idx = configModule.lastIndexOf(QLatin1Char('/')); 0968 const QString pluginNamespace = configModule.left(idx); 0969 const QString pluginId = configModule.mid(idx + 1); 0970 metaDataList = {KPluginMetaData::findPluginById(pluginNamespace, pluginId)}; // Clear the list to avoid old desktop files to appear twice 0971 } else { 0972 // the KCMs don't need any metadata themselves, just use the one from the KPluginInfo object 0973 // this way for example a KPackage plugin can specify plugin keyword 0974 KPluginMetaData data(pluginInfo.toMetaData().rawData(), absoluteKCMPath); 0975 metaDataList = {data}; // Clear the list to avoid old desktop files to appear twice 0976 } 0977 } 0978 for (const KPluginMetaData &data : std::as_const(metaDataList)) { 0979 if (!data.rawData().value(QStringLiteral("NoDisplay")).toBool()) { 0980 KCModuleProxy *currentModuleProxy = new KCModuleProxy(data, moduleProxyParentWidget, pluginSelector_d->kcmArguments); 0981 if (currentModuleProxy->realModule()) { 0982 moduleProxyList << currentModuleProxy; 0983 if (mainWidget && !newTabWidget) { 0984 // we already created one KCModuleProxy, so we need a tab widget. 0985 // Move the first proxy into the tab widget and ensure this and subsequent 0986 // proxies are in the tab widget 0987 newTabWidget = new QTabWidget(&configDialog); 0988 moduleProxyParentWidget = newTabWidget; 0989 mainWidget->setParent(newTabWidget); 0990 KCModuleProxy *moduleProxy = qobject_cast<KCModuleProxy *>(mainWidget); 0991 if (moduleProxy) { 0992 newTabWidget->addTab(mainWidget, data.name()); 0993 mainWidget = newTabWidget; 0994 } else { 0995 delete newTabWidget; 0996 newTabWidget = nullptr; 0997 moduleProxyParentWidget = &configDialog; 0998 mainWidget->setParent(nullptr); 0999 } 1000 } 1001 1002 if (newTabWidget) { 1003 newTabWidget->addTab(currentModuleProxy, pluginInfo.name()); 1004 } else { 1005 mainWidget = currentModuleProxy; 1006 } 1007 } else { 1008 delete currentModuleProxy; 1009 } 1010 } 1011 } 1012 1013 // it could happen that we had services to show, but none of them were real modules. 1014 if (!moduleProxyList.isEmpty()) { 1015 QVBoxLayout *layout = new QVBoxLayout(&configDialog); 1016 layout->addWidget(mainWidget); 1017 1018 QDialogButtonBox *buttonBox = new QDialogButtonBox(&configDialog); 1019 buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::RestoreDefaults); 1020 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); 1021 KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); 1022 KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); 1023 connect(buttonBox, &QDialogButtonBox::accepted, &configDialog, &QDialog::accept); 1024 connect(buttonBox, &QDialogButtonBox::rejected, &configDialog, &QDialog::reject); 1025 connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &PluginDelegate::slotDefaultClicked); 1026 layout->addWidget(buttonBox); 1027 1028 if (configDialog.exec() == QDialog::Accepted) { 1029 for (KCModuleProxy *moduleProxy : std::as_const(moduleProxyList)) { 1030 QStringList parentComponents; 1031 #if KCMUTILS_BUILD_DEPRECATED_SINCE(5, 88) 1032 if (moduleProxy->moduleInfo().isValid()) { 1033 parentComponents = moduleProxy->moduleInfo().property(QStringLiteral("X-KDE-ParentComponents")).toStringList(); 1034 } 1035 #else 1036 if (moduleProxy->metaData().isValid()) { 1037 parentComponents = moduleProxy->metaData().rawData().value(QStringLiteral("X-KDE-ParentComponents")).toVariant().toStringList(); 1038 } 1039 #endif 1040 moduleProxy->save(); 1041 for (const QString &parentComponent : std::as_const(parentComponents)) { 1042 Q_EMIT configCommitted(parentComponent.toLatin1()); 1043 } 1044 } 1045 } else { 1046 for (KCModuleProxy *moduleProxy : std::as_const(moduleProxyList)) { 1047 moduleProxy->load(); 1048 } 1049 } 1050 1051 qDeleteAll(moduleProxyList); 1052 moduleProxyList.clear(); 1053 } 1054 } 1055 1056 void KPluginSelector::Private::PluginDelegate::slotDefaultClicked() 1057 { 1058 for (KCModuleProxy *moduleProxy : std::as_const(moduleProxyList)) { 1059 moduleProxy->defaults(); 1060 } 1061 } 1062 1063 void KPluginSelector::Private::PluginDelegate::slotResetModel() 1064 { 1065 resetModel(); 1066 } 1067 1068 QFont KPluginSelector::Private::PluginDelegate::titleFont(const QFont &baseFont) const 1069 { 1070 QFont retFont(baseFont); 1071 retFont.setBold(true); 1072 1073 return retFont; 1074 } 1075 void KPluginSelector::Private::PluginDelegate::setHandler(std::function<QPushButton *(const KPluginInfo &)> handler) 1076 { 1077 this->handler = handler; 1078 } 1079 1080 #include "moc_kpluginselector.cpp" 1081 #include "moc_kpluginselector_p.cpp" 1082 #endif