Falkon Develop
Cross-platform Qt-based web browser
locationcompleter.cpp
Go to the documentation of this file.
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 "locationcompleter.h"
22#include "locationbar.h"
23#include "mainapplication.h"
24#include "browserwindow.h"
25#include "tabbedwebview.h"
26#include "tabwidget.h"
27#include "history.h"
28#include "bookmarks.h"
29#include "bookmarkitem.h"
30#include "qzsettings.h"
31#include "opensearchengine.h"
32#include "networkmanager.h"
33#include "searchenginesdialog.h"
34
35#include <QWindow>
36
37LocationCompleterView* LocationCompleter::s_view = nullptr;
38LocationCompleterModel* LocationCompleter::s_model = nullptr;
39
41 : QObject(parent)
42 , m_window(nullptr)
43 , m_locationBar(nullptr)
44 , m_lastRefreshTimestamp(0)
45 , m_popupClosed(false)
46{
47 if (!s_view) {
48 s_model = new LocationCompleterModel;
49 s_view = new LocationCompleterView;
50 s_view->setModel(s_model);
51 }
52}
53
55{
56 m_window = window;
57}
58
60{
61 m_locationBar = locationBar;
62}
63
65{
66 return s_view->isVisible();
67}
68
70{
71 s_view->close();
72}
73
74void LocationCompleter::complete(const QString &string)
75{
76 QString trimmedStr = string.trimmed();
77
78 // Indicates that new completion was requested by user
79 // Eg. popup was not closed yet this completion session
80 m_popupClosed = false;
81
82 Q_EMIT cancelRefreshJob();
83
84 auto* job = new LocationCompleterRefreshJob(trimmedStr);
85 connect(job, &LocationCompleterRefreshJob::finished, this, &LocationCompleter::refreshJobFinished);
86 connect(this, SIGNAL(cancelRefreshJob()), job, SLOT(jobCancelled()));
87
88 if (qzSettings->searchFromAddressBar && qzSettings->showABSearchSuggestions && trimmedStr.length() >= 2) {
89 if (!m_openSearchEngine) {
90 m_openSearchEngine = new OpenSearchEngine(this);
91 m_openSearchEngine->setNetworkAccessManager(mApp->networkManager());
92 connect(m_openSearchEngine, &OpenSearchEngine::suggestions, this, &LocationCompleter::addSuggestions);
93 }
94 m_openSearchEngine->setSuggestionsUrl(LocationBar::searchEngine().suggestionsUrl);
95 m_openSearchEngine->setSuggestionsParameters(LocationBar::searchEngine().suggestionsParameters);
96 m_suggestionsTerm = trimmedStr;
97 m_openSearchEngine->requestSuggestions(m_suggestionsTerm);
98 } else {
99 m_oldSuggestions.clear();
100 }
101
102 // Add/update search/visit item
103 QTimer::singleShot(0, this, [=]() {
104 const QModelIndex index = s_model->index(0, 0);
105 if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) {
106 s_model->setData(index, trimmedStr, Qt::DisplayRole);
107 s_model->setData(index, trimmedStr, LocationCompleterModel::UrlRole);
108 s_model->setData(index, m_locationBar->text(), LocationCompleterModel::SearchStringRole);
109 } else {
110 auto *item = new QStandardItem();
111 item->setText(trimmedStr);
112 item->setData(trimmedStr, LocationCompleterModel::UrlRole);
113 item->setData(m_locationBar->text(), LocationCompleterModel::SearchStringRole);
114 item->setData(true, LocationCompleterModel::VisitSearchItemRole);
115 s_model->setCompletions({item});
116 addSuggestions(m_oldSuggestions);
117 }
118 showPopup();
119 if (!s_view->currentIndex().isValid()) {
120 m_ignoreCurrentChanged = true;
121 s_view->setCurrentIndex(s_model->index(0, 0));
122 m_ignoreCurrentChanged = false;
123 }
124 });
125}
126
128{
129 m_locationBar->setFocus();
130 complete(QString());
131}
132
133void LocationCompleter::refreshJobFinished()
134{
135 auto* job = qobject_cast<LocationCompleterRefreshJob*>(sender());
136 Q_ASSERT(job);
137
138 // Don't show results of older jobs
139 // Also don't open the popup again when it was already closed
140 if (!job->isCanceled() && job->timestamp() > m_lastRefreshTimestamp && !m_popupClosed) {
141 s_model->setCompletions(job->completions());
142 addSuggestions(m_oldSuggestions);
143 showPopup();
144
145 m_lastRefreshTimestamp = job->timestamp();
146
147 if (!s_view->currentIndex().isValid() && s_model->index(0, 0).data(LocationCompleterModel::VisitSearchItemRole).toBool()) {
148 m_ignoreCurrentChanged = true;
149 s_view->setCurrentIndex(s_model->index(0, 0));
150 m_ignoreCurrentChanged = false;
151 }
152
153 if (qzSettings->useInlineCompletion) {
154 Q_EMIT showDomainCompletion(job->domainCompletion());
155 }
156
157 s_model->setData(s_model->index(0, 0), m_locationBar->text(), LocationCompleterModel::SearchStringRole);
158 }
159
160 job->deleteLater();
161}
162
163void LocationCompleter::slotPopupClosed()
164{
165 m_popupClosed = true;
166 m_oldSuggestions.clear();
167
168 disconnect(s_view, &LocationCompleterView::closed, this, &LocationCompleter::slotPopupClosed);
169 disconnect(s_view, &LocationCompleterView::indexActivated, this, &LocationCompleter::indexActivated);
170 disconnect(s_view, &LocationCompleterView::indexCtrlActivated, this, &LocationCompleter::indexCtrlActivated);
171 disconnect(s_view, &LocationCompleterView::indexShiftActivated, this, &LocationCompleter::indexShiftActivated);
172 disconnect(s_view, &LocationCompleterView::indexDeleteRequested, this, &LocationCompleter::indexDeleteRequested);
174 disconnect(s_view, &LocationCompleterView::searchEnginesDialogRequested, this, &LocationCompleter::openSearchEnginesDialog);
175 disconnect(s_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &LocationCompleter::currentChanged);
176
177 Q_EMIT popupClosed();
178}
179
180void LocationCompleter::addSuggestions(const QStringList &suggestions)
181{
182 const auto suggestionItems = s_model->suggestionItems();
183
184 // Delete existing suggestions
185 for (QStandardItem *item : suggestionItems) {
186 s_model->takeRow(item->row());
187 delete item;
188 }
189
190 // Add new suggestions
191 QList<QStandardItem*> items;
192 for (const QString &suggestion : suggestions) {
193 auto* item = new QStandardItem();
194 item->setText(suggestion);
195 item->setData(suggestion, LocationCompleterModel::TitleRole);
196 item->setData(suggestion, LocationCompleterModel::UrlRole);
197 item->setData(m_suggestionsTerm, LocationCompleterModel::SearchStringRole);
199 items.append(item);
200 }
201
202 s_model->addCompletions(items);
203 m_oldSuggestions = suggestions;
204
205 if (!m_popupClosed) {
206 showPopup();
207 }
208}
209
210void LocationCompleter::currentChanged(const QModelIndex &index)
211{
212 if (m_ignoreCurrentChanged) {
213 return;
214 }
215
216 QString completion = index.data().toString();
217
218 bool completeDomain = index.data(LocationCompleterModel::VisitSearchItemRole).toBool();
219
220 const QString originalText = s_model->index(0, 0).data(LocationCompleterModel::SearchStringRole).toString();
221
222 // Domain completion was dismissed
223 if (completeDomain && completion == originalText) {
224 completeDomain = false;
225 }
226
227 if (completion.isEmpty()) {
228 completeDomain = true;
229 completion = originalText;
230 }
231
232 Q_EMIT showCompletion(completion, completeDomain);
233}
234
235void LocationCompleter::indexActivated(const QModelIndex &index)
236{
237 Q_ASSERT(index.isValid());
238
239 closePopup();
240
241 // Clear locationbar
242 Q_EMIT clearCompletion();
243
244 bool ok;
245 const int tabPos = index.data(LocationCompleterModel::TabPositionTabRole).toInt(&ok);
246
247 // Switch to tab with simple index activation
248 if (ok && tabPos > -1) {
249 BrowserWindow* window = static_cast<BrowserWindow*>(index.data(LocationCompleterModel::TabPositionWindowRole).value<void*>());
250 Q_ASSERT(window);
251 switchToTab(window, tabPos);
252 return;
253 }
254
255 loadRequest(createLoadRequest(index));
256}
257
258void LocationCompleter::indexCtrlActivated(const QModelIndex &index)
259{
260 Q_ASSERT(index.isValid());
261 Q_ASSERT(m_window);
262
263 closePopup();
264
265 // Clear locationbar
266 Q_EMIT clearCompletion();
267
268 // Load request in new tab
269 m_window->tabWidget()->addView(createLoadRequest(index), Qz::NT_CleanSelectedTab);
270}
271
272void LocationCompleter::indexShiftActivated(const QModelIndex &index)
273{
274 Q_ASSERT(index.isValid());
275
276 closePopup();
277
278 // Clear locationbar
279 Q_EMIT clearCompletion();
280
281 // Load request
282 if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) {
283 loadRequest(LoadRequest(index.data(LocationCompleterModel::SearchStringRole).toUrl()));
284 } else {
285 loadRequest(createLoadRequest(index));
286 }
287}
288
289void LocationCompleter::indexDeleteRequested(const QModelIndex &index)
290{
291 if (!index.isValid()) {
292 return;
293 }
294
295 if (index.data(LocationCompleterModel::BookmarkRole).toBool()) {
296 BookmarkItem* bookmark = static_cast<BookmarkItem*>(index.data(LocationCompleterModel::BookmarkItemRole).value<void*>());
297 mApp->bookmarks()->removeBookmark(bookmark);
298 } else if (index.data(LocationCompleterModel::HistoryRole).toBool()) {
299 int id = index.data(LocationCompleterModel::IdRole).toInt();
300 mApp->history()->deleteHistoryEntry(id);
301 } else {
302 return;
303 }
304
305 s_view->setUpdatesEnabled(false);
306 s_model->removeRow(index.row(), index.parent());
307 s_view->setUpdatesEnabled(true);
308
309 showPopup();
310}
311
312LoadRequest LocationCompleter::createLoadRequest(const QModelIndex &index)
313{
314 LoadRequest request;
315 BookmarkItem *bookmark = nullptr;
316
317 if (index.data(LocationCompleterModel::HistoryRole).toBool()) {
318 request = index.data(LocationCompleterModel::UrlRole).toUrl();
319 } else if (index.data(LocationCompleterModel::BookmarkRole).toBool()) {
320 bookmark = static_cast<BookmarkItem*>(index.data(LocationCompleterModel::BookmarkItemRole).value<void*>());
321 } else if (index.data(LocationCompleterModel::SearchSuggestionRole).toBool()) {
322 const QString text = index.data(LocationCompleterModel::TitleRole).toString();
323 request = mApp->searchEnginesManager()->searchResult(LocationBar::searchEngine(), text);
324 } else if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) {
325 const auto action = LocationBar::loadAction(index.data(LocationCompleterModel::SearchStringRole).toString());
326 switch (action.type) {
329 request = action.loadRequest;
330 break;
332 bookmark = action.bookmark;
333 break;
334 default:
335 break;
336 }
337 }
338
339 if (bookmark) {
340 bookmark->updateVisitCount();
341 request = bookmark->url();
342 }
343
344 return request;
345}
346
347void LocationCompleter::switchToTab(BrowserWindow* window, int tab)
348{
349 Q_ASSERT(window);
350 Q_ASSERT(tab >= 0);
351
352 TabWidget* tabWidget = window->tabWidget();
353
354 if (window->isActiveWindow() || tabWidget->currentIndex() != tab) {
355 tabWidget->setCurrentIndex(tab);
356 window->show();
357 window->activateWindow();
358 window->raise();
359 }
360 else {
361 tabWidget->webTab()->setFocus();
362 }
363}
364
365void LocationCompleter::loadRequest(const LoadRequest &request)
366{
367 closePopup();
368
369 // Show url in locationbar
370 Q_EMIT showCompletion(request.url().toString(), false);
371
372 // Load request
373 Q_EMIT loadRequested(request);
374}
375
376void LocationCompleter::openSearchEnginesDialog()
377{
378 // Clear locationbar
379 Q_EMIT clearCompletion();
380
381 auto *dialog = new SearchEnginesDialog(m_window);
382 dialog->open();
383}
384
385void LocationCompleter::showPopup()
386{
387 Q_ASSERT(m_window);
388 Q_ASSERT(m_locationBar);
389
390 if (!m_locationBar->hasFocus() || s_model->rowCount() == 0) {
391 s_view->close();
392 return;
393 }
394
395 if (s_view->isVisible()) {
396 adjustPopupSize();
397 return;
398 }
399
400 QRect popupRect(m_locationBar->mapToGlobal(m_locationBar->pos()), m_locationBar->size());
401 popupRect.setY(popupRect.bottom());
402
403 if (qzSettings->completionPopupExpandToWindow) {
404 popupRect.setX(m_window->mapToGlobal(QPoint(0,0)).x());
405 popupRect.setWidth(m_window->size().width());
406 }
407
408 s_view->setGeometry(popupRect);
409 s_view->setFocusProxy(m_locationBar);
410 s_view->setCurrentIndex(QModelIndex());
411
412 connect(s_view, &LocationCompleterView::closed, this, &LocationCompleter::slotPopupClosed);
413 connect(s_view, &LocationCompleterView::indexActivated, this, &LocationCompleter::indexActivated);
414 connect(s_view, &LocationCompleterView::indexCtrlActivated, this, &LocationCompleter::indexCtrlActivated);
415 connect(s_view, &LocationCompleterView::indexShiftActivated, this, &LocationCompleter::indexShiftActivated);
416 connect(s_view, &LocationCompleterView::indexDeleteRequested, this, &LocationCompleter::indexDeleteRequested);
418 connect(s_view, &LocationCompleterView::searchEnginesDialogRequested, this, &LocationCompleter::openSearchEnginesDialog);
419 connect(s_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &LocationCompleter::currentChanged);
420
421 s_view->createWinId();
422 s_view->windowHandle()->setTransientParent(m_window->windowHandle());
423
424 adjustPopupSize();
425}
426
427void LocationCompleter::adjustPopupSize()
428{
429 s_view->adjustSize();
430 s_view->show();
431}
QUrl url() const
void updateVisitCount()
TabWidget * tabWidget() const
QUrl url() const
Definition: loadrequest.cpp:44
QByteArray data() const
Definition: loadrequest.cpp:69
static SearchEngine searchEngine()
static LoadAction loadAction(const QString &text)
void setLocationBar(LocationBar *locationBar)
LocationCompleter(QObject *parent=nullptr)
void setMainWindow(BrowserWindow *window)
void showCompletion(const QString &completion, bool completeDomain)
void loadRequested(const LoadRequest &request)
void complete(const QString &string)
void showDomainCompletion(const QString &completion)
void setCompletions(const QList< QStandardItem * > &items)
void addCompletions(const QList< QStandardItem * > &items)
QList< QStandardItem * > suggestionItems() const
void setModel(QAbstractItemModel *model)
void indexCtrlActivated(const QModelIndex &index)
void indexShiftActivated(const QModelIndex &index)
void indexDeleteRequested(const QModelIndex &index)
QItemSelectionModel * selectionModel() const
void searchEnginesDialogRequested()
void indexActivated(const QModelIndex &index)
void setCurrentIndex(const QModelIndex &index)
void loadRequested(const LoadRequest &request)
QModelIndex currentIndex() const
A class representing a single search engine described in OpenSearch format.
void requestSuggestions(const QString &searchTerm)
void setNetworkAccessManager(QNetworkAccessManager *networkAccessManager)
void setSuggestionsParameters(const Parameters &suggestionsParameters)
void suggestions(const QStringList &suggestions)
void setSuggestionsUrl(const QString &string)
int currentIndex() const
void setCurrentIndex(int index)
Definition: tabwidget.cpp:536
WebTab * webTab(int index=-1) const
Definition: tabwidget.cpp:570
int addView(const LoadRequest &req, const Qz::NewTabPositionFlags &openFlags, bool selectLine=false, bool pinned=false)
Definition: tabwidget.cpp:314
#define mApp
@ NT_CleanSelectedTab
Definition: qzcommon.h:107
#define qzSettings
Definition: qzsettings.h:69