1 /* ============================================================
2 * Falkon - Qt web browser
3 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 * ============================================================ */
18 #include "websearchbar.h"
19 #include "browserwindow.h"
20 #include "mainapplication.h"
21 #include "tabbedwebview.h"
22 #include "webpage.h"
23 #include "settings.h"
24 #include "qzsettings.h"
25 #include "tabwidget.h"
26 #include "clickablelabel.h"
27 #include "buttonwithmenu.h"
28 #include "searchenginesmanager.h"
29 #include "searchenginesdialog.h"
30 #include "networkmanager.h"
31 #include "iconprovider.h"
32 #include "scripts.h"
33 
34 #include <QMimeData>
35 #include <QAbstractItemView>
36 #include <QCompleter>
37 #include <QStringListModel>
38 #include <QMenu>
39 #include <QTimer>
40 #include <QClipboard>
41 #include <QContextMenuEvent>
42 
WebSearchBar_Button(QWidget * parent)43 WebSearchBar_Button::WebSearchBar_Button(QWidget* parent)
44     : ClickableLabel(parent)
45 {
46     setObjectName("websearchbar-searchbutton");
47     setCursor(QCursor(Qt::PointingHandCursor));
48     setFocusPolicy(Qt::ClickFocus);
49 }
50 
contextMenuEvent(QContextMenuEvent * event)51 void WebSearchBar_Button::contextMenuEvent(QContextMenuEvent* event)
52 {
53     event->accept();
54 }
55 
WebSearchBar(BrowserWindow * window)56 WebSearchBar::WebSearchBar(BrowserWindow* window)
57     : LineEdit(window)
58     , m_window(window)
59     , m_reloadingEngines(false)
60 {
61     setObjectName("websearchbar");
62     setDragEnabled(true);
63 
64     m_buttonSearch = new WebSearchBar_Button(this);
65 
66     m_boxSearchType = new ButtonWithMenu(this);
67     m_boxSearchType->setObjectName("websearchbar-searchprovider-combobox");
68     m_boxSearchType->setFocusProxy(this);
69     // RTL Support
70     // If we don't add 'm_boxSearchType' by following code, then we should use suitable padding-left value
71     // but then, when typing RTL text the layout dynamically changed and within RTL layout direction
72     // padding-left is equivalent to padding-right and vice versa, and because style sheet is
73     // not changed dynamically this create padding problems.
74     addWidget(m_boxSearchType, LineEdit::LeftSide);
75 
76     addWidget(m_buttonSearch, LineEdit::RightSide);
77 
78     connect(m_buttonSearch, &ClickableLabel::clicked, this, &WebSearchBar::search);
79     connect(m_buttonSearch, &ClickableLabel::middleClicked, this, &WebSearchBar::searchInNewTab);
80     connect(m_boxSearchType, &ButtonWithMenu::activeItemChanged, this, &WebSearchBar::searchChanged);
81 
82     setWidgetSpacing(0);
83 
84     m_searchManager = mApp->searchEnginesManager();
85     connect(m_boxSearchType->menu(), &QMenu::aboutToShow, this, &WebSearchBar::aboutToShowMenu);
86 
87     m_completer = new QCompleter(this);
88     m_completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
89     m_completerModel = new QStringListModel(this);
90     m_completer->setModel(m_completerModel);
91     m_completer->popup()->setMinimumHeight(90);
92     setCompleter(m_completer);
93     connect(m_completer->popup(), &QAbstractItemView::activated, this, &WebSearchBar::search);
94 
95     m_openSearchEngine = new OpenSearchEngine(this);
96     m_openSearchEngine->setNetworkAccessManager(mApp->networkManager());
97     connect(m_openSearchEngine, &OpenSearchEngine::suggestions, this, &WebSearchBar::addSuggestions);
98     connect(this, &QLineEdit::textEdited, m_openSearchEngine, &OpenSearchEngine::requestSuggestions);
99 
100     editAction(PasteAndGo)->setText(tr("Paste And &Search"));
101     editAction(PasteAndGo)->setIcon(QIcon::fromTheme(QSL("edit-paste")));
102     connect(editAction(PasteAndGo), &QAction::triggered, this, &WebSearchBar::pasteAndGo);
103 
104     QTimer::singleShot(0, this, &WebSearchBar::setupEngines);
105 }
106 
aboutToShowMenu()107 void WebSearchBar::aboutToShowMenu()
108 {
109     QMenu* menu = m_boxSearchType->menu();
110 
111     menu->addSeparator();
112 
113     m_window->weView()->page()->runJavaScript(Scripts::getOpenSearchLinks(), WebPage::SafeJsWorld, [this, menu](const QVariant &res) {
114         const QVariantList &list = res.toList();
115         for (const QVariant &val : list) {
116             const QVariantMap &link = val.toMap();
117             QUrl url = m_window->weView()->url().resolved(link.value(QSL("url")).toUrl());
118             QString title = link.value(QSL("title")).toString();
119 
120             if (url.isEmpty())
121                 continue;
122 
123             if (title.isEmpty())
124                 title = m_window->weView()->title();
125 
126             menu->addAction(m_window->weView()->icon(), tr("Add %1 ...").arg(title), this, &WebSearchBar::addEngineFromAction)->setData(url);
127         }
128 
129         menu->addSeparator();
130         menu->addAction(IconProvider::settingsIcon(), tr("Manage Search Engines"), this, &WebSearchBar::openSearchEnginesDialog);
131     });
132 }
133 
addSuggestions(const QStringList & list)134 void WebSearchBar::addSuggestions(const QStringList &list)
135 {
136     if (qzSettings->showWSBSearchSuggestions) {
137         QStringList list_ = list.mid(0, 6);
138         m_completerModel->setStringList(list_);
139         m_completer->complete();
140     }
141 }
142 
openSearchEnginesDialog()143 void WebSearchBar::openSearchEnginesDialog()
144 {
145     if (!m_searchDialog)
146         m_searchDialog = new SearchEnginesDialog(this);
147 
148     m_searchDialog->open();
149     m_searchDialog->raise();
150     m_searchDialog->activateWindow();
151 }
152 
enableSearchSuggestions(bool enable)153 void WebSearchBar::enableSearchSuggestions(bool enable)
154 {
155     Settings settings;
156     settings.beginGroup("SearchEngines");
157     settings.setValue("showSuggestions", enable);
158     settings.endGroup();
159 
160     qzSettings->showWSBSearchSuggestions = enable;
161     m_completerModel->setStringList(QStringList());
162 }
163 
setupEngines()164 void WebSearchBar::setupEngines()
165 {
166     disconnect(m_searchManager, &SearchEnginesManager::enginesChanged, this, &WebSearchBar::setupEngines);
167     m_reloadingEngines = true;
168 
169     QString activeEngine = m_searchManager->startingEngineName();
170 
171     if (m_boxSearchType->allItems().count() != 0) {
172         activeEngine = m_activeEngine.name;
173     }
174 
175     m_boxSearchType->clearItems();
176 
177     const auto engines = m_searchManager->allEngines();
178     for (const SearchEngine &en : engines) {
179         ButtonWithMenu::Item item;
180         item.icon = en.icon;
181         item.text = en.name;
182         QVariant v;
183         v.setValue<SearchEngine>(en);
184         item.userData = v;
185 
186         m_boxSearchType->addItem(item);
187 
188         if (item.text == activeEngine) {
189             m_boxSearchType->setCurrentItem(item, false);
190         }
191     }
192 
193     searchChanged(m_boxSearchType->currentItem());
194 
195     connect(m_searchManager, &SearchEnginesManager::enginesChanged, this, &WebSearchBar::setupEngines);
196     m_reloadingEngines = false;
197 }
198 
searchChanged(const ButtonWithMenu::Item & item)199 void WebSearchBar::searchChanged(const ButtonWithMenu::Item &item)
200 {
201     setPlaceholderText(item.text);
202     m_completerModel->setStringList(QStringList());
203 
204     m_activeEngine = item.userData.value<SearchEngine>();
205 
206     m_openSearchEngine->setSuggestionsUrl(m_activeEngine.suggestionsUrl);
207     m_openSearchEngine->setSuggestionsParameters(m_activeEngine.suggestionsParameters);
208 
209     m_searchManager->setActiveEngine(m_activeEngine);
210 
211     if (qzSettings->searchOnEngineChange && !m_reloadingEngines && !text().isEmpty()) {
212         search();
213     }
214 }
215 
instantSearchChanged(bool enable)216 void WebSearchBar::instantSearchChanged(bool enable)
217 {
218     Settings settings;
219     settings.beginGroup("SearchEngines");
220     settings.setValue("SearchOnEngineChange", enable);
221     settings.endGroup();
222     qzSettings->searchOnEngineChange = enable;
223 }
224 
search()225 void WebSearchBar::search()
226 {
227     m_window->weView()->setFocus();
228     m_window->weView()->load(m_searchManager->searchResult(m_activeEngine, text()));
229 }
230 
searchInNewTab()231 void WebSearchBar::searchInNewTab()
232 {
233     int index = m_window->tabWidget()->addView(QUrl());
234     m_window->weView(index)->setFocus();
235     m_window->weView(index)->load(m_searchManager->searchResult(m_activeEngine, text()));
236 }
237 
addEngineFromAction()238 void WebSearchBar::addEngineFromAction()
239 {
240     if (QAction* action = qobject_cast<QAction*>(sender())) {
241         m_searchManager->addEngine(action->data().toUrl());
242     }
243 }
244 
pasteAndGo()245 void WebSearchBar::pasteAndGo()
246 {
247     clear();
248     paste();
249     search();
250 }
251 
contextMenuEvent(QContextMenuEvent * event)252 void WebSearchBar::contextMenuEvent(QContextMenuEvent* event)
253 {
254     Q_UNUSED(event)
255 
256     QMenu* menu = createContextMenu();
257     menu->setAttribute(Qt::WA_DeleteOnClose);
258 
259     menu->addSeparator();
260     QAction* act = menu->addAction(tr("Show suggestions"));
261     act->setCheckable(true);
262     act->setChecked(qzSettings->showWSBSearchSuggestions);
263     connect(act, &QAction::triggered, this, &WebSearchBar::enableSearchSuggestions);
264 
265     QAction* instantSearch = menu->addAction(tr("Search when engine changed"));
266     instantSearch->setCheckable(true);
267     instantSearch->setChecked(qzSettings->searchOnEngineChange);
268     connect(instantSearch, &QAction::triggered, this, &WebSearchBar::instantSearchChanged);
269 
270     // Prevent choosing first option with double rightclick
271     QPoint pos = event->globalPos();
272     pos.setY(pos.y() + 1);
273     menu->popup(pos);
274 }
275 
focusOutEvent(QFocusEvent * e)276 void WebSearchBar::focusOutEvent(QFocusEvent* e)
277 {
278     if (text().isEmpty()) {
279         QString search = m_boxSearchType->currentItem().text;
280         setPlaceholderText(search);
281     }
282 
283     LineEdit::focusOutEvent(e);
284 }
285 
dropEvent(QDropEvent * event)286 void WebSearchBar::dropEvent(QDropEvent* event)
287 {
288     if (event->mimeData()->hasText()) {
289         QString dropText = event->mimeData()->text();
290         setText(dropText);
291         search();
292 
293         QFocusEvent event(QFocusEvent::FocusOut);
294         LineEdit::focusOutEvent(&event);
295         return;
296     }
297 
298     LineEdit::dropEvent(event);
299 }
300 
keyPressEvent(QKeyEvent * event)301 void WebSearchBar::keyPressEvent(QKeyEvent* event)
302 {
303     switch (event->key()) {
304     case Qt::Key_V:
305         if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
306             pasteAndGo();
307             event->accept();
308             return;
309         }
310         break;
311 
312     case Qt::Key_Return:
313     case Qt::Key_Enter:
314         if (event->modifiers() == Qt::AltModifier) {
315             searchInNewTab();
316         }
317         else {
318             search();
319         }
320         break;
321 
322     case Qt::Key_Up:
323         if (event->modifiers() == Qt::ControlModifier) {
324             m_boxSearchType->selectPreviousItem();
325         }
326         break;
327 
328     case Qt::Key_Down:
329         if (event->modifiers() == Qt::ControlModifier) {
330             m_boxSearchType->selectNextItem();
331         }
332         break;
333 
334     default:
335         break;
336     }
337 
338     LineEdit::keyPressEvent(event);
339 }
340