File indexing completed on 2024-04-21 04:58:02

0001 /* This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2004 Arend van Beelen jr. <arend@auton.nl>
0003     SPDX-FileCopyrightText: 2009 Fredy Yanardi <fyanardi@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "searchbar.h"
0009 
0010 #include "WebShortcutWidget.h"
0011 
0012 #include <KBuildSycocaProgressDialog>
0013 #include <KCompletionBox>
0014 #include <KConfigGroup>
0015 #include <KSharedConfig>
0016 #include <KDesktopFile>
0017 #include <KDialogJobUiDelegate>
0018 #include <KPluginFactory>
0019 #include <KActionCollection>
0020 #include <KIO/CommandLauncherJob>
0021 #include <KMainWindow>
0022 #include <KParts/Part>
0023 #include "kf5compat.h" //For NavigationExtension
0024 #include <KParts/PartActivateEvent>
0025 #include <KLocalizedString>
0026 #include <KIO/Job>
0027 
0028 #include <QLineEdit>
0029 #include <QApplication>
0030 #include <QDir>
0031 #include <QTimer>
0032 #include <QMenu>
0033 #include <QStyle>
0034 #include <QPainter>
0035 #include <QMouseEvent>
0036 #include <QDBusConnection>
0037 #include <QDBusMessage>
0038 #include <QWidgetAction>
0039 #include <QStandardPaths>
0040 
0041 #include "searchbar_debug.h"
0042 #include <asyncselectorinterface.h>
0043 #include <htmlextension.h>
0044 #include <textextension.h>
0045 #include <browserarguments.h>
0046 #include <browserextension.h>
0047 
0048 K_PLUGIN_CLASS_WITH_JSON(SearchBarPlugin, "searchbar.json")
0049 
0050 SearchBarPlugin::SearchBarPlugin(QObject *parent,
0051                                  const QVariantList &) :
0052     KonqParts::Plugin(parent),
0053     m_popupMenu(nullptr),
0054     m_addWSWidget(nullptr),
0055     m_searchMode(UseSearchProvider),
0056     m_urlEnterLock(false),
0057     m_reloadConfiguration(false)
0058 {
0059     m_searchCombo = new SearchBarCombo(nullptr);
0060     m_searchCombo->lineEdit()->installEventFilter(this);
0061 #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
0062     connect(m_searchCombo, QOverload<int>::of(&QComboBox::activated), this, [this](int n){startSearch(m_searchCombo->itemText(n));});
0063 #else
0064     connect(m_searchCombo, &QComboBox::textActivated, this, &SearchBarPlugin::startSearch);
0065 #endif
0066     connect(m_searchCombo, &SearchBarCombo::iconClicked, this, &SearchBarPlugin::showSelectionMenu);
0067     m_searchCombo->setWhatsThis(i18n("Search Bar<p>"
0068                                      "Enter a search term. Click on the icon to change search mode or provider.</p>"));
0069 
0070     m_searchComboAction = new QWidgetAction(actionCollection());
0071     actionCollection()->addAction(QStringLiteral("toolbar_search_bar"), m_searchComboAction);
0072     m_searchComboAction->setText(i18n("Search Bar"));
0073     m_searchComboAction->setDefaultWidget(m_searchCombo);
0074     actionCollection()->setShortcutsConfigurable(m_searchComboAction, false);
0075 
0076     QAction *a = actionCollection()->addAction(QStringLiteral("focus_search_bar"));
0077     a->setText(i18n("Focus Searchbar"));
0078     actionCollection()->setDefaultShortcut(a, QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_S));
0079     connect(a, &QAction::triggered, this, &SearchBarPlugin::focusSearchbar);
0080     m_searchProvidersDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/kde5/services/searchproviders/";
0081     QDir().mkpath(m_searchProvidersDir);
0082     configurationChanged();
0083 
0084     // parent is the KonqMainWindow and we want to listen to PartActivateEvent events.
0085     parent->installEventFilter(this);
0086 
0087     connect(m_searchCombo->lineEdit(), &QLineEdit::textEdited,
0088             this, &SearchBarPlugin::searchTextChanged);
0089 
0090     QDBusConnection::sessionBus().connect(QString(), QString(), QStringLiteral("org.kde.KUriFilterPlugin"),
0091                                           QStringLiteral("configure"), this, SLOT(reloadConfiguration()));
0092 }
0093 
0094 SearchBarPlugin::~SearchBarPlugin()
0095 {
0096     KConfigGroup config(KSharedConfig::openConfig(), "SearchBar");
0097     config.writeEntry("Mode", (int) m_searchMode);
0098     config.writeEntry("CurrentEngine", m_currentEngine);
0099 
0100     delete m_searchCombo;
0101     m_searchCombo = nullptr;
0102 }
0103 
0104 bool SearchBarPlugin::eventFilter(QObject *o, QEvent *e)
0105 {
0106     if (KParts::PartActivateEvent::test(e)) {
0107         KParts::PartActivateEvent *partEvent = static_cast<KParts::PartActivateEvent *>(e);
0108         KParts::ReadOnlyPart *part = qobject_cast<KParts::ReadOnlyPart *>(partEvent->part());
0109         //qCDebug(SEARCHBAR_LOG) << "Embedded part changed to " << part;
0110         if (part && (m_part.isNull() || part != m_part)) {
0111             m_part = part;
0112 
0113             // Delete the popup menu so a new one can be created with the
0114             // appropriate entries the next time it is shown...
0115             // ######## TODO: This loses the opensearch entries for the old part!!!
0116             if (m_popupMenu) {
0117                 delete m_popupMenu;
0118                 m_popupMenu = nullptr;
0119                 m_addSearchActions.clear(); // the actions had the menu as parent, so they're deleted now
0120             }
0121 
0122             // Change the search mode if it is set to FindInThisPage since
0123             // that feature is currently KHTML specific. It is also completely
0124             // redundant and unnecessary.
0125             if (m_searchMode == FindInThisPage && enableFindInPage()) {
0126                 nextSearchEntry();
0127             }
0128 
0129             connect(part, QOverload<>::of(&KParts::ReadOnlyPart::completed), this, &SearchBarPlugin::HTMLDocLoaded);
0130             connect(part, &KParts::ReadOnlyPart::started, this, &SearchBarPlugin::HTMLLoadingStarted);
0131         }
0132         // Delay since when destroying tabs part 0 gets activated for a bit, before the proper part
0133         QTimer::singleShot(0, this, &SearchBarPlugin::updateComboVisibility);
0134     } else if (o == m_searchCombo->lineEdit() && e->type() == QEvent::KeyPress) {
0135         QKeyEvent *k = (QKeyEvent *)e;
0136         if (k->modifiers() & Qt::ControlModifier) {
0137             if (k->key() == Qt::Key_Down) {
0138                 nextSearchEntry();
0139                 return true;
0140             }
0141             if (k->key() == Qt::Key_Up) {
0142                 previousSearchEntry();
0143                 return true;
0144             }
0145         }
0146     }
0147     return KonqParts::Plugin::eventFilter(o, e);
0148 }
0149 
0150 void SearchBarPlugin::nextSearchEntry()
0151 {
0152     if (m_searchMode == FindInThisPage) {
0153         m_searchMode = UseSearchProvider;
0154         if (m_searchEngines.isEmpty()) {
0155             m_currentEngine = QStringLiteral("google");
0156         } else {
0157             m_currentEngine = m_searchEngines.first();
0158         }
0159     } else {
0160         const int index = m_searchEngines.indexOf(m_currentEngine) + 1;
0161         if (index >= m_searchEngines.count()) {
0162             m_searchMode = FindInThisPage;
0163         } else {
0164             m_currentEngine = m_searchEngines.at(index);
0165         }
0166     }
0167     setIcon();
0168 }
0169 
0170 void SearchBarPlugin::previousSearchEntry()
0171 {
0172     if (m_searchMode == FindInThisPage) {
0173         m_searchMode = UseSearchProvider;
0174         if (m_searchEngines.isEmpty()) {
0175             m_currentEngine = QStringLiteral("google");
0176         } else {
0177             m_currentEngine =  m_searchEngines.last();
0178         }
0179     } else {
0180         const int index = m_searchEngines.indexOf(m_currentEngine) - 1;
0181         if (index <= 0) {
0182             m_searchMode = FindInThisPage;
0183         } else {
0184             m_currentEngine = m_searchEngines.at(index);
0185         }
0186     }
0187     setIcon();
0188 }
0189 
0190 // Called when activating the combobox (Key_Return, or item in popup or in completionbox)
0191 void SearchBarPlugin::startSearch(const QString &search)
0192 {
0193     if (m_urlEnterLock || search.isEmpty() || m_part.isNull()) {
0194         return;
0195     }
0196     m_lastSearch = search;
0197 
0198     if (m_searchMode == FindInThisPage) {
0199         TextExtension *textExt = TextExtension::childObject(m_part);
0200         if (textExt) {
0201             textExt->findText(search, KFind::SearchOptions());
0202         }
0203     } else if (m_searchMode == UseSearchProvider) {
0204         m_urlEnterLock = true;
0205         const KUriFilterSearchProvider &provider = m_searchProviders.value(m_currentEngine);
0206         KUriFilterData data;
0207         data.setData(provider.defaultKey() + m_delimiter + search);
0208         //qCDebug(SEARCHBAR_LOG) << "Query:" << (provider.defaultKey() + m_delimiter + search);
0209         if (!KUriFilter::self()->filterSearchUri(data, KUriFilter::WebShortcutFilter)) {
0210             qCWarning(SEARCHBAR_LOG) << "Failed to filter using web shortcut:" << provider.defaultKey();
0211             return;
0212         }
0213 
0214         KParts::NavigationExtension *ext = KParts::NavigationExtension::childObject(m_part);
0215         if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
0216             KParts::OpenUrlArguments arguments;
0217             BrowserArguments browserArguments;
0218             browserArguments.setNewTab(true);
0219             if (ext) {
0220 
0221 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0222                 emit ext->createNewWindow(data.uri());
0223 #else
0224                 if (auto browserExtension = qobject_cast<BrowserExtension *>(ext)) {
0225                      emit browserExtension->browserCreateNewWindow(data.uri(), arguments, browserArguments);
0226                 } else {
0227                      emit ext->createNewWindow(data.uri());
0228                 }
0229 #endif
0230             }
0231         } else {
0232             if (ext) {
0233                 emit ext->openUrlRequest(data.uri());
0234                 if (!m_part.isNull()) {
0235                     m_part->widget()->setFocus();    // #152923
0236                 }
0237             }
0238         }
0239     }
0240 
0241     m_searchCombo->addToHistory(search);
0242     m_searchCombo->setItemIcon(0, m_searchIcon);
0243 
0244     m_urlEnterLock = false;
0245 }
0246 
0247 void SearchBarPlugin::setIcon()
0248 {
0249     if (m_searchMode == FindInThisPage) {
0250         m_searchIcon = QIcon::fromTheme(QStringLiteral("edit-find")).pixmap(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize));
0251     } else {
0252         const QString engine = (m_currentEngine.isEmpty() ? m_searchEngines.first() : m_currentEngine);
0253         //qCDebug(SEARCHBAR_LOG) << "Icon Name:" << m_searchProviders.value(engine).iconName();
0254         const QString iconName = m_searchProviders.value(engine).iconName();
0255         if (iconName.startsWith(QLatin1Char('/'))) {
0256             m_searchIcon = QPixmap(iconName);
0257         } else {
0258             m_searchIcon = QIcon::fromTheme(iconName).pixmap(qApp->style()->pixelMetric(QStyle::PM_SmallIconSize));
0259         }
0260     }
0261 
0262     // Create a bit wider icon with arrow
0263     QPixmap arrowmap = QPixmap(m_searchIcon.width() + 5, m_searchIcon.height() + 5);
0264     arrowmap.fill(m_searchCombo->lineEdit()->palette().color(m_searchCombo->lineEdit()->backgroundRole()));
0265     QPainter p(&arrowmap);
0266     p.drawPixmap(0, 2, m_searchIcon);
0267     QStyleOption opt;
0268     opt.state = QStyle::State_None;
0269     opt.rect = QRect(arrowmap.width() - 6, arrowmap.height() - 5, 6, 5);
0270     m_searchCombo->style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, &p, m_searchCombo);
0271     p.end();
0272     m_searchIcon = arrowmap;
0273     m_searchCombo->setIcon(m_searchIcon);
0274 
0275     // Set the placeholder text to be the search engine name...
0276 
0277     if (m_searchMode == FindInThisPage) {
0278         m_searchCombo->lineEdit()->setPlaceholderText(i18n("Find in Page..."));
0279     } else {
0280         if (m_searchProviders.contains(m_currentEngine)) {
0281             m_searchCombo->lineEdit()->setPlaceholderText(m_searchProviders.value(m_currentEngine).name());
0282         }
0283     }
0284 }
0285 
0286 void SearchBarPlugin::showSelectionMenu()
0287 {
0288     // Update the configuration, if needed, before showing the menu items...
0289     if (m_reloadConfiguration) {
0290         configurationChanged();
0291     }
0292 
0293     if (!m_popupMenu) {
0294         m_popupMenu = new QMenu(m_searchCombo);
0295         m_popupMenu->setObjectName(QStringLiteral("search selection menu"));
0296 
0297         if (enableFindInPage()) {
0298             m_popupMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("Find in This Page"),
0299                                    this, &SearchBarPlugin::useFindInThisPage);
0300             m_popupMenu->addSeparator();
0301         }
0302 
0303         for (int i = 0, count = m_searchEngines.count(); i != count; ++i) {
0304             const KUriFilterSearchProvider &provider = m_searchProviders.value(m_searchEngines.at(i));
0305             QAction *action = m_popupMenu->addAction(QIcon::fromTheme(provider.iconName()), provider.name());
0306             action->setData(QVariant::fromValue(i));
0307         }
0308 
0309         m_popupMenu->addSeparator();
0310         m_popupMenu->addAction(QIcon::fromTheme(QStringLiteral("preferences-web-browser-shortcuts")), i18n("Select Search Engines..."),
0311                                this, &SearchBarPlugin::selectSearchEngines);
0312         connect(m_popupMenu, &QMenu::triggered, this, &SearchBarPlugin::menuActionTriggered);
0313     } else {
0314         for (QAction *action: m_addSearchActions) {
0315             m_popupMenu->removeAction(action);
0316             delete action;
0317         }
0318         m_addSearchActions.clear();
0319     }
0320 
0321     QList<QAction *> actions = m_popupMenu->actions();
0322     QAction *before = nullptr;
0323     if (actions.size() > 1) {
0324         before = actions[actions.size() - 2];
0325     }
0326 
0327     for (const QString &title: m_openSearchDescs.keys()) {
0328         QAction *addSearchAction = new QAction(m_popupMenu);
0329         addSearchAction->setText(i18n("Add %1...", title));
0330         m_addSearchActions.append(addSearchAction);
0331         addSearchAction->setData(QVariant::fromValue(title));
0332         m_popupMenu->insertAction(before, addSearchAction);
0333     }
0334 
0335     m_popupMenu->popup(m_searchCombo->mapToGlobal(QPoint(0, m_searchCombo->height() + 1)));
0336 }
0337 
0338 void SearchBarPlugin::useFindInThisPage()
0339 {
0340     m_searchMode = FindInThisPage;
0341     setIcon();
0342 }
0343 
0344 void SearchBarPlugin::menuActionTriggered(QAction *action)
0345 {
0346     bool ok = false;
0347     const int id = action->data().toInt(&ok);
0348     if (ok) {
0349         m_searchMode = UseSearchProvider;
0350         m_currentEngine = m_searchEngines.at(id);
0351         setIcon();
0352         m_searchCombo->lineEdit()->selectAll();
0353         return;
0354     }
0355 
0356     m_searchCombo->lineEdit()->setPlaceholderText(QString());
0357     const QString openSearchTitle = action->data().toString();
0358     if (!openSearchTitle.isEmpty()) {
0359         const QString openSearchHref = m_openSearchDescs.value(openSearchTitle);
0360         QUrl url;
0361         QUrl openSearchUrl = QUrl(openSearchHref);
0362         if (openSearchUrl.isRelative()) {
0363             const QUrl docUrl = !m_part.isNull() ? m_part->url() : QUrl();
0364             QString host = docUrl.scheme() + QLatin1String("://") + docUrl.host();
0365             if (docUrl.port() != -1) {
0366                 host += QLatin1String(":") + QString::number(docUrl.port());
0367             }
0368             url = docUrl.resolved(QUrl(openSearchHref));
0369         } else {
0370             url = QUrl(openSearchHref);
0371         }
0372     }
0373 }
0374 
0375 void SearchBarPlugin::selectSearchEngines()
0376 {
0377     KIO::CommandLauncherJob *job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5 webshortcuts"));
0378     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, !m_part.isNull() ? m_part->widget() : nullptr));
0379     job->start();
0380 }
0381 
0382 void SearchBarPlugin::configurationChanged()
0383 {
0384     delete m_popupMenu;
0385     m_popupMenu = nullptr;
0386     m_addSearchActions.clear();
0387     m_searchEngines.clear();
0388     m_searchProviders.clear();
0389 
0390     KUriFilterData data;
0391     data.setSearchFilteringOptions(KUriFilterData::RetrievePreferredSearchProvidersOnly);
0392     data.setAlternateDefaultSearchProvider(QStringLiteral("google"));
0393 
0394     if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) {
0395         m_delimiter = data.searchTermSeparator();
0396         for (const QString &engine: data.preferredSearchProviders()) {
0397             //qCDebug(SEARCHBAR_LOG) << "Found search provider:" << engine;
0398             const KUriFilterSearchProvider &provider = data.queryForSearchProvider(engine);
0399 
0400             m_searchProviders.insert(provider.desktopEntryName(), provider);
0401             m_searchEngines << provider.desktopEntryName();
0402         }
0403     }
0404 
0405     //qCDebug(SEARCHBAR_LOG) << "Found search engines:" << m_searchEngines;
0406     KConfigGroup config = KConfigGroup(KSharedConfig::openConfig(), "SearchBar");
0407     m_searchMode = (SearchModes) config.readEntry("Mode", static_cast<int>(UseSearchProvider));
0408     const QString defaultSearchEngine((m_searchEngines.isEmpty() ?  QStringLiteral("google") : m_searchEngines.first()));
0409     m_currentEngine = config.readEntry("CurrentEngine", defaultSearchEngine);
0410 
0411     m_reloadConfiguration = false;
0412     setIcon();
0413 }
0414 
0415 void SearchBarPlugin::reloadConfiguration()
0416 {
0417     // NOTE: We do not directly connect the dbus signal to the configurationChanged
0418     // slot because our slot my be called before the filter plugins, in which case we
0419     // simply end up retrieving the same configuration information from the plugin.
0420     m_reloadConfiguration = true;
0421 }
0422 
0423 void SearchBarPlugin::updateComboVisibility()
0424 {
0425     if (m_part.isNull()) {
0426         return;
0427     }
0428     // NOTE: We hide the search combobox if the embedded kpart is ReadWrite
0429     // because web browsers by their very nature are ReadOnly kparts...
0430     m_searchComboAction->setVisible(!m_part->inherits("ReadWritePart") &&
0431                                     !m_searchComboAction->associatedWidgets().isEmpty());
0432     m_openSearchDescs.clear();
0433 }
0434 
0435 void SearchBarPlugin::focusSearchbar()
0436 {
0437     m_searchCombo->setFocus(Qt::ShortcutFocusReason);
0438 }
0439 
0440 void SearchBarPlugin::searchTextChanged(const QString &text)
0441 {
0442     // Don't do anything if the user just activated the search for this text
0443     // Popping up suggestions again would just lead to an annoying popup (#231213)
0444     if (m_lastSearch == text) {
0445         return;
0446     }
0447 
0448     // Don't do anything if the user is still pressing on the mouse button
0449     if (qApp->mouseButtons()) {
0450         return;
0451     }
0452 }
0453 
0454 void SearchBarPlugin::HTMLDocLoaded()
0455 {
0456     if (m_part.isNull() || m_part->url().host().isEmpty()) {
0457         return;
0458     }
0459 
0460     //NOTE: the link below seems to be dead
0461     // Testcase for this code: http://search.iwsearch.net
0462     HtmlExtension *ext = HtmlExtension::childObject(m_part);
0463     AsyncSelectorInterface *asyncIface = qobject_cast<AsyncSelectorInterface*>(ext);
0464     const QString query(QStringLiteral("head > link[rel=\"search\"][type=\"application/opensearchdescription+xml\"]"));
0465 #if QT_VERSION_MAJOR < 6
0466     KParts::SelectorInterface *selectorInterface = qobject_cast<KParts::SelectorInterface *>(ext);
0467 
0468     if (selectorInterface) {
0469         //if (headElelement.getAttribute("profile") != "http://a9.com/-/spec/opensearch/1.1/") {
0470         //    kWarning() << "Warning: there is no profile attribute or wrong profile attribute in <head>, as specified by open search specification 1.1";
0471         //}
0472         const QList<KParts::SelectorInterface::Element> linkNodes = selectorInterface->querySelectorAll(query, KParts::SelectorInterface::EntireContent);
0473         insertOpenSearchEntries(linkNodes);
0474     } else if (asyncIface) {
0475 #else
0476     if (asyncIface) {
0477 #endif
0478         auto callback = [this](const QList<AsyncSelectorInterface::Element>& elements) {
0479             insertOpenSearchEntries(elements);
0480         };
0481         asyncIface->querySelectorAllAsync(query, AsyncSelectorInterface::EntireContent, callback);
0482     }
0483 }
0484 
0485 void SearchBarPlugin::insertOpenSearchEntries(const QList<AsyncSelectorInterface::Element>& elements)
0486 {
0487     for (const AsyncSelectorInterface::Element &link : elements) {
0488         const QString title = link.attribute(QStringLiteral("title"));
0489         const QString href = link.attribute(QStringLiteral("href"));
0490         //qCDebug(SEARCHBAR_LOG) << "Found opensearch" << title << href;
0491         m_openSearchDescs.insert(title, href);
0492         // TODO associate this with m_part; we can get descs from multiple tabs here...
0493     }
0494 }
0495 
0496 void SearchBarPlugin::openSearchEngineAdded(const QString &name, const QString &searchUrl, const QString &fileName)
0497 {
0498     //qCDebug(SEARCHBAR_LOG) << "New Open Search Engine Added: " << name << ", searchUrl " << searchUrl;
0499 
0500     KConfig _service(m_searchProvidersDir + fileName + ".desktop", KConfig::SimpleConfig);
0501     KConfigGroup service(&_service, "Desktop Entry");
0502     service.writeEntry("Type", "Service");
0503     service.writeEntry("ServiceTypes", "SearchProvider");
0504     service.writeEntry("Name", name);
0505     service.writeEntry("Query", searchUrl);
0506     service.writeEntry("Keys", fileName);
0507     // TODO
0508     service.writeEntry("Charset", "" /* provider->charset() */);
0509 
0510     // we might be overwriting a hidden entry
0511     service.writeEntry("Hidden", false);
0512 
0513     // Show the add web shortcut widget
0514     if (!m_addWSWidget) {
0515         m_addWSWidget = new WebShortcutWidget(m_searchCombo);
0516         m_addWSWidget->setWindowFlags(Qt::Popup);
0517 
0518         connect(m_addWSWidget, &WebShortcutWidget::webShortcutSet, this, &SearchBarPlugin::webShortcutSet);
0519     }
0520 
0521     QPoint pos = m_searchCombo->mapToGlobal(QPoint(m_searchCombo->width() - m_addWSWidget->width(), m_searchCombo->height() + 1));
0522     m_addWSWidget->setGeometry(QRect(pos, m_addWSWidget->size()));
0523     m_addWSWidget->show(name, fileName);
0524 }
0525 
0526 void SearchBarPlugin::webShortcutSet(const QString &name, const QString &webShortcut, const QString &fileName)
0527 {
0528     Q_UNUSED(name);
0529     KConfig _service(m_searchProvidersDir + fileName + ".desktop", KConfig::SimpleConfig);
0530     KConfigGroup service(&_service, "Desktop Entry");
0531     service.writeEntry("Keys", webShortcut);
0532     _service.sync();
0533 
0534     // Update filters in running applications including ourselves...
0535     QDBusConnection::sessionBus().send(QDBusMessage::createSignal(QStringLiteral("/"), QStringLiteral("org.kde.KUriFilterPlugin"), QStringLiteral("configure")));
0536 
0537     // If the providers changed, tell sycoca to rebuild its database...
0538     KBuildSycocaProgressDialog::rebuildKSycoca(m_searchCombo);
0539 }
0540 
0541 void SearchBarPlugin::HTMLLoadingStarted()
0542 {
0543     // reset the open search availability, so that if there is previously detected engine,
0544     // it will not be shown
0545     m_openSearchDescs.clear();
0546 }
0547 
0548 void SearchBarPlugin::addSearchSuggestion(const QStringList &suggestions)
0549 {
0550     m_searchCombo->setSuggestionItems(suggestions);
0551 }
0552 
0553 SearchBarCombo::SearchBarCombo(QWidget *parent)
0554     : KHistoryComboBox(true, parent)
0555 {
0556     setDuplicatesEnabled(false);
0557     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
0558     setMaximumWidth(300);
0559     connect(this, &KHistoryComboBox::cleared, this, &SearchBarCombo::historyCleared);
0560 
0561     Q_ASSERT(useCompletion());
0562 
0563     KConfigGroup config(KSharedConfig::openConfig(), "SearchBar");
0564     setCompletionMode(static_cast<KCompletion::CompletionMode>(config.readEntry("CompletionMode", static_cast<int>(KCompletion::CompletionPopup))));
0565     const QStringList list = config.readEntry("History list", QStringList());
0566     setHistoryItems(list, true);
0567     Q_ASSERT(currentText().isEmpty()); // KHistoryComboBox calls clearEditText
0568 
0569     // use our own item delegate to display our fancy stuff :D
0570     KCompletionBox *box = completionBox();
0571     box->setItemDelegate(new SearchBarItemDelegate(this));
0572     connect(lineEdit(), &QLineEdit::textEdited, box, &KCompletionBox::setCancelledText);
0573 }
0574 
0575 SearchBarCombo::~SearchBarCombo()
0576 {
0577     KConfigGroup config(KSharedConfig::openConfig(), "SearchBar");
0578     config.writeEntry("History list", historyItems());
0579     const int mode = completionMode();
0580     config.writeEntry("CompletionMode", mode);
0581 }
0582 
0583 const QPixmap &SearchBarCombo::icon() const
0584 {
0585     return m_icon;
0586 }
0587 
0588 void SearchBarCombo::setIcon(const QPixmap &icon)
0589 {
0590     m_icon = icon;
0591     const QString editText = currentText();
0592     if (count() == 0) {
0593         insertItem(0, m_icon, nullptr);
0594     } else {
0595         for (int i = 0; i < count(); i++) {
0596             setItemIcon(i, m_icon);
0597         }
0598     }
0599     setEditText(editText);
0600 }
0601 
0602 int SearchBarCombo::findHistoryItem(const QString &searchText)
0603 {
0604     for (int i = 0; i < count(); i++) {
0605         if (itemText(i) == searchText) {
0606             return i;
0607         }
0608     }
0609 
0610     return -1;
0611 }
0612 
0613 bool SearchBarCombo::overIcon(int x)
0614 {
0615     QStyleOptionComplex opt;
0616     const int x0 = QStyle::visualRect(layoutDirection(), style()->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this), rect()).x();
0617     return (x > x0 + 2 && x < lineEdit()->x());
0618 }
0619 
0620 void SearchBarCombo::contextMenuEvent(QContextMenuEvent *e)
0621 {
0622     if (overIcon(e->x())) {
0623         // Do not pass on the event, so that the combo box context menu
0624         // does not pop up over the search engine selection menu.  That
0625         // menu will have been triggered by the iconClicked() signal emitted
0626         // in mousePressEvent() below.
0627         e->accept();
0628     } else {
0629         KHistoryComboBox::contextMenuEvent(e);
0630     }
0631 }
0632 
0633 void SearchBarCombo::mousePressEvent(QMouseEvent *e)
0634 {
0635     if (overIcon(e->x())) {
0636         emit iconClicked();
0637         e->accept();
0638     } else {
0639         KHistoryComboBox::mousePressEvent(e);
0640     }
0641 }
0642 
0643 void SearchBarCombo::historyCleared()
0644 {
0645     setIcon(m_icon);
0646 }
0647 
0648 void SearchBarCombo::setSuggestionItems(const QStringList &suggestions)
0649 {
0650     if (!m_suggestions.isEmpty()) {
0651         clearSuggestions();
0652     }
0653 
0654     m_suggestions = suggestions;
0655     if (!suggestions.isEmpty()) {
0656         const int size = completionBox()->count();
0657         QListWidgetItem *item = new QListWidgetItem(suggestions.at(0));
0658         item->setData(Qt::UserRole, "suggestion");
0659         completionBox()->insertItem(size + 1, item);
0660         const int suggestionCount = suggestions.count();
0661         for (int i = 1; i < suggestionCount; i++) {
0662             completionBox()->insertItem(size + 1 + i, suggestions.at(i));
0663         }
0664         completionBox()->popup();
0665     }
0666 }
0667 
0668 void SearchBarCombo::clearSuggestions()
0669 {
0670     // Removing items can change the current item in completion box,
0671     // which makes the lineEdit emit textEdited, and we would then
0672     // re-enter this method, so block lineEdit signals.
0673     const bool oldBlock = lineEdit()->blockSignals(true);
0674     int size = completionBox()->count();
0675     if (!m_suggestions.isEmpty() && size >= m_suggestions.count()) {
0676         for (int i = size - 1; i >= size - m_suggestions.size(); i--) {
0677             completionBox()->takeItem(i);
0678         }
0679     }
0680     m_suggestions.clear();
0681     lineEdit()->blockSignals(oldBlock);
0682 }
0683 
0684 SearchBarItemDelegate::SearchBarItemDelegate(QObject *parent)
0685     : QItemDelegate(parent)
0686 {
0687 }
0688 
0689 void SearchBarItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0690 {
0691     QString userText = index.data(Qt::UserRole).toString();
0692     QString text = index.data(Qt::DisplayRole).toString();
0693 
0694     // Get item data
0695     if (!userText.isEmpty()) {
0696         // This font is for the "information" text, small size + italic + gray in color
0697         QFont usrTxtFont = option.font;
0698         usrTxtFont.setItalic(true);
0699         usrTxtFont.setPointSize(6);
0700 
0701         QFontMetrics usrTxtFontMetrics(usrTxtFont);
0702         int width = usrTxtFontMetrics.horizontalAdvance(userText);
0703         QRect rect(option.rect.x(), option.rect.y(), option.rect.width() - width, option.rect.height());
0704         QFontMetrics textFontMetrics(option.font);
0705         QString elidedText = textFontMetrics.elidedText(text,
0706                              Qt::ElideRight, option.rect.width() - width - option.decorationSize.width());
0707 
0708         QAbstractItemModel *itemModel = const_cast<QAbstractItemModel *>(index.model());
0709         itemModel->setData(index, elidedText, Qt::DisplayRole);
0710         QItemDelegate::paint(painter, option, index);
0711         itemModel->setData(index, text, Qt::DisplayRole);
0712 
0713         painter->setFont(usrTxtFont);
0714         painter->setPen(QPen(QColor(Qt::gray)));
0715         painter->drawText(option.rect, Qt::AlignRight, userText);
0716 
0717         // Draw a separator above this item
0718         if (index.row() > 0) {
0719             painter->drawLine(option.rect.x(), option.rect.y(), option.rect.x() + option.rect.width(), option.rect.y());
0720         }
0721     } else {
0722         QItemDelegate::paint(painter, option, index);
0723     }
0724 }
0725 
0726 bool SearchBarPlugin::enableFindInPage() const
0727 {
0728     return true;
0729 }
0730 
0731 #include "searchbar.moc"