File indexing completed on 2024-12-22 04:41:07
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 "locationcompleter.h" 0019 #include "locationcompletermodel.h" 0020 #include "locationcompleterview.h" 0021 #include "locationcompleterrefreshjob.h" 0022 #include "locationbar.h" 0023 #include "mainapplication.h" 0024 #include "browserwindow.h" 0025 #include "tabbedwebview.h" 0026 #include "tabwidget.h" 0027 #include "history.h" 0028 #include "bookmarks.h" 0029 #include "bookmarkitem.h" 0030 #include "qzsettings.h" 0031 #include "opensearchengine.h" 0032 #include "networkmanager.h" 0033 #include "searchenginesdialog.h" 0034 0035 #include <QWindow> 0036 0037 LocationCompleterView* LocationCompleter::s_view = nullptr; 0038 LocationCompleterModel* LocationCompleter::s_model = nullptr; 0039 0040 LocationCompleter::LocationCompleter(QObject* parent) 0041 : QObject(parent) 0042 , m_window(nullptr) 0043 , m_locationBar(nullptr) 0044 , m_lastRefreshTimestamp(0) 0045 , m_popupClosed(false) 0046 { 0047 if (!s_view) { 0048 s_model = new LocationCompleterModel; 0049 s_view = new LocationCompleterView; 0050 s_view->setModel(s_model); 0051 } 0052 } 0053 0054 void LocationCompleter::setMainWindow(BrowserWindow* window) 0055 { 0056 m_window = window; 0057 } 0058 0059 void LocationCompleter::setLocationBar(LocationBar* locationBar) 0060 { 0061 m_locationBar = locationBar; 0062 } 0063 0064 bool LocationCompleter::isVisible() const 0065 { 0066 return s_view->isVisible(); 0067 } 0068 0069 void LocationCompleter::closePopup() 0070 { 0071 s_view->close(); 0072 } 0073 0074 void LocationCompleter::complete(const QString &string) 0075 { 0076 QString trimmedStr = string.trimmed(); 0077 0078 // Indicates that new completion was requested by user 0079 // Eg. popup was not closed yet this completion session 0080 m_popupClosed = false; 0081 0082 Q_EMIT cancelRefreshJob(); 0083 0084 auto* job = new LocationCompleterRefreshJob(trimmedStr); 0085 connect(job, &LocationCompleterRefreshJob::finished, this, &LocationCompleter::refreshJobFinished); 0086 connect(this, SIGNAL(cancelRefreshJob()), job, SLOT(jobCancelled())); 0087 0088 if (qzSettings->searchFromAddressBar && qzSettings->showABSearchSuggestions && trimmedStr.length() >= 2) { 0089 if (!m_openSearchEngine) { 0090 m_openSearchEngine = new OpenSearchEngine(this); 0091 m_openSearchEngine->setNetworkAccessManager(mApp->networkManager()); 0092 connect(m_openSearchEngine, &OpenSearchEngine::suggestions, this, &LocationCompleter::addSuggestions); 0093 } 0094 m_openSearchEngine->setSuggestionsUrl(LocationBar::searchEngine().suggestionsUrl); 0095 m_openSearchEngine->setSuggestionsParameters(LocationBar::searchEngine().suggestionsParameters); 0096 m_suggestionsTerm = trimmedStr; 0097 m_openSearchEngine->requestSuggestions(m_suggestionsTerm); 0098 } else { 0099 m_oldSuggestions.clear(); 0100 } 0101 0102 // Add/update search/visit item 0103 QTimer::singleShot(0, this, [=]() { 0104 const QModelIndex index = s_model->index(0, 0); 0105 if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) { 0106 s_model->setData(index, trimmedStr, Qt::DisplayRole); 0107 s_model->setData(index, trimmedStr, LocationCompleterModel::UrlRole); 0108 s_model->setData(index, m_locationBar->text(), LocationCompleterModel::SearchStringRole); 0109 } else { 0110 auto *item = new QStandardItem(); 0111 item->setText(trimmedStr); 0112 item->setData(trimmedStr, LocationCompleterModel::UrlRole); 0113 item->setData(m_locationBar->text(), LocationCompleterModel::SearchStringRole); 0114 item->setData(true, LocationCompleterModel::VisitSearchItemRole); 0115 s_model->setCompletions({item}); 0116 addSuggestions(m_oldSuggestions); 0117 } 0118 showPopup(); 0119 if (!s_view->currentIndex().isValid()) { 0120 m_ignoreCurrentChanged = true; 0121 s_view->setCurrentIndex(s_model->index(0, 0)); 0122 m_ignoreCurrentChanged = false; 0123 } 0124 }); 0125 } 0126 0127 void LocationCompleter::showMostVisited() 0128 { 0129 m_locationBar->setFocus(); 0130 complete(QString()); 0131 } 0132 0133 void LocationCompleter::refreshJobFinished() 0134 { 0135 auto* job = qobject_cast<LocationCompleterRefreshJob*>(sender()); 0136 Q_ASSERT(job); 0137 0138 // Don't show results of older jobs 0139 // Also don't open the popup again when it was already closed 0140 if (!job->isCanceled() && job->timestamp() > m_lastRefreshTimestamp && !m_popupClosed) { 0141 s_model->setCompletions(job->completions()); 0142 addSuggestions(m_oldSuggestions); 0143 showPopup(); 0144 0145 m_lastRefreshTimestamp = job->timestamp(); 0146 0147 if (!s_view->currentIndex().isValid() && s_model->index(0, 0).data(LocationCompleterModel::VisitSearchItemRole).toBool()) { 0148 m_ignoreCurrentChanged = true; 0149 s_view->setCurrentIndex(s_model->index(0, 0)); 0150 m_ignoreCurrentChanged = false; 0151 } 0152 0153 if (qzSettings->useInlineCompletion) { 0154 Q_EMIT showDomainCompletion(job->domainCompletion()); 0155 } 0156 0157 s_model->setData(s_model->index(0, 0), m_locationBar->text(), LocationCompleterModel::SearchStringRole); 0158 } 0159 0160 job->deleteLater(); 0161 } 0162 0163 void LocationCompleter::slotPopupClosed() 0164 { 0165 m_popupClosed = true; 0166 m_oldSuggestions.clear(); 0167 0168 disconnect(s_view, &LocationCompleterView::closed, this, &LocationCompleter::slotPopupClosed); 0169 disconnect(s_view, &LocationCompleterView::indexActivated, this, &LocationCompleter::indexActivated); 0170 disconnect(s_view, &LocationCompleterView::indexCtrlActivated, this, &LocationCompleter::indexCtrlActivated); 0171 disconnect(s_view, &LocationCompleterView::indexShiftActivated, this, &LocationCompleter::indexShiftActivated); 0172 disconnect(s_view, &LocationCompleterView::indexDeleteRequested, this, &LocationCompleter::indexDeleteRequested); 0173 disconnect(s_view, &LocationCompleterView::loadRequested, this, &LocationCompleter::loadRequested); 0174 disconnect(s_view, &LocationCompleterView::searchEnginesDialogRequested, this, &LocationCompleter::openSearchEnginesDialog); 0175 disconnect(s_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &LocationCompleter::currentChanged); 0176 0177 Q_EMIT popupClosed(); 0178 } 0179 0180 void LocationCompleter::addSuggestions(const QStringList &suggestions) 0181 { 0182 const auto suggestionItems = s_model->suggestionItems(); 0183 0184 // Delete existing suggestions 0185 for (QStandardItem *item : suggestionItems) { 0186 s_model->takeRow(item->row()); 0187 delete item; 0188 } 0189 0190 // Add new suggestions 0191 QList<QStandardItem*> items; 0192 for (const QString &suggestion : suggestions) { 0193 auto* item = new QStandardItem(); 0194 item->setText(suggestion); 0195 item->setData(suggestion, LocationCompleterModel::TitleRole); 0196 item->setData(suggestion, LocationCompleterModel::UrlRole); 0197 item->setData(m_suggestionsTerm, LocationCompleterModel::SearchStringRole); 0198 item->setData(true, LocationCompleterModel::SearchSuggestionRole); 0199 items.append(item); 0200 } 0201 0202 s_model->addCompletions(items); 0203 m_oldSuggestions = suggestions; 0204 0205 if (!m_popupClosed) { 0206 showPopup(); 0207 } 0208 } 0209 0210 void LocationCompleter::currentChanged(const QModelIndex &index) 0211 { 0212 if (m_ignoreCurrentChanged) { 0213 return; 0214 } 0215 0216 QString completion = index.data().toString(); 0217 0218 bool completeDomain = index.data(LocationCompleterModel::VisitSearchItemRole).toBool(); 0219 0220 const QString originalText = s_model->index(0, 0).data(LocationCompleterModel::SearchStringRole).toString(); 0221 0222 // Domain completion was dismissed 0223 if (completeDomain && completion == originalText) { 0224 completeDomain = false; 0225 } 0226 0227 if (completion.isEmpty()) { 0228 completeDomain = true; 0229 completion = originalText; 0230 } 0231 0232 Q_EMIT showCompletion(completion, completeDomain); 0233 } 0234 0235 void LocationCompleter::indexActivated(const QModelIndex &index) 0236 { 0237 Q_ASSERT(index.isValid()); 0238 0239 closePopup(); 0240 0241 // Clear locationbar 0242 Q_EMIT clearCompletion(); 0243 0244 bool ok; 0245 const int tabPos = index.data(LocationCompleterModel::TabPositionTabRole).toInt(&ok); 0246 0247 // Switch to tab with simple index activation 0248 if (ok && tabPos > -1) { 0249 BrowserWindow* window = static_cast<BrowserWindow*>(index.data(LocationCompleterModel::TabPositionWindowRole).value<void*>()); 0250 Q_ASSERT(window); 0251 switchToTab(window, tabPos); 0252 return; 0253 } 0254 0255 loadRequest(createLoadRequest(index)); 0256 } 0257 0258 void LocationCompleter::indexCtrlActivated(const QModelIndex &index) 0259 { 0260 Q_ASSERT(index.isValid()); 0261 Q_ASSERT(m_window); 0262 0263 closePopup(); 0264 0265 // Clear locationbar 0266 Q_EMIT clearCompletion(); 0267 0268 // Load request in new tab 0269 m_window->tabWidget()->addView(createLoadRequest(index), Qz::NT_CleanSelectedTab); 0270 } 0271 0272 void LocationCompleter::indexShiftActivated(const QModelIndex &index) 0273 { 0274 Q_ASSERT(index.isValid()); 0275 0276 closePopup(); 0277 0278 // Clear locationbar 0279 Q_EMIT clearCompletion(); 0280 0281 // Load request 0282 if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) { 0283 loadRequest(LoadRequest(index.data(LocationCompleterModel::SearchStringRole).toUrl())); 0284 } else { 0285 loadRequest(createLoadRequest(index)); 0286 } 0287 } 0288 0289 void LocationCompleter::indexDeleteRequested(const QModelIndex &index) 0290 { 0291 if (!index.isValid()) { 0292 return; 0293 } 0294 0295 if (index.data(LocationCompleterModel::BookmarkRole).toBool()) { 0296 BookmarkItem* bookmark = static_cast<BookmarkItem*>(index.data(LocationCompleterModel::BookmarkItemRole).value<void*>()); 0297 mApp->bookmarks()->removeBookmark(bookmark); 0298 } else if (index.data(LocationCompleterModel::HistoryRole).toBool()) { 0299 int id = index.data(LocationCompleterModel::IdRole).toInt(); 0300 mApp->history()->deleteHistoryEntry(id); 0301 } else { 0302 return; 0303 } 0304 0305 s_view->setUpdatesEnabled(false); 0306 s_model->removeRow(index.row(), index.parent()); 0307 s_view->setUpdatesEnabled(true); 0308 0309 showPopup(); 0310 } 0311 0312 LoadRequest LocationCompleter::createLoadRequest(const QModelIndex &index) 0313 { 0314 LoadRequest request; 0315 BookmarkItem *bookmark = nullptr; 0316 0317 if (index.data(LocationCompleterModel::HistoryRole).toBool()) { 0318 request = index.data(LocationCompleterModel::UrlRole).toUrl(); 0319 } else if (index.data(LocationCompleterModel::BookmarkRole).toBool()) { 0320 bookmark = static_cast<BookmarkItem*>(index.data(LocationCompleterModel::BookmarkItemRole).value<void*>()); 0321 } else if (index.data(LocationCompleterModel::SearchSuggestionRole).toBool()) { 0322 const QString text = index.data(LocationCompleterModel::TitleRole).toString(); 0323 request = mApp->searchEnginesManager()->searchResult(LocationBar::searchEngine(), text); 0324 } else if (index.data(LocationCompleterModel::VisitSearchItemRole).toBool()) { 0325 const auto action = LocationBar::loadAction(index.data(LocationCompleterModel::SearchStringRole).toString()); 0326 switch (action.type) { 0327 case LocationBar::LoadAction::Url: 0328 case LocationBar::LoadAction::Search: 0329 request = action.loadRequest; 0330 break; 0331 case LocationBar::LoadAction::Bookmark: 0332 bookmark = action.bookmark; 0333 break; 0334 default: 0335 break; 0336 } 0337 } 0338 0339 if (bookmark) { 0340 bookmark->updateVisitCount(); 0341 request = bookmark->url(); 0342 } 0343 0344 return request; 0345 } 0346 0347 void LocationCompleter::switchToTab(BrowserWindow* window, int tab) 0348 { 0349 Q_ASSERT(window); 0350 Q_ASSERT(tab >= 0); 0351 0352 TabWidget* tabWidget = window->tabWidget(); 0353 0354 if (window->isActiveWindow() || tabWidget->currentIndex() != tab) { 0355 tabWidget->setCurrentIndex(tab); 0356 window->show(); 0357 window->activateWindow(); 0358 window->raise(); 0359 } 0360 else { 0361 tabWidget->webTab()->setFocus(); 0362 } 0363 } 0364 0365 void LocationCompleter::loadRequest(const LoadRequest &request) 0366 { 0367 closePopup(); 0368 0369 // Show url in locationbar 0370 Q_EMIT showCompletion(request.url().toString(), false); 0371 0372 // Load request 0373 Q_EMIT loadRequested(request); 0374 } 0375 0376 void LocationCompleter::openSearchEnginesDialog() 0377 { 0378 // Clear locationbar 0379 Q_EMIT clearCompletion(); 0380 0381 auto *dialog = new SearchEnginesDialog(m_window); 0382 dialog->open(); 0383 } 0384 0385 void LocationCompleter::showPopup() 0386 { 0387 Q_ASSERT(m_window); 0388 Q_ASSERT(m_locationBar); 0389 0390 if (!m_locationBar->hasFocus() || s_model->rowCount() == 0) { 0391 s_view->close(); 0392 return; 0393 } 0394 0395 if (s_view->isVisible()) { 0396 adjustPopupSize(); 0397 return; 0398 } 0399 0400 QRect popupRect(m_locationBar->mapToGlobal(m_locationBar->pos()), m_locationBar->size()); 0401 popupRect.setY(popupRect.bottom()); 0402 0403 if (qzSettings->completionPopupExpandToWindow) { 0404 popupRect.setX(m_window->mapToGlobal(QPoint(0,0)).x()); 0405 popupRect.setWidth(m_window->size().width()); 0406 } 0407 0408 s_view->setGeometry(popupRect); 0409 s_view->setFocusProxy(m_locationBar); 0410 s_view->setCurrentIndex(QModelIndex()); 0411 0412 connect(s_view, &LocationCompleterView::closed, this, &LocationCompleter::slotPopupClosed); 0413 connect(s_view, &LocationCompleterView::indexActivated, this, &LocationCompleter::indexActivated); 0414 connect(s_view, &LocationCompleterView::indexCtrlActivated, this, &LocationCompleter::indexCtrlActivated); 0415 connect(s_view, &LocationCompleterView::indexShiftActivated, this, &LocationCompleter::indexShiftActivated); 0416 connect(s_view, &LocationCompleterView::indexDeleteRequested, this, &LocationCompleter::indexDeleteRequested); 0417 connect(s_view, &LocationCompleterView::loadRequested, this, &LocationCompleter::loadRequested); 0418 connect(s_view, &LocationCompleterView::searchEnginesDialogRequested, this, &LocationCompleter::openSearchEnginesDialog); 0419 connect(s_view->selectionModel(), &QItemSelectionModel::currentChanged, this, &LocationCompleter::currentChanged); 0420 0421 s_view->createWinId(); 0422 s_view->windowHandle()->setTransientParent(m_window->windowHandle()); 0423 0424 adjustPopupSize(); 0425 } 0426 0427 void LocationCompleter::adjustPopupSize() 0428 { 0429 s_view->adjustSize(); 0430 s_view->show(); 0431 }