/* SPDX-FileCopyrightText: 2000 Yves Arrouye SPDX-FileCopyrightText: 2001, 2002 Dawit Alemayehu SPDX-FileCopyrightText: 2009 Nick Shaforostoff SPDX-License-Identifier: GPL-2.0-or-later */ #include "ikwsopts.h" #include "ikwsopts_p.h" #include "searchprovider.h" #include "searchproviderdlg.h" #include #include #include #include #ifdef WITH_DBUS #include #include #endif #include #include #include // BEGIN ProvidersModel ProvidersModel::~ProvidersModel() { } QVariant ProvidersModel::headerData(int section, Qt::Orientation orientation, int role) const { Q_UNUSED(orientation); if (role == Qt::DisplayRole) { switch (section) { case Name: return i18nc("@title:column Name label from web search keyword column", "Name"); case Shortcuts: return i18nc("@title:column", "Keywords"); case Preferred: return i18nc("@title:column", "Preferred"); default: break; } } return QVariant(); } Qt::ItemFlags ProvidersModel::flags(const QModelIndex &index) const { if (!index.isValid()) { return Qt::ItemIsEnabled; } if (index.column() == Preferred) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } bool ProvidersModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::CheckStateRole) { if (value.toInt() == Qt::Checked) { m_favoriteEngines.insert(m_providers.at(index.row())->desktopEntryName()); } else { m_favoriteEngines.remove(m_providers.at(index.row())->desktopEntryName()); } Q_EMIT dataModified(); return true; } return false; } QVariant ProvidersModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (role == Qt::CheckStateRole && index.column() == Preferred) { return m_favoriteEngines.contains(m_providers.at(index.row())->desktopEntryName()) ? Qt::Checked : Qt::Unchecked; } if (role == Qt::DecorationRole && index.column() == Name) { return QIcon::fromTheme(m_providers.at(index.row())->iconName()); } if (role == Qt::DisplayRole) { if (index.column() == Name) { return m_providers.at(index.row())->name(); } if (index.column() == Shortcuts) { return m_providers.at(index.row())->keys().join(QLatin1Char(',')); } } if (role == Qt::ToolTipRole || role == Qt::WhatsThisRole) { if (index.column() == Preferred) { return xi18nc("@info:tooltip", "Check this box to select the highlighted Web search keyword " "as preferred.Preferred Web search keywords are used in " "places where only a few select keywords can be shown " "at one time."); } } if (role == Qt::UserRole) { return index.row(); // a nice way to bypass proxymodel } return QVariant(); } void ProvidersModel::setProviders(const QList &providers, const QStringList &favoriteEngines) { m_providers = providers; setFavoriteProviders(favoriteEngines); } void ProvidersModel::setFavoriteProviders(const QStringList &favoriteEngines) { beginResetModel(); m_favoriteEngines = QSet(favoriteEngines.begin(), favoriteEngines.end()); endResetModel(); } int ProvidersModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_providers.size(); } QAbstractListModel *ProvidersModel::createListModel() { ProvidersListModel *pListModel = new ProvidersListModel(m_providers, this); connect(this, &QAbstractItemModel::modelAboutToBeReset, pListModel, &QAbstractItemModel::modelAboutToBeReset); connect(this, &QAbstractItemModel::modelReset, pListModel, &QAbstractItemModel::modelReset); connect(this, &QAbstractItemModel::dataChanged, pListModel, &ProvidersListModel::emitDataChanged); connect(this, &QAbstractItemModel::rowsAboutToBeInserted, pListModel, &ProvidersListModel::emitRowsAboutToBeInserted); connect(this, &QAbstractItemModel::rowsAboutToBeRemoved, pListModel, &ProvidersListModel::emitRowsAboutToBeRemoved); connect(this, &QAbstractItemModel::rowsInserted, pListModel, &ProvidersListModel::emitRowsInserted); connect(this, &QAbstractItemModel::rowsRemoved, pListModel, &ProvidersListModel::emitRowsRemoved); return pListModel; } void ProvidersModel::deleteProvider(SearchProvider *p) { const int row = m_providers.indexOf(p); beginRemoveRows(QModelIndex(), row, row); m_favoriteEngines.remove(m_providers.takeAt(row)->desktopEntryName()); endRemoveRows(); Q_EMIT dataModified(); } void ProvidersModel::addProvider(SearchProvider *p) { beginInsertRows(QModelIndex(), m_providers.size(), m_providers.size()); m_providers.append(p); endInsertRows(); Q_EMIT dataModified(); } void ProvidersModel::changeProvider(SearchProvider *p) { const int row = m_providers.indexOf(p); Q_EMIT dataChanged(index(row, 0), index(row, ColumnCount - 1)); Q_EMIT dataModified(); } QStringList ProvidersModel::favoriteEngines() const { return QStringList(m_favoriteEngines.cbegin(), m_favoriteEngines.cend()); } // END ProvidersModel // BEGIN ProvidersListModel ProvidersListModel::ProvidersListModel(QList &providers, QObject *parent) : QAbstractListModel(parent) , m_providers(providers) { } QVariant ProvidersListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } const int row = index.row(); const bool noProvider = (row == m_providers.size()); // `None` is the last item switch (role) { case Qt::DisplayRole: if (noProvider) { return i18nc("@item:inlistbox No default web search keyword", "None"); } return m_providers.at(index.row())->name(); case ShortNameRole: if (noProvider) { return QString(); } return m_providers.at(index.row())->desktopEntryName(); case Qt::DecorationRole: if (noProvider) { return QIcon::fromTheme(QStringLiteral("empty")); } return QIcon::fromTheme(m_providers.at(index.row())->iconName()); } return QVariant(); } int ProvidersListModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_providers.size() + 1; } // END ProvidersListModel static QSortFilterProxyModel *wrapInProxyModel(QAbstractItemModel *model) { QSortFilterProxyModel *proxyModel = new QSortFilterProxyModel(model); proxyModel->setSourceModel(model); proxyModel->setDynamicSortFilter(true); proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); proxyModel->setFilterKeyColumn(-1); return proxyModel; } FilterOptions::FilterOptions(QObject *parent, const KPluginMetaData &data) : KCModule(parent, data) , m_providersModel(new ProvidersModel(this)) { // Used for tab text in the KCM widget()->setWindowTitle(i18n("Search F&ilters")); m_dlg.setupUi(widget()); QSortFilterProxyModel *searchProviderModel = wrapInProxyModel(m_providersModel); m_dlg.lvSearchProviders->setModel(searchProviderModel); m_dlg.cmbDefaultEngine->setModel(wrapInProxyModel(m_providersModel->createListModel())); // Connect all the signals/slots... connect(m_dlg.cbEnableShortcuts, &QAbstractButton::toggled, this, &FilterOptions::markAsChanged); connect(m_dlg.cbEnableShortcuts, &QAbstractButton::toggled, this, &FilterOptions::updateSearchProviderEditingButons); connect(m_dlg.cbUseSelectedShortcutsOnly, &QAbstractButton::toggled, this, &FilterOptions::markAsChanged); connect(m_providersModel, &ProvidersModel::dataModified, this, &FilterOptions::markAsChanged); connect(m_dlg.cmbDefaultEngine, qOverload(&QComboBox::currentIndexChanged), this, &FilterOptions::markAsChanged); connect(m_dlg.cmbDelimiter, qOverload(&QComboBox::currentIndexChanged), this, &FilterOptions::markAsChanged); connect(m_dlg.pbNew, &QAbstractButton::clicked, this, &FilterOptions::addSearchProvider); connect(m_dlg.pbDelete, &QAbstractButton::clicked, this, &FilterOptions::deleteSearchProvider); connect(m_dlg.pbChange, &QAbstractButton::clicked, this, &FilterOptions::changeSearchProvider); connect(m_dlg.lvSearchProviders->selectionModel(), &QItemSelectionModel::currentChanged, this, &FilterOptions::updateSearchProviderEditingButons); connect(m_dlg.lvSearchProviders, &QAbstractItemView::doubleClicked, this, &FilterOptions::changeSearchProvider); connect(m_dlg.searchLineEdit, &QLineEdit::textEdited, searchProviderModel, &QSortFilterProxyModel::setFilterFixedString); } void FilterOptions::setDefaultEngine(int index) { QSortFilterProxyModel *proxy = qobject_cast(m_dlg.cmbDefaultEngine->model()); if (index == -1) { index = proxy->rowCount() - 1; //"None" is the last } const QModelIndex modelIndex = proxy->mapFromSource(proxy->sourceModel()->index(index, 0)); m_dlg.cmbDefaultEngine->setCurrentIndex(modelIndex.row()); m_dlg.cmbDefaultEngine->view()->setCurrentIndex(modelIndex); // TODO: remove this when Qt bug is fixed } void FilterOptions::load() { KConfig config(QStringLiteral("kuriikwsfilterrc"), KConfig::NoGlobals); KConfigGroup group = config.group(QStringLiteral("General")); const QString defaultSearchEngine = group.readEntry("DefaultWebShortcut", "duckduckgo"); const QStringList favoriteEngines = group.readEntry("PreferredWebShortcuts", m_defaultProviders); const QList allProviders = m_registry.findAll(); QList providers; for (auto *provider : allProviders) { if (!provider->isHidden()) { providers << provider; } } int defaultProviderIndex = providers.size(); // default is "None", it is last in the list for (SearchProvider *provider : std::as_const(providers)) { if (defaultSearchEngine == provider->desktopEntryName()) { defaultProviderIndex = providers.indexOf(provider); break; } } m_providersModel->setProviders(providers, favoriteEngines); m_dlg.lvSearchProviders->setColumnWidth(0, 200); m_dlg.lvSearchProviders->resizeColumnToContents(1); m_dlg.lvSearchProviders->sortByColumn(0, Qt::AscendingOrder); m_dlg.cmbDefaultEngine->model()->sort(0, Qt::AscendingOrder); setDefaultEngine(defaultProviderIndex); m_dlg.cbEnableShortcuts->setChecked(group.readEntry("EnableWebShortcuts", true)); m_dlg.cbUseSelectedShortcutsOnly->setChecked(group.readEntry("UsePreferredWebShortcutsOnly", false)); const QString delimiter = group.readEntry("KeywordDelimiter", ":"); setDelimiter(delimiter.at(0).toLatin1()); } char FilterOptions::delimiter() { const char delimiters[] = {':', ' '}; return delimiters[m_dlg.cmbDelimiter->currentIndex()]; } void FilterOptions::setDelimiter(char sep) { m_dlg.cmbDelimiter->setCurrentIndex(sep == ' '); } void FilterOptions::save() { KConfig config(QStringLiteral("kuriikwsfilterrc"), KConfig::NoGlobals); KConfigGroup group = config.group(QStringLiteral("General")); group.writeEntry("EnableWebShortcuts", m_dlg.cbEnableShortcuts->isChecked()); group.writeEntry("KeywordDelimiter", QString(QLatin1Char(delimiter()))); group.writeEntry("DefaultWebShortcut", m_dlg.cmbDefaultEngine->view()->currentIndex().data(ProvidersListModel::ShortNameRole)); group.writeEntry("PreferredWebShortcuts", m_providersModel->favoriteEngines()); group.writeEntry("UsePreferredWebShortcutsOnly", m_dlg.cbUseSelectedShortcutsOnly->isChecked()); const QList providers = m_providersModel->providers(); const QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/kf6/searchproviders/"); for (SearchProvider *provider : providers) { if (!provider->isDirty()) { continue; } KConfig _service(path + provider->desktopEntryName() + QLatin1String(".desktop"), KConfig::SimpleConfig); KConfigGroup service(&_service, QStringLiteral("Desktop Entry")); service.writeEntry("Type", "Service"); service.writeEntry("Name", provider->name()); service.writeEntry("Query", provider->query()); service.writeEntry("Keys", provider->keys()); service.writeEntry("Charset", provider->charset()); service.writeEntry("Hidden", false); // we might be overwriting a hidden entry } const QStringList servicesDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("kf6/searchproviders/"), QStandardPaths::LocateDirectory); for (const QString &providerName : std::as_const(m_deletedProviders)) { QStringList matches; for (const QString &dir : servicesDirs) { QString current = dir + QLatin1Char('/') + providerName + QLatin1String(".desktop"); if (QFile::exists(current)) { matches += current; } } // Shouldn't happen if (matches.isEmpty()) { continue; } if (matches.size() == 1 && matches.first().startsWith(path)) { // If only the local copy existed, unlink it // TODO: error handling QFile::remove(matches.first()); continue; } KConfig _service(path + providerName + QLatin1String(".desktop"), KConfig::SimpleConfig); KConfigGroup service(&_service, QStringLiteral("Desktop Entry")); service.writeEntry("Type", "Service"); service.writeEntry("Hidden", true); } config.sync(); setNeedsSave(false); #ifdef WITH_DBUS // Update filters in running applications... QDBusMessage msg = QDBusMessage::createSignal(QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure")); QDBusConnection::sessionBus().send(msg); #endif } void FilterOptions::defaults() { m_dlg.cbEnableShortcuts->setChecked(true); m_dlg.cbUseSelectedShortcutsOnly->setChecked(false); m_providersModel->setFavoriteProviders(m_defaultProviders); setDelimiter(':'); const QList providers = m_providersModel->providers(); int defaultProviderIndex = providers.size(); // default is "None", it is last in the list for (SearchProvider *provider : std::as_const(providers)) { if (QLatin1String("duckduckgo") == provider->desktopEntryName()) { defaultProviderIndex = providers.indexOf(provider); break; } } setDefaultEngine(defaultProviderIndex); } void FilterOptions::addSearchProvider() { QList providers = m_providersModel->providers(); QPointer dlg = new SearchProviderDialog(nullptr, providers, widget()); if (dlg->exec()) { m_providersModel->addProvider(dlg->provider()); m_providersModel->changeProvider(dlg->provider()); } delete dlg; } void FilterOptions::changeSearchProvider() { QList providers = m_providersModel->providers(); SearchProvider *provider = providers.at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); QPointer dlg = new SearchProviderDialog(provider, providers, widget()); if (dlg->exec()) { m_providersModel->changeProvider(dlg->provider()); } delete dlg; } void FilterOptions::deleteSearchProvider() { SearchProvider *provider = m_providersModel->providers().at(m_dlg.lvSearchProviders->currentIndex().data(Qt::UserRole).toInt()); m_deletedProviders.append(provider->desktopEntryName()); m_providersModel->deleteProvider(provider); } void FilterOptions::updateSearchProviderEditingButons() { const bool enable = (m_dlg.cbEnableShortcuts->isChecked() && m_dlg.lvSearchProviders->currentIndex().isValid()); m_dlg.pbChange->setEnabled(enable); m_dlg.pbDelete->setEnabled(enable); } K_PLUGIN_CLASS_WITH_JSON(FilterOptions, "kcm_webshortcuts.json") #include "ikwsopts.moc" #include "moc_ikwsopts.cpp" #include "moc_ikwsopts_p.cpp"