1
mirror of https://invent.kde.org/network/falkon.git synced 2024-11-14 19:12:11 +01:00
falkonOfficial/src/lib/navigation/locationbar.cpp
David Rosca 16b3a74aa1 LocationCompleter: Add persistent first item that shows used search engine
Or in case searching from location bar is disabled or entered text
is valid URL, it indicates that this URL will be loaded.
2017-08-11 12:32:18 +02:00

670 lines
19 KiB
C++

/* ============================================================
* QupZilla - Qt web browser
* Copyright (C) 2010-2017 David Rosca <nowrep@gmail.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 3 of the License, or
* (at your option) any later version.
*
* 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/>.
* ============================================================ */
#include "locationbar.h"
#include "browserwindow.h"
#include "tabbedwebview.h"
#include "mainapplication.h"
#include "webpage.h"
#include "tabwidget.h"
#include "bookmarksicon.h"
#include "bookmarks.h"
#include "bookmarkitem.h"
#include "bookmarkstoolbar.h"
#include "siteicon.h"
#include "goicon.h"
#include "downicon.h"
#include "qztools.h"
#include "iconprovider.h"
#include "qzsettings.h"
#include "colors.h"
#include "autofillicon.h"
#include "searchenginesmanager.h"
#include "completer/locationcompleter.h"
#include <QTimer>
#include <QMimeData>
#include <QCompleter>
#include <QStringListModel>
#include <QContextMenuEvent>
#include <QStyleOptionFrameV3>
LocationBar::LocationBar(BrowserWindow* window)
: LineEdit(window)
, m_window(window)
, m_webView(0)
, m_holdingAlt(false)
, m_oldTextLength(0)
, m_currentTextLength(0)
, m_loadProgress(0)
, m_progressVisible(false)
{
setObjectName("locationbar");
setDragEnabled(true);
// Disable Oxygen QLineEdit transitions, it breaks with setText() && home()
setProperty("_kde_no_animations", QVariant(true));
m_bookmarkIcon = new BookmarksIcon(this);
m_goIcon = new GoIcon(this);
m_siteIcon = new SiteIcon(m_window, this);
m_autofillIcon = new AutoFillIcon(this);
DownIcon* down = new DownIcon(this);
addWidget(m_siteIcon, LineEdit::LeftSide);
addWidget(m_autofillIcon, LineEdit::RightSide);
addWidget(m_bookmarkIcon, LineEdit::RightSide);
addWidget(m_goIcon, LineEdit::RightSide);
addWidget(down, LineEdit::RightSide);
m_completer = new LocationCompleter(this);
m_completer->setMainWindow(m_window);
m_completer->setLocationBar(this);
connect(m_completer, SIGNAL(showCompletion(QString,bool)), this, SLOT(showCompletion(QString,bool)));
connect(m_completer, SIGNAL(showDomainCompletion(QString)), this, SLOT(showDomainCompletion(QString)));
connect(m_completer, SIGNAL(loadCompletion()), this, SLOT(requestLoadUrl()));
connect(m_completer, SIGNAL(clearCompletion()), this, SLOT(clearCompletion()));
m_domainCompleterModel = new QStringListModel(this);
QCompleter* domainCompleter = new QCompleter(this);
domainCompleter->setCompletionMode(QCompleter::InlineCompletion);
domainCompleter->setModel(m_domainCompleterModel);
setCompleter(domainCompleter);
m_progressTimer = new QTimer(this);
m_progressTimer->setInterval(700);
m_progressTimer->setSingleShot(true);
connect(m_progressTimer, &QTimer::timeout, this, &LocationBar::hideProgress);
editAction(PasteAndGo)->setText(tr("Paste And &Go"));
editAction(PasteAndGo)->setIcon(QIcon::fromTheme(QSL("edit-paste")));
connect(editAction(PasteAndGo), SIGNAL(triggered()), this, SLOT(pasteAndGo()));
connect(this, SIGNAL(textEdited(QString)), this, SLOT(textEdited(QString)));
connect(m_goIcon, SIGNAL(clicked(QPoint)), this, SLOT(requestLoadUrl()));
connect(down, SIGNAL(clicked(QPoint)), m_completer, SLOT(showMostVisited()));
connect(mApp->searchEnginesManager(), SIGNAL(activeEngineChanged()), this, SLOT(updatePlaceHolderText()));
connect(mApp->searchEnginesManager(), SIGNAL(defaultEngineChanged()), this, SLOT(updatePlaceHolderText()));
connect(mApp, SIGNAL(settingsReloaded()), SLOT(loadSettings()));
loadSettings();
updateSiteIcon();
// Hide icons by default
m_goIcon->setVisible(qzSettings->alwaysShowGoIcon);
m_autofillIcon->hide();
QTimer::singleShot(0, this, SLOT(updatePlaceHolderText()));
}
TabbedWebView* LocationBar::webView() const
{
return m_webView;
}
void LocationBar::setWebView(TabbedWebView* view)
{
m_webView = view;
m_bookmarkIcon->setWebView(m_webView);
m_siteIcon->setWebView(m_webView);
m_autofillIcon->setWebView(m_webView);
connect(m_webView, SIGNAL(loadStarted()), SLOT(loadStarted()));
connect(m_webView, SIGNAL(loadProgress(int)), SLOT(loadProgress(int)));
connect(m_webView, SIGNAL(loadFinished(bool)), SLOT(loadFinished()));
connect(m_webView, SIGNAL(urlChanged(QUrl)), this, SLOT(showUrl(QUrl)));
connect(m_webView, SIGNAL(privacyChanged(bool)), this, SLOT(setPrivacyState(bool)));
connect(m_webView, &TabbedWebView::iconChanged, this, &LocationBar::updateSiteIcon);
}
void LocationBar::setText(const QString &text)
{
m_oldTextLength = text.length();
m_currentTextLength = m_oldTextLength;
LineEdit::setText(text);
refreshTextFormat();
}
void LocationBar::updatePlaceHolderText()
{
if (qzSettings->searchFromAddressBar) {
setPlaceholderText(tr("Enter URL address or search on %1").arg(searchEngineName()));
} else
setPlaceholderText(tr("Enter URL address"));
}
void LocationBar::showCompletion(const QString &completion, bool completeDomain)
{
LineEdit::setText(completion);
// Move cursor to the end
end(false);
if (completeDomain) {
completer()->complete();
}
}
void LocationBar::clearCompletion()
{
m_webView->setFocus();
showUrl(m_webView->url());
}
void LocationBar::showDomainCompletion(const QString &completion)
{
m_domainCompleterModel->setStringList(QStringList() << completion);
// We need to manually force the completion because model is updated asynchronously
// But only force completion when the user actually added new text
if (m_oldTextLength < m_currentTextLength)
completer()->complete();
}
LoadRequest LocationBar::createLoadRequest() const
{
LoadRequest req;
const QString &t = text().trimmed();
// Check for Search Engine shortcut
int firstSpacePos = t.indexOf(QLatin1Char(' '));
if (firstSpacePos != -1) {
const QString shortcut = t.left(firstSpacePos);
const QString searchedString = t.mid(firstSpacePos).trimmed();
SearchEngine en = mApp->searchEnginesManager()->engineForShortcut(shortcut);
if (!en.name.isEmpty()) {
req = mApp->searchEnginesManager()->searchResult(en, searchedString);
}
}
// Check for Bookmark keyword
QList<BookmarkItem*> items = mApp->bookmarks()->searchKeyword(t);
if (!items.isEmpty()) {
BookmarkItem* item = items.at(0);
item->updateVisitCount();
req.setUrl(item->url());
}
if (req.isEmpty()) {
// One word needs special handling, because QUrl::fromUserInput
// would convert it to QUrl("http://WORD")
if (!t.contains(QL1C(' ')) && !t.contains(QL1C('.'))) {
req.setUrl(QUrl(t));
}
else {
const QUrl &guessed = QUrl::fromUserInput(t);
if (!guessed.isEmpty())
req.setUrl(guessed);
else
req.setUrl(QUrl::fromEncoded(t.toUtf8()));
}
}
return req;
}
QString LocationBar::convertUrlToText(const QUrl &url)
{
// It was most probably entered by user, so don't urlencode it
if (url.scheme().isEmpty()) {
return QUrl::fromPercentEncoding(url.toEncoded());
}
QString stringUrl = QzTools::urlEncodeQueryString(url);
if (stringUrl == QL1S("qupzilla:speeddial") || stringUrl == QL1S("about:blank")) {
stringUrl.clear();
}
return stringUrl;
}
QString LocationBar::searchEngineName()
{
if (!qzSettings->searchFromAddressBar) {
return QString();
} else if (qzSettings->searchWithDefaultEngine) {
return mApp->searchEnginesManager()->defaultEngine().name;
} else {
return mApp->searchEnginesManager()->activeEngine().name;
}
}
void LocationBar::refreshTextFormat()
{
if (!m_webView) {
return;
}
TextFormat textFormat;
const QString hostName = m_webView->url().isEmpty() ? QUrl(text()).host() : m_webView->url().host();
if (!hostName.isEmpty()) {
const int hostPos = text().indexOf(hostName);
if (hostPos > 0) {
QTextCharFormat format;
format.setForeground(Colors::mid(palette().color(QPalette::Base), palette().color(QPalette::Text), 1, 1));
QTextLayout::FormatRange schemePart;
schemePart.start = 0;
schemePart.length = hostPos;
schemePart.format = format;
QTextLayout::FormatRange hostPart;
hostPart.start = hostPos;
hostPart.length = hostName.size();
QTextLayout::FormatRange remainingPart;
remainingPart.start = hostPos + hostName.size();
remainingPart.length = text().size() - remainingPart.start;
remainingPart.format = format;
textFormat.append(schemePart);
textFormat.append(hostPart);
textFormat.append(remainingPart);
}
}
setTextFormat(textFormat);
}
void LocationBar::requestLoadUrl()
{
const LoadRequest req = createLoadRequest();
const QString urlString = convertUrlToText(req.url());
m_completer->closePopup();
m_webView->setFocus();
if (urlString != text()) {
setText(urlString);
}
m_webView->userLoadAction(req);
}
void LocationBar::textEdited(const QString &text)
{
m_oldTextLength = m_currentTextLength;
m_currentTextLength = text.length();
if (!text.isEmpty()) {
m_completer->complete(text);
}
else {
m_completer->closePopup();
}
setGoIconVisible(true);
}
void LocationBar::setGoIconVisible(bool state)
{
if (state) {
m_bookmarkIcon->hide();
m_goIcon->show();
}
else {
m_bookmarkIcon->show();
if (!qzSettings->alwaysShowGoIcon) {
m_goIcon->hide();
}
}
updateTextMargins();
}
void LocationBar::showUrl(const QUrl &url)
{
if (hasFocus() || url.isEmpty()) {
return;
}
const QString stringUrl = convertUrlToText(url);
if (text() == stringUrl) {
home(false);
refreshTextFormat();
return;
}
// Set converted url as text
setText(stringUrl);
// Move cursor to the start
home(false);
m_bookmarkIcon->checkBookmark(url);
}
void LocationBar::updateSiteIcon()
{
QIcon icon = m_webView ? m_webView->icon() : IconProvider::emptyWebIcon();
if (m_webView && m_webView->url().scheme() == QL1S("https"))
icon = QIcon::fromTheme(QSL("document-encrypted"), icon);
m_siteIcon->setIcon(QIcon(icon.pixmap(16)));
}
void LocationBar::setPrivacyState(bool state)
{
m_siteIcon->setProperty("secured", QVariant(state));
m_siteIcon->style()->unpolish(m_siteIcon);
m_siteIcon->style()->polish(m_siteIcon);
setProperty("secured", QVariant(state));
style()->unpolish(this);
style()->polish(this);
}
void LocationBar::pasteAndGo()
{
clear();
paste();
requestLoadUrl();
}
void LocationBar::contextMenuEvent(QContextMenuEvent* event)
{
QMenu* menu = createContextMenu();
menu->setAttribute(Qt::WA_DeleteOnClose);
// Prevent choosing first option with double rightclick
QPoint pos = event->globalPos();
pos.setY(pos.y() + 1);
menu->popup(pos);
}
void LocationBar::showEvent(QShowEvent* event)
{
LineEdit::showEvent(event);
refreshTextFormat();
}
void LocationBar::focusInEvent(QFocusEvent* event)
{
if (m_webView) {
const QString stringUrl = convertUrlToText(m_webView->url());
// Text has been edited, let's show go button
if (stringUrl != text()) {
setGoIconVisible(true);
}
}
clearTextFormat();
LineEdit::focusInEvent(event);
if (Settings().value("Browser-View-Settings/instantBookmarksToolbar").toBool()) {
m_window->bookmarksToolbar()->show();
}
}
void LocationBar::focusOutEvent(QFocusEvent* event)
{
// Context menu or completer popup were opened
// Let's block focusOutEvent to trick QLineEdit and paint cursor properly
if (event->reason() == Qt::PopupFocusReason) {
return;
}
LineEdit::focusOutEvent(event);
setGoIconVisible(false);
if (text().trimmed().isEmpty()) {
clear();
}
refreshTextFormat();
if (Settings().value("Browser-View-Settings/instantBookmarksToolbar").toBool()) {
m_window->bookmarksToolbar()->hide();
}
}
void LocationBar::dropEvent(QDropEvent* event)
{
if (event->mimeData()->hasUrls()) {
const QUrl dropUrl = event->mimeData()->urls().at(0);
if (WebView::isUrlValid(dropUrl)) {
setText(dropUrl.toString());
m_webView->setFocus();
m_webView->userLoadAction(dropUrl);
QFocusEvent event(QFocusEvent::FocusOut);
LineEdit::focusOutEvent(&event);
return;
}
}
else if (event->mimeData()->hasText()) {
const QString dropText = event->mimeData()->text().trimmed();
const QUrl dropUrl = QUrl(dropText);
if (WebView::isUrlValid(dropUrl)) {
setText(dropUrl.toString());
m_webView->setFocus();
m_webView->userLoadAction(dropUrl);
QFocusEvent event(QFocusEvent::FocusOut);
LineEdit::focusOutEvent(&event);
return;
} else {
setText(dropText);
setFocus();
return;
}
}
LineEdit::dropEvent(event);
}
void LocationBar::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
case Qt::Key_V:
if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
pasteAndGo();
event->accept();
return;
}
break;
case Qt::Key_Down:
m_completer->complete(text());
break;
case Qt::Key_Left:
m_completer->closePopup();
break;
case Qt::Key_Escape:
m_webView->setFocus();
showUrl(m_webView->url());
event->accept();
break;
case Qt::Key_Alt:
m_holdingAlt = true;
break;
case Qt::Key_Return:
case Qt::Key_Enter:
switch (event->modifiers()) {
case Qt::ControlModifier:
if (!text().endsWith(QL1S(".com")))
setText(text().append(QL1S(".com")));
requestLoadUrl();
m_holdingAlt = false;
break;
case Qt::AltModifier:
m_completer->closePopup();
m_window->tabWidget()->addView(createLoadRequest());
m_holdingAlt = false;
break;
default:
requestLoadUrl();
m_holdingAlt = false;
}
break;
case Qt::Key_0:
case Qt::Key_1:
case Qt::Key_2:
case Qt::Key_3:
case Qt::Key_4:
case Qt::Key_5:
case Qt::Key_6:
case Qt::Key_7:
case Qt::Key_8:
case Qt::Key_9:
if (event->modifiers() & Qt::AltModifier || event->modifiers() & Qt::ControlModifier) {
event->ignore();
m_holdingAlt = false;
return;
}
break;
default:
m_holdingAlt = false;
}
LineEdit::keyPressEvent(event);
}
void LocationBar::loadStarted()
{
m_progressVisible = true;
m_progressTimer->stop();
m_autofillIcon->hide();
m_siteIcon->setIcon(IconProvider::emptyWebIcon());
}
void LocationBar::loadProgress(int progress)
{
if (qzSettings->showLoadingProgress) {
m_loadProgress = progress;
update();
}
}
void LocationBar::loadFinished()
{
if (qzSettings->showLoadingProgress) {
m_progressTimer->start();
}
WebPage* page = qobject_cast<WebPage*>(m_webView->page());
if (page && page->hasMultipleUsernames()) {
m_autofillIcon->setFormData(page->autoFillData());
m_autofillIcon->show();
}
updateSiteIcon();
}
void LocationBar::loadSettings()
{
Settings settings;
settings.beginGroup("AddressBar");
m_progressStyle = static_cast<ProgressStyle>(settings.value("ProgressStyle", 0).toInt());
bool customColor = settings.value("UseCustomProgressColor", false).toBool();
m_progressColor = customColor ? settings.value("CustomProgressColor", palette().color(QPalette::Highlight)).value<QColor>() : QColor();
settings.endGroup();
}
void LocationBar::hideProgress()
{
if (qzSettings->showLoadingProgress) {
m_progressVisible = false;
update();
}
}
void LocationBar::paintEvent(QPaintEvent* event)
{
LineEdit::paintEvent(event);
// Show loading progress
if (qzSettings->showLoadingProgress && m_progressVisible) {
QStyleOptionFrame option;
initStyleOption(&option);
int lm, tm, rm, bm;
getTextMargins(&lm, &tm, &rm, &bm);
QRect contentsRect = style()->subElementRect(QStyle::SE_LineEditContents, &option, this);
contentsRect.adjust(lm, tm, -rm, -bm);
QColor bg = m_progressColor;
if (!bg.isValid() || bg.alpha() == 0) {
bg = Colors::mid(palette().color(QPalette::Base), palette().color(QPalette::Text), m_progressStyle > 0 ? 4 : 8, 1);
}
QPainter p(this);
p.setBrush(QBrush(bg));
// We are painting over text, make sure the text stays visible
p.setOpacity(0.5);
QPen outlinePen(bg.darker(110), 0.8);
p.setPen(outlinePen);
switch (m_progressStyle) {
case ProgressFilled: {
QRect bar = contentsRect.adjusted(0, 1, 0, -1);
bar.setWidth(bar.width() * m_loadProgress / 100);
const int roundness = bar.height() / 4.0;
p.drawRoundedRect(bar, roundness, roundness);
break;
}
case ProgressBottom: {
outlinePen.setWidthF(0.3);
outlinePen.setColor(outlinePen.color().darker(130));
p.setPen(outlinePen);
QRect bar(contentsRect.x(), contentsRect.bottom() - 3,
contentsRect.width() * m_loadProgress / 100.0, 3);
p.drawRoundedRect(bar, 1, 1);
break;
}
case ProgressTop: {
outlinePen.setWidthF(0.3);
outlinePen.setColor(outlinePen.color().darker(130));
p.setPen(outlinePen);
QRect bar(contentsRect.x(), contentsRect.top() + 1, contentsRect.width() * m_loadProgress / 100.0, 3);
p.drawRoundedRect(bar, 1, 1);
break;
}
default:
break;
}
}
}