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"