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

Merge pull request #1376 from srazi/loc-highlighter

New highlighter algorithm using QTextLayout that fixes rendering of RTL strings.
This commit is contained in:
David Rosca 2014-06-17 14:56:51 +02:00
commit 5558d32d82
2 changed files with 120 additions and 126 deletions

View File

@ -24,6 +24,7 @@
#include <QPainter>
#include <QApplication>
#include <QMouseEvent>
#include <QTextLayout>
LocationCompleterDelegate::LocationCompleterDelegate(LocationCompleterView* parent)
: QStyledItemDelegate(parent)
@ -100,10 +101,10 @@ void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewI
// RTL Support: remove conflicting of right-aligned text and starpixmap!
const int rightTitleEdge = rightPosition - m_padding - starPixmapWidth;
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);
drawHighlightedTextLine(titleRect, title, searchText, painter, style, opt, colorRole);
viewItemDrawText(painter, &opt, titleRect, title, colorRole, searchText);
// Draw link
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 = opt.fontMetrics.elidedText(link, Qt::ElideRight, linkRect.width());
painter->setFont(opt.font);
// Draw url (or switch to tab)
@ -136,10 +136,10 @@ void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewI
QRect textRect(linkRect);
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 {
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
@ -185,150 +185,149 @@ bool LocationCompleterDelegate::drawSwitchToTab() const
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)
{
return s1.size() > s2.size();
}
void LocationCompleterDelegate::drawHighlightedTextLine(const QRect &rect, const QString &text, const QString &searchText,
QPainter* painter, const QStyle* style, const QStyleOptionViewItemV4 &option,
const QPalette::ColorRole &role) const
static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth)
{
QList<int> delimiters;
QStringList searchStrings = searchText.split(QLatin1Char(' '), QString::SkipEmptyParts);
qreal height = 0;
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
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);
}
textLayout.endLayout();
}
return QSizeF(widthUsed, height);
}
// 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);
// most of codes taken from QCommonStylePrivate::viewItemDrawText()
// added highlighting and simplified for single-line textlayouts
void LocationCompleterDelegate::viewItemDrawText(QPainter *p, const QStyleOptionViewItemV4 *option, const QRect &rect,
const QString &text, const QPalette::ColorRole &role, const QString &searchText) const
{
if (text.isEmpty()) {
return;
}
QFont normalFont = painter->font();
QFont boldFont = normalFont;
boldFont.setBold(true);
const QColor &color = option->palette.color(role);
const QWidget* widget = option->widget;
const QStyle* proxyStyle = widget ? widget->style()->proxy() : QApplication::style()->proxy();
const int textMargin = proxyStyle->pixelMetric(QStyle::PM_FocusFrameHMargin, 0, widget) + 1;
QFontMetrics normalMetrics(normalFont);
QFontMetrics boldMetrics(boldFont);
QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
const QFontMetrics fontMetrics(p->font());
QString elidedText = fontMetrics.elidedText(text, option->textElideMode, textRect.width());
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);
int lastEndPos = 0;
int lastRectPos = rect.left();
if (!searchText.isEmpty()) {
QList<int> delimiters;
QStringList searchStrings = searchText.split(QLatin1Char(' '), QString::SkipEmptyParts);
// Look for longer parts first
qSort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan);
while (!delimiters.isEmpty()) {
int start = delimiters.takeFirst();
int end = delimiters.takeFirst();
foreach (const QString &string, searchStrings) {
int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive);
const QString normalPart = text.mid(lastEndPos, start - lastEndPos);
const QString boldPart = text.mid(start, end - start);
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);
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);
if (dStart <= start && dEnd >= end) {
alreadyContains = true;
break;
}
}
if (!alreadyContains) {
delimiters.append(start);
delimiters.append(end);
}
painter->setFont(normalFont);
drawTextLine(nRect, normalPart, painter, style, option, role);
lastRectPos += nRect.width();
delimiter = text.indexOf(string, end, Qt::CaseInsensitive);
}
}
if (!boldPart.isEmpty()) {
int width = boldMetrics.width(boldPart);
QRect bRect = adjustRect(rect, QRect(lastRectPos, rect.top(), width, rect.height()));
// We need to sort delimiters to properly paint all parts that user typed
qSort(delimiters);
if (bRect.width() > 0) {
if (text.isRightToLeft()) {
bRect = style->visualRect(Qt::RightToLeft, rect, bRect);
}
painter->setFont(boldFont);
drawTextLine(bRect, boldPart, painter, style, option, role);
// If we don't find any match, just paint it without any highlight
if (!delimiters.isEmpty() && !(delimiters.count() % 2)) {
QList<QTextLayout::FormatRange> highlightParts;
// 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();
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;
if (delimiters.isEmpty() && lastEndPos != text.size()) {
const QString lastText = text.mid(lastEndPos);
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);
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);
highlightParts << highlightedPart;
}
painter->setFont(normalFont);
drawTextLine(nRect, lastText, painter, style, option, role);
textLayout.setAdditionalFormats(highlightParts);
}
}
}
void LocationCompleterDelegate::drawTextLine(const QRect &rect, QString text, QPainter* painter,
const QStyle* style, const QStyleOptionViewItemV4 &option,
const QPalette::ColorRole &role) const
{
if (rect.width() > 0) {
const Qt::LayoutDirection direction = option.widget ? option.widget->layoutDirection() : QApplication::layoutDirection();
Qt::LayoutDirection textDirection = text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight;
Qt::Alignment alignment = textDirection == direction ? Qt::AlignLeft : Qt::AlignRight;
// do layout
viewItemTextLayout(textLayout, textRect.width());
// Insert unicode control characters: prepend RLE or LRE and append (RLM or LRM)+PDF
text.isRightToLeft() ? text.prepend(QChar(0x202B)).append(0x200F) : text.prepend(QChar(0x202A)).append(0x200E);
text.append(QChar(0x202C));
style->drawItemText(painter, rect, Qt::TextSingleLine | alignment, option.palette, true, text, role);
if (textLayout.lineCount() <= 0) {
return;
}
QTextLine textLine = textLayout.lineAt(0);
// if elidedText after highlighting is longer
// than available width then re-elide it and redo layout
int diff = textLine.naturalTextWidth() - textRect.width();
if (diff > 0) {
elidedText = fontMetrics.elidedText(elidedText, option->textElideMode, textRect.width() - diff);
textLayout.setText(elidedText);
// redo layout
viewItemTextLayout(textLayout, textRect.width());
if (textLayout.lineCount() <= 0) {
return;
}
textLine = textLayout.lineAt(0);
}
// draw line
p->setPen(color);
qreal width = qMax<qreal>(textRect.width(), textLayout.lineAt(0).width());
const QRect &layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, QSize(int(width), int(textLine.height())), textRect);
const QPointF &position = layoutRect.topLeft();
textLine.draw(p, position);
}

View File

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