1
mirror of https://invent.kde.org/network/falkon.git synced 2024-12-20 10:46:35 +01:00

[LocationCompleter] New highlighter algorithm using QTextLayout that fixes rendering of RTL strings.

This commit is contained in:
S. Razi Alavizadeh 2014-05-29 23:29:02 +04:30
parent 7a1f4c77cd
commit 877266b418
2 changed files with 118 additions and 145 deletions

View File

@ -24,6 +24,7 @@
#include <QPainter> #include <QPainter>
#include <QApplication> #include <QApplication>
#include <QMouseEvent> #include <QMouseEvent>
#include <QTextLayout>
LocationCompleterDelegate::LocationCompleterDelegate(LocationCompleterView* parent) LocationCompleterDelegate::LocationCompleterDelegate(LocationCompleterView* parent)
: QStyledItemDelegate(parent) : QStyledItemDelegate(parent)
@ -100,10 +101,10 @@ void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewI
// RTL Support: remove conflicting of right-aligned text and starpixmap! // RTL Support: remove conflicting of right-aligned text and starpixmap!
const int rightTitleEdge = rightPosition - m_padding - starPixmapWidth; const int rightTitleEdge = rightPosition - m_padding - starPixmapWidth;
QRect titleRect(leftTitleEdge, opt.rect.top() + m_padding, rightTitleEdge - leftTitleEdge, titleMetrics.height()); QRect titleRect(leftTitleEdge, opt.rect.top() + m_padding, rightTitleEdge - leftTitleEdge, titleMetrics.height());
QString title(titleMetrics.elidedText(index.data(LocationCompleterModel::TitleRole).toString(), Qt::ElideRight, titleRect.width())); QString title = index.data(LocationCompleterModel::TitleRole).toString();
painter->setFont(titleFont); painter->setFont(titleFont);
drawHighlightedTextLine(titleRect, title, searchText, painter, style, opt, colorRole); viewItemDrawText(painter, &opt, titleRect, title, colorRole, searchText);
// Draw link // Draw link
const int infoYPos = titleRect.bottom() + opt.fontMetrics.leading() + 2; const int infoYPos = titleRect.bottom() + opt.fontMetrics.leading() + 2;
@ -122,7 +123,6 @@ void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewI
link = QString::fromLatin1(linkArray.left(500)); link = QString::fromLatin1(linkArray.left(500));
} }
link = opt.fontMetrics.elidedText(link, Qt::ElideRight, linkRect.width());
painter->setFont(opt.font); painter->setFont(opt.font);
// Draw url (or switch to tab) // Draw url (or switch to tab)
@ -136,10 +136,10 @@ void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewI
QRect textRect(linkRect); QRect textRect(linkRect);
textRect.setX(textRect.x() + m_padding + 16 + m_padding); textRect.setX(textRect.x() + m_padding + 16 + m_padding);
drawTextLine(textRect, LocationCompleterView::tr("Switch to tab"), painter, style, opt, colorLinkRole); viewItemDrawText(painter, &opt, textRect, LocationCompleterView::tr("Switch to tab"), colorLinkRole);
} }
else { else {
drawHighlightedTextLine(linkRect, link, searchText, painter, style, opt, colorLinkRole); viewItemDrawText(painter, &opt, linkRect, link, colorLinkRole, searchText);
} }
// Draw line at the very bottom of item if the item is not highlighted // Draw line at the very bottom of item if the item is not highlighted
@ -185,150 +185,128 @@ bool LocationCompleterDelegate::drawSwitchToTab() const
return qzSettings->showSwitchTab && m_drawSwitchToTab; return qzSettings->showSwitchTab && m_drawSwitchToTab;
} }
QRect LocationCompleterDelegate::adjustRect(const QRect &original, const QRect &created) const
{
if (created.left() + created.width() >= original.right()) {
QRect nRect = created;
nRect.setWidth(original.right() - created.left());
return nRect;
}
return created;
}
static bool sizeBiggerThan(const QString &s1, const QString &s2) static bool sizeBiggerThan(const QString &s1, const QString &s2)
{ {
return s1.size() > s2.size(); return s1.size() > s2.size();
} }
void LocationCompleterDelegate::drawHighlightedTextLine(const QRect &rect, const QString &text, const QString &searchText, static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
QPainter* painter, const QStyle* style, const QStyleOptionViewItemV4 &option,
const QPalette::ColorRole &role) const
{ {
QList<int> delimiters; qreal height = 0;
QStringList searchStrings = searchText.split(QLatin1Char(' '), QString::SkipEmptyParts); qreal widthUsed = 0;
textLayout.beginLayout();
QTextLine line = textLayout.createLine();
if (line.isValid()) {
line.setLineWidth(lineWidth);
line.setPosition(QPointF(0, height));
height += line.height();
widthUsed = qMax(widthUsed, line.naturalTextWidth());
// Look for longer parts first textLayout.endLayout();
qSort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan);
foreach (const QString &string, searchStrings) {
int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive);
while (delimiter != -1) {
int start = delimiter;
int end = delimiter + string.length();
bool alreadyContains = false;
for (int i = 0; i < delimiters.count(); ++i) {
int dStart = delimiters.at(i);
int dEnd = delimiters.at(++i);
if (dStart <= start && dEnd >= end) {
alreadyContains = true;
break;
}
}
if (!alreadyContains) {
delimiters.append(start);
delimiters.append(end);
}
delimiter = text.indexOf(string, end, Qt::CaseInsensitive);
}
}
// We need to sort delimiters to properly paint all parts that user typed
qSort(delimiters);
// If we don't find any match, just paint it without any highlight
if (delimiters.isEmpty() || delimiters.count() % 2) {
drawTextLine(rect, text, painter, style, option, role);
return;
}
QFont normalFont = painter->font();
QFont boldFont = normalFont;
boldFont.setBold(true);
QFontMetrics normalMetrics(normalFont);
QFontMetrics boldMetrics(boldFont);
int lastEndPos = 0;
int lastRectPos = rect.left();
while (!delimiters.isEmpty()) {
int start = delimiters.takeFirst();
int end = delimiters.takeFirst();
const QString normalPart = text.mid(lastEndPos, start - lastEndPos);
const QString boldPart = text.mid(start, end - start);
lastEndPos = end;
if (!normalPart.isEmpty()) {
int width = normalMetrics.width(normalPart);
QRect nRect = adjustRect(rect, QRect(lastRectPos, rect.top(), width, rect.height()));
if (nRect.width() > 0) {
if (text.isRightToLeft()) {
nRect = style->visualRect(Qt::RightToLeft, rect, nRect);
}
painter->setFont(normalFont);
drawTextLine(nRect, normalPart, painter, style, option, role);
lastRectPos += nRect.width();
}
}
if (!boldPart.isEmpty()) {
int width = boldMetrics.width(boldPart);
QRect bRect = adjustRect(rect, QRect(lastRectPos, rect.top(), width, rect.height()));
if (bRect.width() > 0) {
if (text.isRightToLeft()) {
bRect = style->visualRect(Qt::RightToLeft, rect, bRect);
}
painter->setFont(boldFont);
drawTextLine(bRect, boldPart, painter, style, option, role);
// Paint manually line under text instead of using QFont::underline
QRect underlineRect(bRect.left(), bRect.top() + boldMetrics.ascent() + 1,
bRect.width(), boldFont.pointSize() > 8 ? 2 : 1);
painter->fillRect(underlineRect, option.palette.color(role));
lastRectPos += bRect.width();
}
}
if (delimiters.isEmpty() && lastEndPos != text.size()) {
const QString lastText = text.mid(lastEndPos);
int width = normalMetrics.width(lastText);
QRect nRect = adjustRect(rect, QRect(lastRectPos, rect.top(), width, rect.height()));
if (text.isRightToLeft()) {
nRect = style->visualRect(Qt::RightToLeft, rect, nRect);
}
painter->setFont(normalFont);
drawTextLine(nRect, lastText, painter, style, option, role);
}
} }
return QSizeF(widthUsed, height);
} }
void LocationCompleterDelegate::drawTextLine(const QRect &rect, QString text, QPainter* painter, // most of codes taken from QCommonStylePrivate::viewItemDrawText()
const QStyle* style, const QStyleOptionViewItemV4 &option, // added highlighting and simplified for single-line textlayouts
const QPalette::ColorRole &role) const void LocationCompleterDelegate::viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect,
const QString &text, const QPalette::ColorRole &role, const QString &searchText) const
{ {
if (rect.width() > 0) { const QColor &color = option->palette.color(role);
const Qt::LayoutDirection direction = option.widget ? option.widget->layoutDirection() : QApplication::layoutDirection(); const QWidget *widget = option->widget;
Qt::LayoutDirection textDirection = text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight; const QStyle* proxyStyle = widget ? widget->style()->proxy() : QApplication::style()->proxy();
Qt::Alignment alignment = textDirection == direction ? Qt::AlignLeft : Qt::AlignRight; const int textMargin = proxyStyle->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
// Insert unicode control characters: prepend RLE or LRE and append (RLM or LRM)+PDF QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
text.isRightToLeft() ? text.prepend(QChar(0x202B)).append(0x200F) : text.prepend(QChar(0x202A)).append(0x200E); const QFontMetrics &fontMetrics(p->font());
text.append(QChar(0x202C)); // a workaround for not drawing highlighted text on icon
const QString &elidedText = fontMetrics.elidedText(text, option->textElideMode, textRect.width() - 2 * m_padding - 2);
QTextOption textOption;
textOption.setWrapMode(QTextOption::NoWrap);
textOption.setTextDirection(text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight);
textOption.setAlignment(QStyle::visualAlignment(textOption.textDirection(), option->displayAlignment));
QTextLayout textLayout;
textLayout.setFont(p->font());
textLayout.setText(elidedText);
textLayout.setTextOption(textOption);
style->drawItemText(painter, rect, Qt::TextSingleLine | alignment, option.palette, true, text, role); if (!searchText.isEmpty()) {
QList<int> delimiters;
QStringList searchStrings = searchText.split(QLatin1Char(' '), QString::SkipEmptyParts);
// Look for longer parts first
qSort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan);
foreach (const QString &string, searchStrings) {
int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive);
while (delimiter != -1) {
int start = delimiter;
int end = delimiter + string.length();
bool alreadyContains = false;
for (int i = 0; i < delimiters.count(); ++i) {
int dStart = delimiters.at(i);
int dEnd = delimiters.at(++i);
if (dStart <= start && dEnd >= end) {
alreadyContains = true;
break;
}
}
if (!alreadyContains) {
delimiters.append(start);
delimiters.append(end);
}
delimiter = text.indexOf(string, end, Qt::CaseInsensitive);
}
}
// We need to sort delimiters to properly paint all parts that user typed
qSort(delimiters);
// If we don't find any match, just paint it without any highlight
if (!delimiters.isEmpty() && !(delimiters.count() % 2)) {
QList<QTextLayout::FormatRange> highlightParts;
QTextLayout::FormatRange lighterWholeLine;
lighterWholeLine.start = 0;
lighterWholeLine.length = elidedText.size();
QColor lighterColor = color.lighter(130);
if (lighterColor == color) {
lighterColor = QColor(Qt::gray).darker(180);
}
lighterWholeLine.format.setForeground(lighterColor);
highlightParts << lighterWholeLine;
while (!delimiters.isEmpty()) {
QTextLayout::FormatRange highlightedPart;
int start = delimiters.takeFirst();
int end = delimiters.takeFirst();
highlightedPart.start = start;
highlightedPart.length = end - start;
highlightedPart.format.setFontWeight(QFont::Bold);
highlightedPart.format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
highlightedPart.format.setForeground(color);
highlightParts << highlightedPart;
}
textLayout.setAdditionalFormats(highlightParts);
}
}
// do layout
viewItemTextLayout(textLayout, textRect.width());
// draw line
p->setPen(color);
const int lineCount = textLayout.lineCount();
if (lineCount > 0) {
qreal height = textLayout.lineAt(0).height();
qreal width = qMax<qreal>(textRect.width(), textLayout.lineAt(0).width());
const QRect &layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, QSize(int(width), int(height)), textRect);
const QPointF &position = layoutRect.topLeft();
textLayout.lineAt(0).draw(p, position);
} }
} }

View File

@ -36,15 +36,10 @@ public:
private: private:
bool drawSwitchToTab() const; bool drawSwitchToTab() const;
QRect adjustRect(const QRect &original, const QRect &created) const;
void drawHighlightedTextLine(const QRect &rect, const QString &text, const QString &searchText, void viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect,
QPainter* painter, const QStyle* style, const QStyleOptionViewItemV4 &option, const QString &text, const QPalette::ColorRole &role,
const QPalette::ColorRole &role) const; const QString &searchText = QString()) const;
void drawTextLine(const QRect &rect, QString text, QPainter* painter,
const QStyle* style, const QStyleOptionViewItemV4 &option,
const QPalette::ColorRole &role) const;
mutable int m_rowHeight; mutable int m_rowHeight;
mutable int m_padding; mutable int m_padding;