File indexing completed on 2024-05-12 04:58:09

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 #include "websearchbar.h"
0019 #include "browserwindow.h"
0020 #include "mainapplication.h"
0021 #include "tabbedwebview.h"
0022 #include "webpage.h"
0023 #include "settings.h"
0024 #include "qzsettings.h"
0025 #include "tabwidget.h"
0026 #include "searchenginesdialog.h"
0027 #include "networkmanager.h"
0028 #include "iconprovider.h"
0029 #include "scripts.h"
0030 
0031 #include <QMimeData>
0032 #include <QAbstractItemView>
0033 #include <QCompleter>
0034 #include <QStringListModel>
0035 #include <QMenu>
0036 #include <QTimer>
0037 #include <QClipboard>
0038 #include <QContextMenuEvent>
0039 
0040 WebSearchBar_Button::WebSearchBar_Button(QWidget* parent)
0041     : ClickableLabel(parent)
0042 {
0043     setObjectName("websearchbar-searchbutton");
0044     setCursor(QCursor(Qt::PointingHandCursor));
0045     setFocusPolicy(Qt::ClickFocus);
0046 }
0047 
0048 void WebSearchBar_Button::contextMenuEvent(QContextMenuEvent* event)
0049 {
0050     event->accept();
0051 }
0052 
0053 WebSearchBar::WebSearchBar(BrowserWindow* window)
0054     : LineEdit(window)
0055     , m_window(window)
0056     , m_reloadingEngines(false)
0057 {
0058     setObjectName("websearchbar");
0059     setDragEnabled(true);
0060 
0061     m_buttonSearch = new WebSearchBar_Button(this);
0062 
0063     m_boxSearchType = new ButtonWithMenu(this);
0064     m_boxSearchType->setObjectName("websearchbar-searchprovider-combobox");
0065     m_boxSearchType->setFocusProxy(this);
0066     // RTL Support
0067     // If we don't add 'm_boxSearchType' by following code, then we should use suitable padding-left value
0068     // but then, when typing RTL text the layout dynamically changed and within RTL layout direction
0069     // padding-left is equivalent to padding-right and vice versa, and because style sheet is
0070     // not changed dynamically this create padding problems.
0071     addWidget(m_boxSearchType, LineEdit::LeftSide);
0072 
0073     addWidget(m_buttonSearch, LineEdit::RightSide);
0074 
0075     connect(m_buttonSearch, &ClickableLabel::clicked, this, &WebSearchBar::search);
0076     connect(m_buttonSearch, &ClickableLabel::middleClicked, this, &WebSearchBar::searchInNewTab);
0077     connect(m_boxSearchType, &ButtonWithMenu::activeItemChanged, this, &WebSearchBar::searchChanged);
0078 
0079     setWidgetSpacing(0);
0080 
0081     m_searchManager = mApp->searchEnginesManager();
0082     connect(m_boxSearchType->menu(), &QMenu::aboutToShow, this, &WebSearchBar::aboutToShowMenu);
0083 
0084     m_completer = new QCompleter(this);
0085     m_completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
0086     m_completerModel = new QStringListModel(this);
0087     m_completer->setModel(m_completerModel);
0088     m_completer->popup()->setMinimumHeight(90);
0089     setCompleter(m_completer);
0090     connect(m_completer->popup(), &QAbstractItemView::activated, this, &WebSearchBar::search);
0091 
0092     m_openSearchEngine = new OpenSearchEngine(this);
0093     m_openSearchEngine->setNetworkAccessManager(mApp->networkManager());
0094     connect(m_openSearchEngine, &OpenSearchEngine::suggestions, this, &WebSearchBar::addSuggestions);
0095     connect(this, &QLineEdit::textEdited, m_openSearchEngine, &OpenSearchEngine::requestSuggestions);
0096 
0097     editAction(PasteAndGo)->setText(tr("Paste And &Search"));
0098     editAction(PasteAndGo)->setIcon(QIcon::fromTheme(QSL("edit-paste")));
0099     connect(editAction(PasteAndGo), &QAction::triggered, this, &WebSearchBar::pasteAndGo);
0100 
0101     QTimer::singleShot(0, this, &WebSearchBar::setupEngines);
0102 }
0103 
0104 void WebSearchBar::aboutToShowMenu()
0105 {
0106     QMenu* menu = m_boxSearchType->menu();
0107 
0108     menu->addSeparator();
0109 
0110     m_window->weView()->page()->runJavaScript(Scripts::getOpenSearchLinks(), WebPage::SafeJsWorld, [this, menu](const QVariant &res) {
0111         const QVariantList &list = res.toList();
0112         for (const QVariant &val : list) {
0113             const QVariantMap &link = val.toMap();
0114             QUrl url = m_window->weView()->url().resolved(link.value(QSL("url")).toUrl());
0115             QString title = link.value(QSL("title")).toString();
0116 
0117             if (url.isEmpty())
0118                 continue;
0119 
0120             if (title.isEmpty())
0121                 title = m_window->weView()->title();
0122 
0123             menu->addAction(m_window->weView()->icon(), tr("Add %1 ...").arg(title), this, &WebSearchBar::addEngineFromAction)->setData(url);
0124         }
0125 
0126         menu->addSeparator();
0127         menu->addAction(IconProvider::settingsIcon(), tr("Manage Search Engines"), this, &WebSearchBar::openSearchEnginesDialog);
0128     });
0129 }
0130 
0131 void WebSearchBar::addSuggestions(const QStringList &list)
0132 {
0133     if (qzSettings->showWSBSearchSuggestions) {
0134         QStringList list_ = list.mid(0, 6);
0135         m_completerModel->setStringList(list_);
0136         m_completer->complete();
0137     }
0138 }
0139 
0140 void WebSearchBar::openSearchEnginesDialog()
0141 {
0142     if (!m_searchDialog)
0143         m_searchDialog = new SearchEnginesDialog(this);
0144 
0145     m_searchDialog->open();
0146     m_searchDialog->raise();
0147     m_searchDialog->activateWindow();
0148 }
0149 
0150 void WebSearchBar::enableSearchSuggestions(bool enable)
0151 {
0152     Settings settings;
0153     settings.beginGroup(QSL("SearchEngines"));
0154     settings.setValue(QSL("showSuggestions"), enable);
0155     settings.endGroup();
0156 
0157     qzSettings->showWSBSearchSuggestions = enable;
0158     m_completerModel->setStringList(QStringList());
0159     updateOpenSearchEngine();
0160 }
0161 
0162 void WebSearchBar::setupEngines()
0163 {
0164     disconnect(m_searchManager, &SearchEnginesManager::enginesChanged, this, &WebSearchBar::setupEngines);
0165     m_reloadingEngines = true;
0166 
0167     QString activeEngine = m_searchManager->startingEngineName();
0168 
0169     if (m_boxSearchType->allItems().count() != 0) {
0170         activeEngine = m_activeEngine.name;
0171     }
0172 
0173     m_boxSearchType->clearItems();
0174 
0175     const auto engines = m_searchManager->allEngines();
0176     for (const SearchEngine &en : engines) {
0177         ButtonWithMenu::Item item;
0178         item.icon = en.icon;
0179         item.text = en.name;
0180         QVariant v;
0181         v.setValue(en);
0182         item.userData = v;
0183 
0184         m_boxSearchType->addItem(item);
0185 
0186         if (item.text == activeEngine) {
0187             m_boxSearchType->setCurrentItem(item, false);
0188         }
0189     }
0190 
0191     searchChanged(m_boxSearchType->currentItem());
0192 
0193     connect(m_searchManager, &SearchEnginesManager::enginesChanged, this, &WebSearchBar::setupEngines);
0194     m_reloadingEngines = false;
0195 }
0196 
0197 void WebSearchBar::searchChanged(const ButtonWithMenu::Item &item)
0198 {
0199     setPlaceholderText(item.text);
0200     m_completerModel->setStringList(QStringList());
0201 
0202     m_activeEngine = item.userData.value<SearchEngine>();
0203 
0204     updateOpenSearchEngine();
0205 
0206     m_searchManager->setActiveEngine(m_activeEngine);
0207 
0208     if (qzSettings->searchOnEngineChange && !m_reloadingEngines && !text().isEmpty()) {
0209         search();
0210     }
0211 }
0212 
0213 void WebSearchBar::instantSearchChanged(bool enable)
0214 {
0215     Settings settings;
0216     settings.beginGroup(QSL("SearchEngines"));
0217     settings.setValue(QSL("SearchOnEngineChange"), enable);
0218     settings.endGroup();
0219     qzSettings->searchOnEngineChange = enable;
0220 }
0221 
0222 void WebSearchBar::search()
0223 {
0224     m_window->weView()->setFocus();
0225     m_window->weView()->load(m_searchManager->searchResult(m_activeEngine, text()));
0226 }
0227 
0228 void WebSearchBar::searchInNewTab()
0229 {
0230     int index = m_window->tabWidget()->addView(QUrl());
0231     m_window->weView(index)->setFocus();
0232     m_window->weView(index)->load(m_searchManager->searchResult(m_activeEngine, text()));
0233 }
0234 
0235 void WebSearchBar::addEngineFromAction()
0236 {
0237     if (auto* action = qobject_cast<QAction*>(sender())) {
0238         m_searchManager->addEngine(action->data().toUrl());
0239     }
0240 }
0241 
0242 void WebSearchBar::pasteAndGo()
0243 {
0244     clear();
0245     paste();
0246     search();
0247 }
0248 
0249 void WebSearchBar::contextMenuEvent(QContextMenuEvent* event)
0250 {
0251     Q_UNUSED(event)
0252 
0253     QMenu* menu = createContextMenu();
0254     menu->setAttribute(Qt::WA_DeleteOnClose);
0255 
0256     menu->addSeparator();
0257     QAction* act = menu->addAction(tr("Show suggestions"));
0258     act->setCheckable(true);
0259     act->setChecked(qzSettings->showWSBSearchSuggestions);
0260     connect(act, &QAction::triggered, this, &WebSearchBar::enableSearchSuggestions);
0261 
0262     QAction* instantSearch = menu->addAction(tr("Search when engine changed"));
0263     instantSearch->setCheckable(true);
0264     instantSearch->setChecked(qzSettings->searchOnEngineChange);
0265     connect(instantSearch, &QAction::triggered, this, &WebSearchBar::instantSearchChanged);
0266 
0267     // Prevent choosing first option with double rightclick
0268     QPoint pos = event->globalPos();
0269     pos.setY(pos.y() + 1);
0270     menu->popup(pos);
0271 }
0272 
0273 void WebSearchBar::focusOutEvent(QFocusEvent* e)
0274 {
0275     if (text().isEmpty()) {
0276         QString search = m_boxSearchType->currentItem().text;
0277         setPlaceholderText(search);
0278     }
0279 
0280     LineEdit::focusOutEvent(e);
0281 }
0282 
0283 void WebSearchBar::dropEvent(QDropEvent* event)
0284 {
0285     if (event->mimeData()->hasText()) {
0286         QString dropText = event->mimeData()->text();
0287         setText(dropText);
0288         search();
0289 
0290         QFocusEvent event(QFocusEvent::FocusOut);
0291         LineEdit::focusOutEvent(&event);
0292         return;
0293     }
0294 
0295     LineEdit::dropEvent(event);
0296 }
0297 
0298 void WebSearchBar::keyPressEvent(QKeyEvent* event)
0299 {
0300     switch (event->key()) {
0301     case Qt::Key_V:
0302         if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
0303             pasteAndGo();
0304             event->accept();
0305             return;
0306         }
0307         break;
0308 
0309     case Qt::Key_Return:
0310     case Qt::Key_Enter:
0311         if (event->modifiers() == Qt::AltModifier) {
0312             searchInNewTab();
0313         }
0314         else {
0315             search();
0316         }
0317         break;
0318 
0319     case Qt::Key_Up:
0320         if (event->modifiers() == Qt::ControlModifier) {
0321             m_boxSearchType->selectPreviousItem();
0322         }
0323         break;
0324 
0325     case Qt::Key_Down:
0326         if (event->modifiers() == Qt::ControlModifier) {
0327             m_boxSearchType->selectNextItem();
0328         }
0329         break;
0330 
0331     default:
0332         break;
0333     }
0334 
0335     LineEdit::keyPressEvent(event);
0336 }
0337 
0338 void WebSearchBar::updateOpenSearchEngine()
0339 {
0340     if (qzSettings->showWSBSearchSuggestions) {
0341         m_openSearchEngine->setSuggestionsUrl(m_activeEngine.suggestionsUrl);
0342         m_openSearchEngine->setSuggestionsParameters(m_activeEngine.suggestionsParameters);
0343     }
0344     else {
0345         m_openSearchEngine->setSuggestionsUrl(QL1S(""));
0346     }
0347 }