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 }