diff options
author | aqua <aqua@iserlohn-fortress.net> | 2022-09-04 14:46:52 +0300 |
---|---|---|
committer | aqua <aqua@iserlohn-fortress.net> | 2022-09-05 23:07:42 +0300 |
commit | e0d4d8376a2481f2f5daeaec1c9967e62b0bb8b0 (patch) | |
tree | 346d6e12d32c3997e527625c74513afc18dbc113 /src/urlbar | |
parent | Close main window after last tab is closed (diff) | |
download | rekonq-e0d4d8376a2481f2f5daeaec1c9967e62b0bb8b0.tar.xz |
UrlBar: add completer
Diffstat (limited to 'src/urlbar')
-rw-r--r-- | src/urlbar/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/urlbar/completer.cpp | 132 | ||||
-rw-r--r-- | src/urlbar/completer.hpp | 33 | ||||
-rw-r--r-- | src/urlbar/urlbar.cpp | 12 | ||||
-rw-r--r-- | src/urlbar/urlbar.hpp (renamed from src/urlbar/urlbar.h) | 0 | ||||
-rw-r--r-- | src/urlbar/urlsuggester.cpp | 386 | ||||
-rw-r--r-- | src/urlbar/urlsuggester.h | 135 |
7 files changed, 172 insertions, 530 deletions
diff --git a/src/urlbar/CMakeLists.txt b/src/urlbar/CMakeLists.txt index 6a68ce58..7827dfb2 100644 --- a/src/urlbar/CMakeLists.txt +++ b/src/urlbar/CMakeLists.txt @@ -1,2 +1,2 @@ -add_library(urlbar STATIC urlbar.cpp urlbar.h) -target_link_libraries(urlbar PUBLIC Qt6::Widgets)
\ No newline at end of file +add_library(urlbar STATIC urlbar.cpp urlbar.hpp completer.cpp completer.hpp) +target_link_libraries(urlbar PUBLIC Qt6::Widgets) diff --git a/src/urlbar/completer.cpp b/src/urlbar/completer.cpp new file mode 100644 index 00000000..df864e05 --- /dev/null +++ b/src/urlbar/completer.cpp @@ -0,0 +1,132 @@ +/* ============================================================ + * The rekonq project + * ============================================================ + * SPDX-License-Identifier: BSD-3-Clause + * Copyright (C) 2016 The Qt Company Ltd. + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net> + * ============================================================ */ + +#include "completer.hpp" +#include "urlbar.hpp" +#include <QHeaderView> +#include <QKeyEvent> +#include <QLineEdit> +#include <QTreeWidget> + +static const QString searchUrl{"https://duckduckgo.com/?q=%1"}; + +// ------------------------------------------------------------------------ +// Ctor and Dtor + +UrlSuggester::UrlSuggester(UrlBar *parent) : QObject(parent), m_editor(parent), m_popup(new QTreeWidget) +{ + m_popup->setWindowFlags(Qt::Popup); + m_popup->setFocusPolicy(Qt::NoFocus); + m_popup->setFocusProxy(parent); + m_popup->setMouseTracking(true); + + m_popup->setColumnCount(2); + m_popup->setUniformRowHeights(true); + m_popup->setRootIsDecorated(false); + m_popup->setEditTriggers(QTreeWidget::NoEditTriggers); + m_popup->setSelectionBehavior(QTreeWidget::SelectRows); + m_popup->setFrameStyle(QFrame::Box | QFrame::Plain); + m_popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_popup->header()->hide(); + + m_popup->installEventFilter(this); +} + +UrlSuggester::~UrlSuggester() { delete m_popup; } + +bool UrlSuggester::eventFilter(QObject *obj, QEvent *ev) +{ + if (obj != m_popup) return false; + + if (ev->type() == QEvent::MouseButtonPress) { + m_popup->hide(); + m_editor->setFocus(); + return true; + } + + if (ev->type() == QEvent::KeyPress) { + bool consumed = false; + int key = static_cast<QKeyEvent *>(ev)->key(); + switch (key) { + case Qt::Key_Enter: + case Qt::Key_Return: + doneCompletion(); + consumed = true; + break; + + case Qt::Key_Escape: + m_editor->setFocus(); + m_popup->hide(); + consumed = true; + break; + + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + break; + + default: + m_editor->setFocus(); + m_editor->event(ev); + // m_popup->hide(); + break; + } + + return consumed; + } + + return false; +} + +// ------------------------------------------------------------------------ +// slots + +void UrlSuggester::showCompletions(const QString &text) +{ + if (text.isEmpty()) return; + + m_popup->setUpdatesEnabled(false); + m_popup->clear(); + + { // go item + auto *item = new QTreeWidgetItem(m_popup); + item->setIcon(0, QIcon::fromTheme("go-jump")); + item->setText(0, tr("Go to %1").arg(text)); + item->setText(1, QUrl::fromUserInput(text).toString()); + } + { // search item + auto *item = new QTreeWidgetItem(m_popup); + item->setIcon(0, QIcon::fromTheme("search")); + item->setText(0, tr("Search for %1").arg(text)); + item->setText(1, searchUrl.arg(QUrl::toPercentEncoding(text))); + } + + m_popup->setCurrentItem(m_popup->topLevelItem(0)); + m_popup->resizeColumnToContents(0); + m_popup->setUpdatesEnabled(true); + + m_popup->move(m_editor->mapToGlobal(QPoint(0, m_editor->height()))); + m_popup->resize(m_editor->width(), 250); + m_popup->setFocus(); + m_popup->show(); +} + +void UrlSuggester::doneCompletion() +{ + m_popup->hide(); + m_editor->setFocus(); + auto *item = m_popup->currentItem(); + if (item) { + m_editor->setText(item->text(1)); + emit m_editor->returnPressed(); + } +} diff --git a/src/urlbar/completer.hpp b/src/urlbar/completer.hpp new file mode 100644 index 00000000..11c0f62b --- /dev/null +++ b/src/urlbar/completer.hpp @@ -0,0 +1,33 @@ +/* ============================================================ + * The rekonq project + * ============================================================ + * SPDX-License-Identifier: BSD-3-Clause + * Copyright (C) 2016 The Qt Company Ltd. + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net> + * ============================================================ */ + +#pragma once + +#include <QObject> + +class UrlBar; +class QLineEdit; +class QTreeWidget; +class UrlSuggester : public QObject { + Q_OBJECT + +public: + explicit UrlSuggester(UrlBar *parent = nullptr); + ~UrlSuggester() override; + + bool eventFilter(QObject *obj, QEvent *ev) override; + +public slots: + void showCompletions(const QString &text); + void doneCompletion(); + +private: + QLineEdit *m_editor; + QTreeWidget *m_popup; +}; diff --git a/src/urlbar/urlbar.cpp b/src/urlbar/urlbar.cpp index d9059833..f3a34a36 100644 --- a/src/urlbar/urlbar.cpp +++ b/src/urlbar/urlbar.cpp @@ -10,7 +10,8 @@ * Copyright (C) 2022 aqua <aqua@iserlohn-fortress.net> * ============================================================ */ -#include "urlbar.h" +#include "urlbar.hpp" +#include "completer.hpp" #include <QApplication> #include <QClipboard> #include <QPaintEvent> @@ -50,9 +51,7 @@ UrlBar::UrlBar(QWidget *parent) : QLineEdit(parent), _icon(new IconButton(this)), backgroundColor{qApp->palette().color(QPalette::Base)}, highlightColor{qApp->palette().color(QPalette::Highlight)} { - // set initial icon - //_icon->setIcon(QIcon("arrow-right")); _icon->setIcon(QIcon::fromTheme("arrow-right")); _icon->show(); @@ -75,10 +74,9 @@ UrlBar::UrlBar(QWidget *parent) // accept focus, via tabbing, clicking & wheeling setFocusPolicy(Qt::WheelFocus); - // TODO: disable completion object (we have our own :) ) - // setCompletionObject(0); - - //_tab = qobject_cast<WebTab *>(parent); + // set completer + auto *completer = new UrlSuggester(this); + connect(this, &QLineEdit::textEdited, completer, &UrlSuggester::showCompletions); // connect(_tab, SIGNAL(loadProgressing()), this, SLOT(update())); diff --git a/src/urlbar/urlbar.h b/src/urlbar/urlbar.hpp index da34151e..da34151e 100644 --- a/src/urlbar/urlbar.h +++ b/src/urlbar/urlbar.hpp diff --git a/src/urlbar/urlsuggester.cpp b/src/urlbar/urlsuggester.cpp deleted file mode 100644 index aed50c22..00000000 --- a/src/urlbar/urlsuggester.cpp +++ /dev/null @@ -1,386 +0,0 @@ -/* ============================================================ -* -* This file is a part of the rekonq project -* -* Copyright (C) 2009-2012 by Andrea Diamantini <adjam7 at gmail dot com> -* -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License as -* published by the Free Software Foundation; either version 2 of -* the License or (at your option) version 3 or any later version -* accepted by the membership of KDE e.V. (or its successor approved -* by the membership of KDE e.V.), which shall act as a proxy -* defined in Section 14 of version 3 of the license. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* ============================================================ */ - - -// Self Includes -#include "urlsuggester.h" -#include "urlsuggester.moc" - -// Local Includes -#include "historymanager.h" -#include "bookmarkmanager.h" - -#include "searchengine.h" - -// KDE Includes -#include <KBookmark> -#include <KService> -#include <KProtocolInfo> - -// Qt Includes -#include <QByteArray> - - -// NOTE -// default kurifilter plugin list (at least in my box): -// 1. "kshorturifilter" -// 2. "kurisearchfilter" -// 3. "localdomainurifilter" -// 4 ."kuriikwsfilter" -// 5. "fixhosturifilter" - - -// ------------------------------------------------------------------------ - - -// NOTE -// Induce an order in the history items -bool isHistoryItemRelevant(const HistoryItem &a, const HistoryItem &b) -{ - return a.relevance() > b.relevance(); -} - - -// ------------------------------------------------------------------------ - - -QRegExp UrlSuggester::_browseRegexp; -QRegExp UrlSuggester::_searchEnginesRegexp; - - -UrlSuggester::UrlSuggester(const QString &typedUrl) - : QObject() - , _typedString(typedUrl.trimmed()) - , _isKDEShortUrl(false) -{ - if (_browseRegexp.isEmpty()) - { - QString protocol = QString("^(%1)").arg(KProtocolInfo::protocols().join("|")); - protocol += QL1S("|javascript"); - - QString localhost = QL1S("^localhost"); - - QString local = QL1S("^/"); - - QString ipv4 = QL1S("^0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])"\ - "\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])\\.0*([1-9]?\\d|1\\d\\d|2[0-4]\\d|25[0-5])"); - - QString ipv6 = QL1S("^([0-9a-fA-F]{4}|0)(\\:([0-9a-fA-F]{4}|0)){7}"); - - QString address = QL1S("[\\d\\w-.]+\\.(a[cdefgilmnoqrstuwz]|b[abdefghijmnorstvwyz]|"\ - "c[acdfghiklmnoruvxyz]|d[ejkmnoz]|e[ceghrstu]|f[ijkmnor]|g[abdefghilmnpqrstuwy]|"\ - "h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|"\ - "m[acdghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eouw]|"\ - "s[abcdeghijklmnortuvyz]|t[cdfghjkmnoprtvwz]|u[augkmsyz]|v[aceginu]|w[fs]|"\ - "y[etu]|z[amw]|aero|arpa|biz|com|coop|edu|info|int|gov|local|mil|museum|name|net|org|"\ - "pro)"); - - QString joiner = QL1S(")|("); - _browseRegexp = QRegExp(QL1C('(') + - protocol + joiner + - localhost + joiner + - local + joiner + - address + joiner + - ipv6 + joiner + - ipv4 + QL1C(')') - ); - } - - if (_searchEnginesRegexp.isEmpty()) - { - QString reg; - QString engineUrl; - Q_FOREACH(KService::Ptr s, SearchEngine::favorites()) - { - engineUrl = QRegExp::escape(s->property("Query").toString()).replace(QL1S("\\\\\\{@\\}"), QL1S("[\\d\\w-.]+")); - if (reg.isEmpty()) - reg = QL1C('(') + engineUrl + QL1C(')'); - else - reg = reg + QL1S("|(") + engineUrl + QL1C(')'); - } - _searchEnginesRegexp = QRegExp(reg); - } -} - - -// UrlSuggestionList UrlSuggester::orderedSearchItems() -UrlSuggestionList UrlSuggester::computeSuggestions() -{ - if (_typedString.startsWith(QL1S("rekonq:"))) - { - QStringList aboutUrlList; - aboutUrlList - << QL1S("rekonq:home") - << QL1S("rekonq:favorites") - << QL1S("rekonq:bookmarks") - << QL1S("rekonq:history") - << QL1S("rekonq:downloads") - << QL1S("rekonq:closedtabs") - ; - - QStringList aboutUrlResults = aboutUrlList.filter(_typedString, Qt::CaseInsensitive); - - UrlSuggestionList list; - - Q_FOREACH(const QString & urlResult, aboutUrlResults) - { - QString name = urlResult; - name.remove(0, 6); - UrlSuggestionItem item(UrlSuggestionItem::Browse, urlResult, name); - list << item; - } - - return list; - } - - // NOTE: this sets _isKDEShortUrl. - // IF it is true we can just suggest it - computeWebSearches(); - - if (_isKDEShortUrl) - { - return _webSearches; - } - - //compute lists - computeHistory(); - computeQurlFromUserInput(); - computeBookmarks(); - - return orderLists(); -} - - -UrlSuggestionList UrlSuggester::orderLists() -{ - // NOTE - // The const int here decides the number of proper suggestions, taken from history & bookmarks - // You have to add here the "browse & search" options, always available. - const int availableEntries = 8; - - // Browse & Search results - UrlSuggestionList browseSearch; - QString lowerTypedString = _typedString.toLower(); - - bool textIsUrl = (_browseRegexp.indexIn(lowerTypedString) != -1); - - if (textIsUrl) - { - // browse url case (typed kde.org): show resolved url before - browseSearch << _qurlFromUserInput; - browseSearch << _webSearches; - } - else - { - // NON url case: propose web search before - browseSearch << _webSearches; - browseSearch << _qurlFromUserInput; - } - - - // find relevant items (the one you are more probably searching...) - UrlSuggestionList relevant; - - // history - Q_FOREACH(const UrlSuggestionItem & item, _history) - { - QString hst = KUrl(item.url).host(); - if (item.url.startsWith(_typedString) - || hst.startsWith(_typedString) - || hst.remove("www.").startsWith(_typedString)) - { - relevant << item; - _history.removeOne(item); - break; - } - } - - // just add this is relevant is Null - - if (relevant.count() == 0) - { - // bookmarks - Q_FOREACH(const UrlSuggestionItem & item, _bookmarks) - { - QString hst = KUrl(item.url).host(); - if (item.url.startsWith(_typedString) - || hst.startsWith(_typedString) - || hst.remove("www.").startsWith(_typedString)) - { - relevant << item; - _bookmarks.removeOne(item); - break; - } - } - } - - // decide history & bookmarks number - int historyCount = _history.count(); - int bookmarksCount = _bookmarks.count(); - int relevantCount = relevant.count(); - - const int historyEntries = (availableEntries - relevantCount) / 2; - const int bookmarksEntries = availableEntries - relevantCount - historyEntries; - - if (historyCount >= historyEntries && bookmarksCount >= bookmarksEntries) - { - _history = _history.mid(0, historyEntries); - _bookmarks = _bookmarks.mid(0, bookmarksEntries); - } - else if (historyCount < historyEntries && bookmarksCount >= bookmarksEntries) - { - if (historyCount + bookmarksCount > availableEntries) - { - _bookmarks = _bookmarks.mid(0, availableEntries - historyCount); - } - } - else if (historyCount >= historyEntries && bookmarksCount < bookmarksEntries) - { - if (historyCount + bookmarksCount > availableEntries) - { - _history = _history.mid(0, availableEntries - bookmarksCount); - } - } - - if (_typedString.count() > 1) - removeBookmarksDuplicates(); - - // and finally, results - UrlSuggestionList list; - if (textIsUrl) - list += browseSearch + relevant + _history + _bookmarks; - else - list += relevant + browseSearch + _history + _bookmarks; - - return list; -} - - -////////////////////////////////////////////////////////////////////////// -// PRIVATE ENGINES - - -// QUrl from User Input (easily the best solution... ) -void UrlSuggester::computeQurlFromUserInput() -{ - QString url = _typedString; - QUrl urlFromUserInput = QUrl::fromUserInput(url); - if (urlFromUserInput.isValid()) - { - // ensure http(s) hosts are lower cases - if (urlFromUserInput.scheme().startsWith(QL1S("http"))) - { - QString hst = urlFromUserInput.host(); - urlFromUserInput.setHost(hst.toLower()); - } - - QString urlString = urlFromUserInput.toString(); - QString gTitle = i18nc("Browse a website", "Browse"); - UrlSuggestionItem gItem(UrlSuggestionItem::Browse, urlString, gTitle); - _qurlFromUserInput << gItem; - } -} - - -// webSearches -void UrlSuggester::computeWebSearches() -{ - QString query = _typedString; - - // this result is generated when an user types something like gg:kde - KService::Ptr engine = SearchEngine::fromString(_typedString); - if (engine) - { - query = query.remove(0, _typedString.indexOf(SearchEngine::delimiter()) + 1); - _isKDEShortUrl = true; - } - else - { - engine = SearchEngine::defaultEngine(); - } - - if (engine) - { - UrlSuggestionItem item = UrlSuggestionItem(UrlSuggestionItem::Search, SearchEngine::buildQuery(engine, query), query, engine->name()); - UrlSuggestionList list; - list << item; - _webSearches = list; - } -} - - -// history -void UrlSuggester::computeHistory() -{ - QList<HistoryItem> found = HistoryManager::self()->find(_typedString); - - // FIXME: profiling computeHistory, this seems too much expensive (around 1 second for) - // doing it just from second time... - if (_typedString.count() > 1) - qSort(found.begin(), found.end(), isHistoryItemRelevant); - - Q_FOREACH(const HistoryItem & i, found) - { - if (_searchEnginesRegexp.isEmpty() || _searchEnginesRegexp.indexIn(i.url) == -1) //filter all urls that are search engine results - { - UrlSuggestionItem gItem(UrlSuggestionItem::History, i.url, i.title); - _history << gItem; - } - } -} - - -// bookmarks -void UrlSuggester::computeBookmarks() -{ - QList<KBookmark> found = BookmarkManager::self()->find(_typedString); - Q_FOREACH(const KBookmark & b, found) - { - UrlSuggestionItem gItem(UrlSuggestionItem::Bookmark, b.url().url(), b.fullText()); - _bookmarks << gItem; - } -} - - -////////////////////////////////////////////////////////////////////////// - - -// WARNING: this seems A LOT expensive and has to be done just -// when the two groups (history & bookmarks) have just been "restricted".. -void UrlSuggester::removeBookmarksDuplicates() -{ - Q_FOREACH(const UrlSuggestionItem & item, _history) - { - QString hu = item.url; - Q_FOREACH(const UrlSuggestionItem & item, _bookmarks) - { - if (hu == item.url) - { - _bookmarks.removeOne(item); - break; - } - } - } -} diff --git a/src/urlbar/urlsuggester.h b/src/urlbar/urlsuggester.h deleted file mode 100644 index 22bc4320..00000000 --- a/src/urlbar/urlsuggester.h +++ /dev/null @@ -1,135 +0,0 @@ -/* ============================================================ -* -* This file is a part of the rekonq project -* -* Copyright (C) 2009-2012 by Andrea Diamantini <adjam7 at gmail dot com> -* -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public License as -* published by the Free Software Foundation; either version 2 of -* the License or (at your option) version 3 or any later version -* accepted by the membership of KDE e.V. (or its successor approved -* by the membership of KDE e.V.), which shall act as a proxy -* defined in Section 14 of version 3 of the license. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see <http://www.gnu.org/licenses/>. -* -* ============================================================ */ - - -#ifndef URL_SUGGESTER_H -#define URL_SUGGESTER_H - - -// Rekonq Includes -#include "rekonq_defines.h" - -// KDE Includes -#include <KUrl> -#include <KService> - -// Qt Includes -#include <QString> -#include <QList> - - -class UrlSuggestionItem -{ -public: - - enum types - { - Undefined = 0x00000000, - Search = 0x00000001, - Browse = 0x00000010, - History = 0x00000100, - Bookmark = 0x00001000 - }; - - int type; - QString url; - QString title; - QString description; - QString bookmarkPath; - - UrlSuggestionItem(const UrlSuggestionItem &item) - : type(item.type) - , url(item.url) - , title(item.title) - , description(item.description) - {}; - - UrlSuggestionItem() - : type(UrlSuggestionItem::Undefined) - , url(QString()) - , title(QString()) - , description(QString()) - {}; - - UrlSuggestionItem(const int &_type, - const QString &_url, - const QString &_title = QString(), - const QString &_description = QString() - ) - : type(_type) - , url(_url) - , title(_title) - , description(_description) - {}; - - inline bool operator==(const UrlSuggestionItem &i) const - { - return i.url == url;//TODO && i.title == title; - } -}; - - -typedef QList <UrlSuggestionItem> UrlSuggestionList; - - -// ---------------------------------------------------------------------- - - -class UrlSuggester : public QObject -{ - Q_OBJECT - -public: - explicit UrlSuggester(const QString &typedUrl); - - UrlSuggestionList computeSuggestions(); - -private: - void computeWebSearches(); - void computeHistory(); - void computeQurlFromUserInput(); - void computeBookmarks(); - - void removeBookmarksDuplicates(); - - UrlSuggestionList orderLists(); - - QString _typedString; - - UrlSuggestionList _webSearches; - UrlSuggestionList _qurlFromUserInput; - UrlSuggestionList _history; - UrlSuggestionList _bookmarks; - UrlSuggestionList _suggestions; - - bool _isKDEShortUrl; - - static QRegExp _browseRegexp; - static QRegExp _searchEnginesRegexp; -}; - -// ------------------------------------------------------------------------------ - -#endif // URL_SUGGESTER_H |