File indexing completed on 2024-05-19 05:00:22
0001 /* 0002 SPDX-FileCopyrightText: 2000 Yves Arrouye <yves@realnames.com> 0003 SPDX-FileCopyrightText: 2001, 2002 Dawit Alemayehu <adawit@kde.org> 0004 SPDX-FileCopyrightText: 2009 Nick Shaforostoff <shaforostoff@kde.ru> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "ikwsopts.h" 0010 #include "ikwsopts_p.h" 0011 0012 #include "searchprovider.h" 0013 #include "searchproviderdlg.h" 0014 0015 #include <KConfigGroup> 0016 #include <KLocalizedString> 0017 #include <KPluginFactory> 0018 #include <KSharedConfig> 0019 0020 #include <QDBusConnection> 0021 #include <QDBusMessage> 0022 #include <QFile> 0023 #include <QPointer> 0024 #include <QSortFilterProxyModel> 0025 0026 // BEGIN ProvidersModel 0027 0028 ProvidersModel::~ProvidersModel() 0029 { 0030 } 0031 0032 QVariant ProvidersModel::headerData(int section, Qt::Orientation orientation, int role) const 0033 { 0034 Q_UNUSED(orientation); 0035 if (role == Qt::DisplayRole) { 0036 switch (section) { 0037 case Name: 0038 return i18nc("@title:column Name label from web search keyword column", "Name"); 0039 case Shortcuts: 0040 return i18nc("@title:column", "Keywords"); 0041 case Preferred: 0042 return i18nc("@title:column", "Preferred"); 0043 default: 0044 break; 0045 } 0046 } 0047 return QVariant(); 0048 } 0049 0050 Qt::ItemFlags ProvidersModel::flags(const QModelIndex &index) const 0051 { 0052 if (!index.isValid()) { 0053 return Qt::ItemIsEnabled; 0054 } 0055 if (index.column() == Preferred) { 0056 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; 0057 } 0058 return Qt::ItemIsEnabled | Qt::ItemIsSelectable; 0059 } 0060 0061 bool ProvidersModel::setData(const QModelIndex &index, const QVariant &value, int role) 0062 { 0063 if (role == Qt::CheckStateRole) { 0064 if (value.toInt() == Qt::Checked) { 0065 m_favoriteEngines.insert(m_providers.at(index.row())->desktopEntryName()); 0066 } else { 0067 m_favoriteEngines.remove(m_providers.at(index.row())->desktopEntryName()); 0068 } 0069 Q_EMIT dataModified(); 0070 return true; 0071 } 0072 return false; 0073 } 0074 0075 QVariant ProvidersModel::data(const QModelIndex &index, int role) const 0076 { 0077 if (!index.isValid()) { 0078 return QVariant(); 0079 } 0080 0081 if (role == Qt::CheckStateRole && index.column() == Preferred) { 0082 return m_favoriteEngines.contains(m_providers.at(index.row())->desktopEntryName()) ? Qt::Checked : Qt::Unchecked; 0083 } 0084 0085 if (role == Qt::DecorationRole && index.column() == Name) { 0086 return QIcon::fromTheme(m_providers.at(index.row())->iconName()); 0087 } 0088 0089 if (role == Qt::DisplayRole) { 0090 if (index.column() == Name) { 0091 return m_providers.at(index.row())->name(); 0092 } 0093 if (index.column() == Shortcuts) { 0094 return m_providers.at(index.row())->keys().join(QLatin1Char(',')); 0095 } 0096 } 0097 0098 if (role == Qt::ToolTipRole || role == Qt::WhatsThisRole) { 0099 if (index.column() == Preferred) { 0100 return xi18nc("@info:tooltip", 0101 "Check this box to select the highlighted web search keyword " 0102 "as preferred.<nl/>Preferred web search keywords are used in " 0103 "places where only a few select keywords can be shown " 0104 "at one time."); 0105 } 0106 } 0107 0108 if (role == Qt::UserRole) { 0109 return index.row(); // a nice way to bypass proxymodel 0110 } 0111 0112 return QVariant(); 0113 } 0114 0115 void ProvidersModel::setProviders(const QList<SearchProvider *> &providers, const QStringList &favoriteEngines) 0116 { 0117 m_providers = providers; 0118 setFavoriteProviders(favoriteEngines); 0119 } 0120 0121 void ProvidersModel::setFavoriteProviders(const QStringList &favoriteEngines) 0122 { 0123 beginResetModel(); 0124 0125 m_favoriteEngines = QSet<QString>(favoriteEngines.begin(), favoriteEngines.end()); 0126 0127 endResetModel(); 0128 } 0129 0130 int ProvidersModel::rowCount(const QModelIndex &parent) const 0131 { 0132 if (parent.isValid()) { 0133 return 0; 0134 } 0135 return m_providers.size(); 0136 } 0137 0138 QAbstractListModel *ProvidersModel::createListModel() 0139 { 0140 ProvidersListModel *pListModel = new ProvidersListModel(m_providers, this); 0141 connect(this, &QAbstractItemModel::modelAboutToBeReset, pListModel, &QAbstractItemModel::modelAboutToBeReset); 0142 connect(this, &QAbstractItemModel::modelReset, pListModel, &QAbstractItemModel::modelReset); 0143 connect(this, &QAbstractItemModel::dataChanged, pListModel, &ProvidersListModel::emitDataChanged); 0144 connect(this, &QAbstractItemModel::rowsAboutToBeInserted, pListModel, &ProvidersListModel::emitRowsAboutToBeInserted); 0145 connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, pListModel, &ProvidersListModel::emitRowsAboutToBeRemoved); 0146 connect(this, &QAbstractItemModel::rowsInserted, pListModel, &ProvidersListModel::emitRowsInserted); 0147 connect(this, &QAbstractItemModel::rowsRemoved, pListModel, &ProvidersListModel::emitRowsRemoved); 0148 0149 return pListModel; 0150 } 0151 0152 void ProvidersModel::deleteProvider(SearchProvider *p) 0153 { 0154 const int row = m_providers.indexOf(p); 0155 beginRemoveRows(QModelIndex(), row, row); 0156 m_favoriteEngines.remove(m_providers.takeAt(row)->desktopEntryName()); 0157 endRemoveRows(); 0158 Q_EMIT dataModified(); 0159 } 0160 0161 void ProvidersModel::addProvider(SearchProvider *p) 0162 { 0163 beginInsertRows(QModelIndex(), m_providers.size(), m_providers.size()); 0164 m_providers.append(p); 0165 endInsertRows(); 0166 Q_EMIT dataModified(); 0167 } 0168 0169 void ProvidersModel::changeProvider(SearchProvider *p) 0170 { 0171 const int row = m_providers.indexOf(p); 0172 Q_EMIT dataChanged(index(row, 0), index(row, ColumnCount - 1)); 0173 Q_EMIT dataModified(); 0174 } 0175 0176 QStringList ProvidersModel::favoriteEngines() const 0177 { 0178 return QStringList(m_favoriteEngines.cbegin(), m_favoriteEngines.cend()); 0179 } 0180 0181 // END ProvidersModel 0182 0183 // BEGIN ProvidersListModel 0184 ProvidersListModel::ProvidersListModel(QList<SearchProvider *> &providers, QObject *parent) 0185 : QAbstractListModel(parent) 0186 , m_providers(providers) 0187 { 0188 } 0189 0190 QVariant ProvidersListModel::data(const QModelIndex &index, int role) const 0191 { 0192 if (!index.isValid()) { 0193 return QVariant(); 0194 } 0195 0196 const int row = index.row(); 0197 const bool noProvider = (row == m_providers.size()); // `None` is the last item 0198 0199 switch (role) { 0200 case Qt::DisplayRole: 0201 if (noProvider) { 0202 return i18nc("@item:inlistbox No default web search keyword", "None"); 0203 } 0204 return m_providers.at(index.row())->name(); 0205 case ShortNameRole: 0206 if (noProvider) { 0207 return QString(); 0208 } 0209 return m_providers.at(index.row())->desktopEntryName(); 0210 case Qt::DecorationRole: 0211 if (noProvider) { 0212 return QIcon::fromTheme(QStringLiteral("empty")); 0213 } 0214 return QIcon::fromTheme(m_providers.at(index.row())->iconName()); 0215 } 0216 0217 return QVariant(); 0218 } 0219 0220 int ProvidersListModel::rowCount(const QModelIndex &parent) const 0221 { 0222 if (parent.isValid()) { 0223 return 0; 0224 } 0225 return m_providers.size() + 1; 0226 } 0227 0228 // END ProvidersListModel 0229 0230 static QSortFilterProxyModel *wrapInProxyModel(QAbstractItemModel *model) 0231 { 0232 QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(model); 0233 proxyModel->setSourceModel(model); 0234 proxyModel->setDynamicSortFilter(true); 0235 proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); 0236 proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); 0237 proxyModel->setFilterKeyColumn(-1); 0238 return proxyModel; 0239 } 0240 0241 FilterOptions::FilterOptions(QObject *parent, const KPluginMetaData &data) 0242 : KCModule(parent, data) 0243 , m_providersModel(new ProvidersModel(this)) 0244 { 0245 // Used for tab text in the KCM 0246 widget()->setWindowTitle(i18n("Search F&ilters")); 0247 0248 m_dlg.setupUi(widget()); 0249 0250 QSortFilterProxyModel *searchProviderModel = wrapInProxyModel(m_providersModel); 0251 m_dlg.lvSearchProviders->setModel(searchProviderModel); 0252 m_dlg.cmbDefaultEngine->setModel(wrapInProxyModel(m_providersModel->createListModel())); 0253 0254 // Connect all the signals/slots... 0255 connect(m_dlg.cbEnableShortcuts, &QAbstractButton::toggled, this, &FilterOptions::markAsChanged); 0256 connect(m_dlg.cbEnableShortcuts, &QAbstractButton::toggled, this, &FilterOptions::updateSearchProviderEditingButons); 0257 connect(m_dlg.cbUseSelectedShortcutsOnly, &QAbstractButton::toggled, this, &FilterOptions::markAsChanged); 0258 0259 connect(m_providersModel, &ProvidersModel::dataModified, this, &FilterOptions::markAsChanged); 0260 connect(m_dlg.cmbDefaultEngine, qOverload<int>(&QComboBox::currentIndexChanged), this, &FilterOptions::markAsChanged); 0261 connect(m_dlg.cmbDelimiter, qOverload<int>(&QComboBox::currentIndexChanged), this, &FilterOptions::markAsChanged); 0262 0263 connect(m_dlg.pbNew, &QAbstractButton::clicked, this, &FilterOptions::addSearchProvider); 0264 connect(m_dlg.pbDelete, &QAbstractButton::clicked, this, &FilterOptions::deleteSearchProvider); 0265 connect(m_dlg.pbChange, &QAbstractButton::clicked, this, &FilterOptions::changeSearchProvider); 0266 connect(m_dlg.lvSearchProviders->selectionModel(), &QItemSelectionModel::currentChanged, this, &FilterOptions::updateSearchProviderEditingButons); 0267 connect(m_dlg.lvSearchProviders, &QAbstractItemView::doubleClicked, this, &FilterOptions::changeSearchProvider); 0268 connect(m_dlg.searchLineEdit, &QLineEdit::textEdited, searchProviderModel, &QSortFilterProxyModel::setFilterFixedString); 0269 } 0270 0271 void FilterOptions::setDefaultEngine(int index) 0272 { 0273 QSortFilterProxyModel *proxy = qobject_cast<QSortFilterProxyModel *>(m_dlg.cmbDefaultEngine->model()); 0274 if (index == -1) { 0275 index = proxy->rowCount() - 1; //"None" is the last 0276 } 0277 const QModelIndex modelIndex = proxy->mapFromSource(proxy->sourceModel()->index(index, 0)); 0278 m_dlg.cmbDefaultEngine->setCurrentIndex(modelIndex.row()); 0279 m_dlg.cmbDefaultEngine->view()->setCurrentIndex(modelIndex); // TODO: remove this when Qt bug is fixed 0280 } 0281 0282 void FilterOptions::load() 0283 { 0284 KConfig config(QStringLiteral("kuriikwsfilterrc"), KConfig::NoGlobals); 0285 KConfigGroup group = config.group(QStringLiteral("General")); 0286 0287 const QString defaultSearchEngine = group.readEntry("DefaultWebShortcut", "duckduckgo"); 0288 const QStringList favoriteEngines = group.readEntry("PreferredWebShortcuts", m_defaultProviders); 0289 0290 const QList<SearchProvider *> allProviders = m_registry.findAll(); 0291 QList<SearchProvider *> providers; 0292 for (auto *provider : allProviders) { 0293 if (!provider->isHidden()) { 0294 providers << provider; 0295 } 0296 } 0297 0298 int defaultProviderIndex = providers.size(); // default is "None", it is last in the list 0299 0300 for (SearchProvider *provider : std::as_const(providers)) { 0301 if (defaultSearchEngine == provider->desktopEntryName()) { 0302 defaultProviderIndex = providers.indexOf(provider); 0303 break; 0304 } 0305 } 0306 0307 m_providersModel->setProviders(providers, favoriteEngines); 0308 m_dlg.lvSearchProviders->setColumnWidth(0, 200); 0309 m_dlg.lvSearchProviders->resizeColumnToContents(1); 0310 m_dlg.lvSearchProviders->sortByColumn(0, Qt::AscendingOrder); 0311 m_dlg.cmbDefaultEngine->model()->sort(0, Qt::AscendingOrder); 0312 setDefaultEngine(defaultProviderIndex); 0313 0314 m_dlg.cbEnableShortcuts->setChecked(group.readEntry("EnableWebShortcuts", true)); 0315 m_dlg.cbUseSelectedShortcutsOnly->setChecked(group.readEntry("UsePreferredWebShortcutsOnly", false)); 0316 0317 const QString delimiter = group.readEntry("KeywordDelimiter", ":"); 0318 setDelimiter(delimiter.at(0).toLatin1()); 0319 } 0320 0321 char FilterOptions::delimiter() 0322 { 0323 const char delimiters[] = {':', ' '}; 0324 return delimiters[m_dlg.cmbDelimiter->currentIndex()]; 0325 } 0326 0327 void FilterOptions::setDelimiter(char sep) 0328 { 0329 m_dlg.cmbDelimiter->setCurrentIndex(sep == ' '); 0330 } 0331 0332 void FilterOptions::save() 0333 { 0334 KConfig config(QStringLiteral("kuriikwsfilterrc"), KConfig::NoGlobals); 0335 0336 KConfigGroup group = config.group(QStringLiteral("General")); 0337 group.writeEntry("EnableWebShortcuts", m_dlg.cbEnableShortcuts->isChecked()); 0338 group.writeEntry("KeywordDelimiter", QString(QLatin1Char(delimiter()))); 0339 group.writeEntry("DefaultWebShortcut", m_dlg.cmbDefaultEngine->view()->currentIndex().data(ProvidersListModel::ShortNameRole)); 0340 group.writeEntry("PreferredWebShortcuts", m_providersModel->favoriteEngines()); 0341 group.writeEntry("UsePreferredWebShortcutsOnly", m_dlg.cbUseSelectedShortcutsOnly->isChecked()); 0342 0343 const QList<SearchProvider *> providers = m_providersModel->providers(); 0344 const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kf6/searchproviders/"); 0345 0346 for (SearchProvider *provider : providers) { 0347 if (!provider->isDirty()) { 0348 continue; 0349 } 0350 0351 KConfig _service(path + provider->desktopEntryName() + QLatin1String(".desktop"), KConfig::SimpleConfig); 0352 KConfigGroup service(&_service, QStringLiteral("Desktop Entry")); 0353 service.writeEntry("Type", "Service"); 0354 service.writeEntry("Name", provider->name()); 0355 service.writeEntry("Query", provider->query()); 0356 service.writeEntry("Keys", provider->keys()); 0357 service.writeEntry("Charset", provider->charset()); 0358 service.writeEntry("Hidden", false); // we might be overwriting a hidden entry 0359 } 0360 0361 const QStringList servicesDirs = 0362 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kf6/searchproviders/"), QStandardPaths::LocateDirectory); 0363 for (const QString &providerName : std::as_const(m_deletedProviders)) { 0364 QStringList matches; 0365 for (const QString &dir : servicesDirs) { 0366 QString current = dir + QLatin1Char('/') + providerName + QLatin1String(".desktop"); 0367 if (QFile::exists(current)) { 0368 matches += current; 0369 } 0370 } 0371 0372 // Shouldn't happen 0373 if (matches.isEmpty()) { 0374 continue; 0375 } 0376 0377 if (matches.size() == 1 && matches.first().startsWith(path)) { 0378 // If only the local copy existed, unlink it 0379 // TODO: error handling 0380 QFile::remove(matches.first()); 0381 continue; 0382 } 0383 0384 KConfig _service(path + providerName + QLatin1String(".desktop"), KConfig::SimpleConfig); 0385 KConfigGroup service(&_service, QStringLiteral("Desktop Entry")); 0386 service.writeEntry("Type", "Service"); 0387 service.writeEntry("Hidden", true); 0388 } 0389 0390 config.sync(); 0391 0392 setNeedsSave(false); 0393 0394 // Update filters in running applications... 0395 QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure")); 0396 QDBusConnection::sessionBus().send(msg); 0397 } 0398 0399 void FilterOptions::defaults() 0400 { 0401 m_dlg.cbEnableShortcuts->setChecked(true); 0402 m_dlg.cbUseSelectedShortcutsOnly->setChecked(false); 0403 m_providersModel->setFavoriteProviders(m_defaultProviders); 0404 setDelimiter(':'); 0405 0406 const QList<SearchProvider *> providers = m_providersModel->providers(); 0407 int defaultProviderIndex = providers.size(); // default is "None", it is last in the list 0408 for (SearchProvider *provider : std::as_const(providers)) { 0409 if (QLatin1String("duckduckgo") == provider->desktopEntryName()) { 0410 defaultProviderIndex = providers.indexOf(provider); 0411 break; 0412 } 0413 } 0414 setDefaultEngine(defaultProviderIndex); 0415 } 0416 0417 void FilterOptions::addSearchProvider() 0418 { 0419 QList<SearchProvider *> providers = m_providersModel->providers(); 0420 QPointer<SearchProviderDialog> dlg = new SearchProviderDialog(nullptr, providers, widget()); 0421 0422 if (dlg->exec()) { 0423 m_providersModel->addProvider(dlg->provider()); 0424 m_providersModel->changeProvider(dlg->provider()); 0425 } 0426 delete dlg; 0427 } 0428 0429 void FilterOptions::changeSearchProvider() 0430 { 0431 QList<SearchProvider *> providers = m_providersModel->providers(); 0432 SearchProvider *provider = providers.at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); 0433 QPointer<SearchProviderDialog> dlg = new SearchProviderDialog(provider, providers, widget()); 0434 0435 if (dlg->exec()) { 0436 m_providersModel->changeProvider(dlg->provider()); 0437 } 0438 0439 delete dlg; 0440 } 0441 0442 void FilterOptions::deleteSearchProvider() 0443 { 0444 SearchProvider *provider = m_providersModel->providers().at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); 0445 m_deletedProviders.append(provider->desktopEntryName()); 0446 m_providersModel->deleteProvider(provider); 0447 } 0448 0449 void FilterOptions::updateSearchProviderEditingButons() 0450 { 0451 const bool enable = (m_dlg.cbEnableShortcuts->isChecked() && m_dlg.lvSearchProviders->currentIndex().isValid()); 0452 m_dlg.pbChange->setEnabled(enable); 0453 m_dlg.pbDelete->setEnabled(enable); 0454 } 0455 0456 K_PLUGIN_CLASS_WITH_JSON(FilterOptions, "kcm_webshortcuts.json") 0457 0458 #include "ikwsopts.moc"