1
mirror of https://invent.kde.org/network/falkon.git synced 2024-12-20 18:56:34 +01:00

[LocationBar] Major cleanup. New code to change color of text parts

The code to change color of host of url is completely new.
It doesn't use any paint hacks anymore. That means text in
locationbar will now be always rendered pixel perfect, as it would
with normal QLineEdit.

Cleanup in LocationCompleter. It is now using QCompleter in inline
mode to complete domain. It is still not ideal, eg. it refreshes
and show the domain completion with delay (job run in separate thread),
and sometimes it misses it completely.

WebSearchBar: Always complete when receiving suggestions. Fix issue
when suggestions were only showed after typing at least 2 characters.
This commit is contained in:
nowrep 2014-03-15 23:22:35 +01:00
parent 9c7e9a1396
commit 72904a80c3
8 changed files with 198 additions and 293 deletions

View File

@ -23,6 +23,7 @@
#include <QStyleOption>
#include <QPainter>
#include <QFocusEvent>
#include <QApplication>
SideWidget::SideWidget(QWidget* parent)
: QWidget(parent)
@ -185,6 +186,33 @@ int LineEdit::textMargin(WidgetPosition position) const
return w + spacing * 2;
}
int LineEdit::leftMargin() const
{
return m_leftMargin;
}
// http://stackoverflow.com/a/14424003
void LineEdit::setTextFormat(const LineEdit::TextFormat &format)
{
QList<QInputMethodEvent::Attribute> attributes;
foreach (const QTextLayout::FormatRange &fr, format) {
QInputMethodEvent::AttributeType type = QInputMethodEvent::TextFormat;
int start = fr.start - cursorPosition();
int length = fr.length;
QVariant value = fr.format;
attributes.append(QInputMethodEvent::Attribute(type, start, length, value));
}
QInputMethodEvent event(QString(), attributes);
QApplication::sendEvent(this, &event);
}
void LineEdit::clearTextFormat()
{
setTextFormat(TextFormat());
}
void LineEdit::updateTextMargins()
{
int left = m_leftMargin < 0 ? m_leftWidget->sizeHint().width() : m_leftMargin;

View File

@ -47,6 +47,7 @@
* ============================================================ */
#include <QLineEdit>
#include <QTextLayout>
#include "qzcommon.h"
class QHBoxLayout;
@ -68,6 +69,8 @@ class QUPZILLA_EXPORT LineEdit : public QLineEdit
Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin)
public:
typedef QList<QTextLayout::FormatRange> TextFormat;
enum WidgetPosition {
LeftSide,
RightSide
@ -81,8 +84,10 @@ public:
void setWidgetSpacing(int spacing);
int widgetSpacing() const;
int textMargin(WidgetPosition position) const;
int leftMargin() const;
int leftMargin() { return m_leftMargin; }
void setTextFormat(const TextFormat &format);
void clearTextFormat();
public slots:
void setLeftMargin(int margin);

View File

@ -37,7 +37,7 @@ LocationCompleter::LocationCompleter(QObject* parent)
, m_window(0)
, m_locationBar(0)
, m_lastRefreshTimestamp(0)
, m_showingMostVisited(false)
, m_popupClosed(false)
{
if (!s_view) {
s_model = new LocationCompleterModel;
@ -56,37 +56,22 @@ void LocationCompleter::setLocationBar(LocationBar* locationBar)
m_locationBar = locationBar;
}
QString LocationCompleter::domainCompletion() const
{
return qzSettings->useInlineCompletion ? m_domainCompletion : QString();
}
bool LocationCompleter::isShowingMostVisited() const
{
return m_showingMostVisited;
}
bool LocationCompleter::isPopupVisible() const
{
return s_view->isVisible();
}
void LocationCompleter::closePopup()
{
m_domainCompletion.clear();
m_showingMostVisited = false;
m_popupClosed = true;
s_view->close();
}
void LocationCompleter::complete(const QString &string)
{
m_domainCompletion.clear();
m_showingMostVisited = string.isEmpty();
QString trimmedStr = string.trimmed();
LocationCompleterRefreshJob* job = new LocationCompleterRefreshJob(string);
// Indicates that new completion was requested by user
// Eg. popup was not closed yet this completion session
m_popupClosed = false;
LocationCompleterRefreshJob* job = new LocationCompleterRefreshJob(trimmedStr);
connect(job, SIGNAL(finished()), this, SLOT(refreshJobFinished()));
emit domainCompletionChanged();
}
void LocationCompleter::showMostVisited()
@ -94,15 +79,25 @@ void LocationCompleter::showMostVisited()
complete(QString());
}
void LocationCompleter::currentChanged(const QModelIndex &index)
void LocationCompleter::refreshJobFinished()
{
QString completion = index.data().toString();
LocationCompleterRefreshJob* job = qobject_cast<LocationCompleterRefreshJob*>(sender());
Q_ASSERT(job);
if (completion.isEmpty()) {
completion = m_originalText;
// Don't show result of older jobs
// Also don't open the popup again when it was already closed
if (job->timestamp() > m_lastRefreshTimestamp && !m_popupClosed) {
s_model->setCompletions(job->completions());
m_lastRefreshTimestamp = job->timestamp();
showPopup();
if (qzSettings->useInlineCompletion) {
emit showDomainCompletion(job->domainCompletion());
}
}
emit showCompletion(completion);
job->deleteLater();
}
void LocationCompleter::slotPopupClosed()
@ -117,21 +112,15 @@ void LocationCompleter::slotPopupClosed()
emit popupClosed();
}
void LocationCompleter::refreshJobFinished()
void LocationCompleter::currentChanged(const QModelIndex &index)
{
LocationCompleterRefreshJob* job = qobject_cast<LocationCompleterRefreshJob*>(sender());
Q_ASSERT(job);
QString completion = index.data().toString();
if (job->timestamp() > m_lastRefreshTimestamp) {
m_domainCompletion = job->domainCompletion();
s_model->setCompletions(job->completions());
m_lastRefreshTimestamp = job->timestamp();
showPopup();
emit domainCompletionChanged();
if (completion.isEmpty()) {
completion = m_originalText;
}
job->deleteLater();
emit showCompletion(completion);
}
void LocationCompleter::indexActivated(const QModelIndex &index)

View File

@ -39,27 +39,22 @@ public:
void setMainWindow(BrowserWindow* window);
void setLocationBar(LocationBar* locationBar);
QString domainCompletion() const;
bool isShowingMostVisited() const;
bool isPopupVisible() const;
void closePopup();
signals:
void showCompletion(const QString &completion);
void loadCompletion();
void clearCompletion();
void domainCompletionChanged();
void popupClosed();
public slots:
void complete(const QString &string);
void showMostVisited();
signals:
void showCompletion(const QString &completion);
void showDomainCompletion(const QString &completion);
void loadCompletion();
void clearCompletion();
void popupClosed();
private slots:
void slotPopupClosed();
void refreshJobFinished();
void slotPopupClosed();
void currentChanged(const QModelIndex &index);
@ -77,10 +72,9 @@ private:
BrowserWindow* m_window;
LocationBar* m_locationBar;
QString m_originalText;
QString m_domainCompletion;
qint64 m_lastRefreshTimestamp;
bool m_showingMostVisited;
QString m_originalText;
bool m_popupClosed;
static LocationCompleterView* s_view;
static LocationCompleterModel* s_model;

View File

@ -24,7 +24,12 @@
#include "bookmarks.h"
#include <QDateTime>
#include <QtConcurrent/QtConcurrent>
#if QT_VERSION >= 0x050000
#include <QtConcurrent/QtConcurrentRun>
#else
#include <QtConcurrentRun>
#endif
LocationCompleterRefreshJob::LocationCompleterRefreshJob(const QString &searchString)
: QObject()
@ -197,15 +202,15 @@ void LocationCompleterRefreshJob::completeMostVisited()
QString LocationCompleterRefreshJob::createDomainCompletion(const QString &completion) const
{
QString str = completion;
// Make sure search string and completion matches
if (m_searchString.startsWith(QLatin1String("www."))) {
return str.mid(m_searchString.size());
if (m_searchString.startsWith(QL1S("www.")) && !completion.startsWith(QL1S("www."))) {
return QL1S("www.") + completion;
}
if (str.startsWith(QLatin1String("www."))) {
str = str.mid(4);
if (!m_searchString.startsWith(QL1S("www.")) && completion.startsWith(QL1S("www."))) {
return completion.mid(4);
}
return str.mid(m_searchString.size());
return completion;
}

View File

@ -45,6 +45,8 @@
#include <QContextMenuEvent>
#include <QAction>
#include <QMenu>
#include <QCompleter>
#include <QStringListModel>
LocationBar::LocationBar(BrowserWindow* window)
: LineEdit(window)
@ -56,9 +58,6 @@ LocationBar::LocationBar(BrowserWindow* window)
, m_holdingAlt(false)
, m_loadProgress(0)
, m_progressVisible(false)
, m_forcePaintEvent(false)
, m_inlineCompletionVisible(false)
, m_popupClosed(false)
{
setObjectName("locationbar");
setDragEnabled(true);
@ -87,10 +86,15 @@ LocationBar::LocationBar(BrowserWindow* window)
m_completer->setMainWindow(m_window);
m_completer->setLocationBar(this);
connect(m_completer, SIGNAL(showCompletion(QString)), this, SLOT(showCompletion(QString)));
connect(m_completer, SIGNAL(showDomainCompletion(QString)), this, SLOT(showDomainCompletion(QString)));
connect(m_completer, SIGNAL(loadCompletion()), this, SLOT(urlEnter()));
connect(m_completer, SIGNAL(clearCompletion()), this, SLOT(clearCompletion()));
connect(m_completer, SIGNAL(domainCompletionChanged()), this, SLOT(inlineCompletionChanged()));
connect(m_completer, SIGNAL(popupClosed()), this, SLOT(completionPopupClosed()));
m_domainCompleterModel = new QStringListModel(this);
QCompleter* domainCompleter = new QCompleter(this);
domainCompleter->setCompletionMode(QCompleter::InlineCompletion);
domainCompleter->setModel(m_domainCompleterModel);
setCompleter(domainCompleter);
connect(this, SIGNAL(textEdited(QString)), this, SLOT(textEdit()));
connect(m_goIcon, SIGNAL(clicked(QPoint)), this, SLOT(urlEnter()));
@ -110,6 +114,11 @@ LocationBar::LocationBar(BrowserWindow* window)
QTimer::singleShot(0, this, SLOT(updatePlaceHolderText()));
}
TabbedWebView* LocationBar::webView() const
{
return m_webView;
}
void LocationBar::setWebView(TabbedWebView* view)
{
m_webView = view;
@ -127,7 +136,8 @@ void LocationBar::setWebView(TabbedWebView* view)
void LocationBar::setText(const QString &text)
{
LineEdit::setText(text);
m_forcePaintEvent = true;
refreshTextFormat();
}
void LocationBar::updatePlaceHolderText()
@ -140,8 +150,6 @@ void LocationBar::updatePlaceHolderText()
void LocationBar::showCompletion(const QString &completion)
{
m_inlineCompletionVisible = false;
LineEdit::setText(completion);
// Move cursor to the end
@ -154,18 +162,12 @@ void LocationBar::clearCompletion()
showUrl(m_webView->url());
}
void LocationBar::completionPopupClosed()
void LocationBar::showDomainCompletion(const QString &completion)
{
m_inlineCompletionVisible = false;
m_popupClosed = true;
m_domainCompleterModel->setStringList(QStringList() << completion);
}
void LocationBar::inlineCompletionChanged()
{
repaint();
}
QUrl LocationBar::createUrl()
QUrl LocationBar::createUrl() const
{
QUrl urlToLoad;
@ -181,10 +183,6 @@ QUrl LocationBar::createUrl()
}
}
if (isInlineCompletionVisible()) {
urlToLoad = WebView::guessUrlFromString(text() + m_completer->domainCompletion());
}
if (urlToLoad.isEmpty()) {
QUrl guessedUrl = WebView::guessUrlFromString(text());
if (!guessedUrl.isEmpty()) {
@ -214,9 +212,43 @@ QString LocationBar::convertUrlToText(const QUrl &url) const
return stringUrl;
}
bool LocationBar::isInlineCompletionVisible() const
void LocationBar::refreshTextFormat()
{
return m_inlineCompletionVisible && !m_completer->domainCompletion().isEmpty();
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::urlEnter()
@ -238,7 +270,6 @@ void LocationBar::textEdit()
{
if (!text().isEmpty()) {
m_completer->complete(text());
m_inlineCompletionVisible = true;
}
else {
m_completer->closePopup();
@ -332,8 +363,6 @@ void LocationBar::pasteAndGo()
void LocationBar::contextMenuEvent(QContextMenuEvent* event)
{
Q_UNUSED(event)
if (!m_pasteAndGoAction) {
m_pasteAndGoAction = new QAction(QIcon::fromTheme("edit-paste"), tr("Paste And &Go"), this);
m_pasteAndGoAction->setShortcut(QKeySequence("Ctrl+Shift+V"));
@ -392,6 +421,13 @@ void LocationBar::contextMenuEvent(QContextMenuEvent* event)
tempMenu->deleteLater();
}
void LocationBar::showEvent(QShowEvent* event)
{
LineEdit::showEvent(event);
refreshTextFormat();
}
void LocationBar::focusInEvent(QFocusEvent* event)
{
if (m_webView) {
@ -403,9 +439,29 @@ void LocationBar::focusInEvent(QFocusEvent* event)
}
}
clearTextFormat();
LineEdit::focusInEvent(event);
}
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);
hideGoButton();
if (text().trimmed().isEmpty()) {
clear();
}
refreshTextFormat();
}
void LocationBar::dropEvent(QDropEvent* event)
{
if (event->mimeData()->hasUrls()) {
@ -439,24 +495,6 @@ void LocationBar::dropEvent(QDropEvent* event)
LineEdit::dropEvent(event);
}
void LocationBar::focusOutEvent(QFocusEvent* event)
{
LineEdit::focusOutEvent(event);
if (event->reason() == Qt::PopupFocusReason
|| (!selectedText().isEmpty() && event->reason() != Qt::TabFocusReason)) {
return;
}
m_popupClosed = false;
m_forcePaintEvent = true;
hideGoButton();
if (text().trimmed().isEmpty()) {
clear();
}
}
void LocationBar::keyPressEvent(QKeyEvent* event)
{
switch (event->key()) {
@ -468,46 +506,12 @@ void LocationBar::keyPressEvent(QKeyEvent* event)
}
break;
case Qt::Key_A:
if (event->modifiers() == Qt::ControlModifier && m_inlineCompletionVisible) {
m_inlineCompletionVisible = false;
}
break;
case Qt::Key_Down:
m_completer->complete(text());
break;
case Qt::Key_End:
case Qt::Key_Right: {
const QString completionText = m_completer->domainCompletion();
if (m_inlineCompletionVisible && !completionText.isEmpty()) {
m_inlineCompletionVisible = false;
setText(text() + completionText);
setCursorPosition(text().size());
m_completer->closePopup();
}
if (m_completer->isPopupVisible()) {
m_completer->closePopup();
}
break;
}
case Qt::Key_Left:
if (m_completer->isPopupVisible()) {
m_completer->closePopup();
}
break;
case Qt::Key_Delete:
if (m_inlineCompletionVisible) {
m_inlineCompletionVisible = false;
update();
event->accept();
}
break;
case Qt::Key_Escape:
@ -626,6 +630,7 @@ void LocationBar::hideProgress()
void LocationBar::paintEvent(QPaintEvent* event)
{
if (qzSettings->showLoadingProgress && m_progressVisible) {
QStyleOptionFrameV3 option;
initStyleOption(&option);
@ -635,93 +640,14 @@ void LocationBar::paintEvent(QPaintEvent* event)
QRect contentsRect = style()->subElementRect(QStyle::SE_LineEditContents, &option, this);
contentsRect.adjust(lm, tm, -rm, -bm);
const QFontMetrics fm = fontMetrics();
const int x = contentsRect.x() + 4;
const int y = contentsRect.y() + (contentsRect.height() - fm.height() + 1) / 2;
const int width = contentsRect.width() - 6;
const int height = fm.height();
const QRect textRect(x, y, width, height);
QTextOption opt;
opt.setWrapMode(QTextOption::NoWrap);
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
p.setRenderHint(QPainter::TextAntialiasing, true);
if (hasFocus() && m_inlineCompletionVisible) {
// Draw inline domain completion if available
const QString completionText = m_completer->domainCompletion();
if (!completionText.isEmpty()) {
QRect completionRect = textRect;
completionRect.setX(completionRect.x() + fm.width(text()) + 1);
completionRect.setWidth(fm.width(completionText) + 1);
style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &p, this);
// Text part
p.drawText(textRect, text(), opt);
// Completion part
p.fillRect(completionRect, palette().color(QPalette::Highlight));
p.setPen(palette().color(QPalette::HighlightedText));
p.drawText(completionRect, completionText, opt);
return;
}
}
if (m_completer->isPopupVisible() && !m_completer->isShowingMostVisited()) {
// We need to draw cursor when popup is visible
// But don't paint it if we are just showing most visited sites
const int cursorWidth = style()->pixelMetric(QStyle::PM_TextCursorWidth, &option, this);
const int cursorHeight = fm.height();
QString textPart = text().left(cursorPosition());
int cursorXpos = x + fontMetrics().width(textPart);
QRect cursor = cursorRect();
cursor.setX(cursorXpos + 1);
cursor.setWidth(cursorWidth);
cursor.setHeight(cursorHeight);
style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &p, this);
QRect actualTextRect = textRect;
actualTextRect.setWidth(fontMetrics().width(text()) + 1);
// When popup is visible, Ctrl + A (Select All) is the only way to select text
if (selectedText() == text()) {
p.fillRect(actualTextRect, palette().color(QPalette::Highlight));
p.setPen(palette().color(QPalette::HighlightedText));
}
p.drawText(actualTextRect, text(), opt);
if (textRect.contains(cursor.center().x(), cursor.center().y())) {
p.fillRect(cursor, option.palette.text().color());
}
return;
}
if (/*hasFocus() ||*/ text().isEmpty() || m_forcePaintEvent) {
LineEdit::paintEvent(event);
if (m_forcePaintEvent) {
m_forcePaintEvent = false;
update();
}
return;
}
style()->drawPrimitive(QStyle::PE_PanelLineEdit, &option, &p, this);
QPen oldPen = p.pen();
if (qzSettings->showLoadingProgress && m_progressVisible) {
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);
bg = Colors::mid(palette().color(QPalette::Base), palette().color(QPalette::Text), m_progressStyle > 0 ? 4 : 8, 1);
}
QPainter p(this);
p.setBrush(QBrush(bg));
QPen outlinePen(bg.darker(110), 0.8);
@ -748,57 +674,16 @@ void LocationBar::paintEvent(QPaintEvent* event)
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);
QRect bar(contentsRect.x(), contentsRect.top() + 1, contentsRect.width() * m_loadProgress / 100.0, 3);
p.drawRoundedRect(bar, 1, 1);
break;
}
default:
break;
}
return;
}
p.setPen(oldPen);
const QString hostName = m_webView->url().host();
QString currentText = text();
QRect currentRect = textRect;
if (!hostName.isEmpty()) {
const int hostPos = currentText.indexOf(hostName);
if (hostPos > 0) {
QPen lightPen = oldPen;
QColor lightColor = Colors::mid(palette().color(QPalette::Base),
palette().color(QPalette::Text),
1, 1);
lightPen.setColor(lightColor);
p.setPen(lightPen);
currentText = text().mid(0, hostPos);
currentRect.setWidth(fm.width(currentText));
p.drawText(currentRect, currentText, opt);
p.setPen(oldPen);
currentRect.setX(currentRect.x() + currentRect.width());
const int hostWidth = fm.width(hostName);
currentRect.setWidth(hostWidth);
p.drawText(currentRect, hostName, opt);
p.setFont(font());
currentText = text().mid(hostPos + hostName.length());
currentRect.setX(currentRect.x() + hostWidth);
currentRect.setWidth(textRect.width() - currentRect.x() + textRect.x());
p.setPen(lightPen);
if (currentText.isRightToLeft()) {
// Insert unicode control characters: prepend LRE then append LRM+PDF
currentText.prepend(QChar(0x202A)).append(QChar(0x200E)).append(QChar(0x202C));
}
}
}
p.drawText(currentRect, currentText, opt);
}
LocationBar::~LocationBar()
{
LineEdit::paintEvent(event);
}

View File

@ -23,6 +23,8 @@
#include "qzcommon.h"
#include "lineedit.h"
class QStringListModel;
class BrowserWindow;
class LineEdit;
class LocationCompleter;
@ -43,21 +45,20 @@ class QUPZILLA_EXPORT LocationBar : public LineEdit
public:
explicit LocationBar(BrowserWindow* window);
~LocationBar();
TabbedWebView* webView() const;
void setWebView(TabbedWebView* view);
TabbedWebView* webView() { return m_webView; }
signals:
void loadUrl(const QUrl &url);
public slots:
void showUrl(const QUrl &url);
void setText(const QString &text);
void showUrl(const QUrl &url);
protected:
void paintEvent(QPaintEvent* event);
signals:
void loadUrl(const QUrl &url);
private slots:
void textEdit();
void urlEnter();
@ -70,9 +71,8 @@ private slots:
void updatePlaceHolderText();
void showCompletion(const QString &completion);
void showDomainCompletion(const QString &completion);
void clearCompletion();
void completionPopupClosed();
void inlineCompletionChanged();
void onLoadStarted();
void onLoadProgress(int progress);
@ -89,20 +89,22 @@ private:
};
void contextMenuEvent(QContextMenuEvent* event);
void showEvent(QShowEvent* event);
void focusInEvent(QFocusEvent* event);
void focusOutEvent(QFocusEvent* event);
void keyPressEvent(QKeyEvent* event);
void keyReleaseEvent(QKeyEvent* event);
void dropEvent(QDropEvent* event);
QUrl createUrl();
QUrl createUrl() const;
QString convertUrlToText(const QUrl &url) const;
bool isInlineCompletionVisible() const;
void refreshTextFormat();
void showGoButton();
void hideGoButton();
LocationCompleter* m_completer;
QStringListModel* m_domainCompleterModel;
BookmarksIcon* m_bookmarkIcon;
GoIcon* m_goIcon;
@ -123,10 +125,6 @@ private:
bool m_progressVisible;
ProgressStyle m_progressStyle;
QColor m_progressColor;
bool m_forcePaintEvent;
bool m_inlineCompletionVisible;
bool m_popupClosed;
};
#endif // LOCATIONBAR_H

View File

@ -114,6 +114,7 @@ void WebSearchBar::addSuggestions(const QStringList &list)
if (qzSettings->showSearchSuggestions) {
QStringList list_ = list.mid(0, 6);
m_completerModel->setStringList(list_);
m_completer->complete();
}
}