File indexing completed on 2024-12-22 04:41:07
0001 /* ============================================================ 0002 * Falkon - Qt web browser 0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "locationcompleterdelegate.h" 0019 #include "locationcompletermodel.h" 0020 #include "locationbar.h" 0021 #include "iconprovider.h" 0022 #include "qzsettings.h" 0023 #include "mainapplication.h" 0024 #include "bookmarkitem.h" 0025 0026 #include <algorithm> 0027 0028 #include <QPainter> 0029 #include <QApplication> 0030 #include <QMouseEvent> 0031 #include <QTextLayout> 0032 #include <QtGuiVersion> 0033 0034 LocationCompleterDelegate::LocationCompleterDelegate(QObject *parent) 0035 : QStyledItemDelegate(parent) 0036 , m_rowHeight(0) 0037 , m_padding(0) 0038 { 0039 } 0040 0041 void LocationCompleterDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0042 { 0043 QStyleOptionViewItem opt = option; 0044 initStyleOption(&opt, index); 0045 0046 const QWidget* w = opt.widget; 0047 const QStyle* style = w ? w->style() : QApplication::style(); 0048 0049 const int height = opt.rect.height(); 0050 const int center = height / 2 + opt.rect.top(); 0051 0052 // Prepare link font 0053 QFont linkFont = opt.font; 0054 linkFont.setPointSize(linkFont.pointSize() - 1); 0055 0056 const QFontMetrics linkMetrics(linkFont); 0057 0058 int leftPosition = m_padding * 2; 0059 int rightPosition = opt.rect.right() - m_padding; 0060 0061 opt.state |= QStyle::State_Active; 0062 0063 const QIcon::Mode iconMode = opt.state & QStyle::State_Selected ? QIcon::Selected : QIcon::Normal; 0064 0065 const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; 0066 const QPalette::ColorRole colorLinkRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Link; 0067 0068 #ifdef Q_OS_WIN 0069 opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text)); 0070 opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108)); 0071 #endif 0072 0073 QPalette textPalette = opt.palette; 0074 textPalette.setCurrentColorGroup(opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled); 0075 0076 // Draw background 0077 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w); 0078 0079 const bool isVisitSearchItem = index.data(LocationCompleterModel::VisitSearchItemRole).toBool(); 0080 const bool isSearchSuggestion = index.data(LocationCompleterModel::SearchSuggestionRole).toBool(); 0081 0082 LocationBar::LoadAction loadAction; 0083 bool isWebSearch = isSearchSuggestion; 0084 0085 BookmarkItem *bookmark = static_cast<BookmarkItem*>(index.data(LocationCompleterModel::BookmarkItemRole).value<void*>()); 0086 0087 if (isVisitSearchItem) { 0088 loadAction = LocationBar::loadAction(index.data(LocationCompleterModel::SearchStringRole).toString()); 0089 isWebSearch = loadAction.type == LocationBar::LoadAction::Search; 0090 if (!m_forceVisitItem) { 0091 bookmark = loadAction.bookmark; 0092 } 0093 } 0094 0095 // Draw icon 0096 const int iconSize = 16; 0097 const int iconYPos = center - (iconSize / 2); 0098 QRect iconRect(leftPosition, iconYPos, iconSize, iconSize); 0099 QPixmap pixmap = index.data(Qt::DecorationRole).value<QIcon>().pixmap(iconSize); 0100 if (isSearchSuggestion || (isVisitSearchItem && isWebSearch)) { 0101 pixmap = QIcon::fromTheme(QSL("edit-find"), QIcon(QSL(":icons/menu/search-icon.svg"))).pixmap(iconSize, iconMode); 0102 } 0103 if (isVisitSearchItem && bookmark) { 0104 pixmap = bookmark->icon().pixmap(iconSize); 0105 } else if (loadAction.type == LocationBar::LoadAction::Search) { 0106 if (loadAction.searchEngine.name != LocationBar::searchEngine().name) { 0107 pixmap = loadAction.searchEngine.icon.pixmap(iconSize); 0108 } 0109 } 0110 painter->drawPixmap(iconRect, pixmap); 0111 leftPosition = iconRect.right() + m_padding * 2; 0112 0113 // Draw star to bookmark items 0114 int starPixmapWidth = 0; 0115 if (bookmark) { 0116 const QIcon icon = IconProvider::instance()->bookmarkIcon(); 0117 const QSize starSize(16, 16); 0118 starPixmapWidth = starSize.width(); 0119 QPoint pos(rightPosition - starPixmapWidth, center - starSize.height() / 2); 0120 QRect starRect(pos, starSize); 0121 painter->drawPixmap(starRect, icon.pixmap(starSize, iconMode)); 0122 } 0123 0124 QString searchText = index.data(LocationCompleterModel::SearchStringRole).toString(); 0125 0126 // Draw title 0127 leftPosition += 2; 0128 QRect titleRect(leftPosition, center - opt.fontMetrics.height() / 2, opt.rect.width() * 0.6, opt.fontMetrics.height()); 0129 QString title = index.data(LocationCompleterModel::TitleRole).toString(); 0130 painter->setFont(opt.font); 0131 0132 if (isVisitSearchItem) { 0133 if (bookmark) { 0134 title = bookmark->title(); 0135 } else { 0136 title = index.data(LocationCompleterModel::SearchStringRole).toString(); 0137 searchText.clear(); 0138 } 0139 } 0140 0141 leftPosition += viewItemDrawText(painter, &opt, titleRect, title, textPalette.color(colorRole), searchText); 0142 leftPosition += m_padding * 2; 0143 0144 // Trim link to maximum number of characters that can be visible, otherwise there may be perf issue with huge URLs 0145 const int maxChars = (opt.rect.width() - leftPosition) / opt.fontMetrics.horizontalAdvance(QL1C('i')); 0146 QString link; 0147 const QByteArray linkArray = index.data(Qt::DisplayRole).toByteArray(); 0148 if (!linkArray.startsWith("data") && !linkArray.startsWith("javascript")) { 0149 link = QString::fromUtf8(QByteArray::fromPercentEncoding(linkArray)).left(maxChars); 0150 } else { 0151 link = QString::fromLatin1(linkArray.left(maxChars)); 0152 } 0153 0154 if (isVisitSearchItem || isSearchSuggestion) { 0155 if (!opt.state.testFlag(QStyle::State_Selected) && !opt.state.testFlag(QStyle::State_MouseOver)) { 0156 link.clear(); 0157 } else if (isVisitSearchItem && (!isWebSearch || m_forceVisitItem)) { 0158 link = tr("Visit"); 0159 } else { 0160 QString searchEngineName = loadAction.searchEngine.name; 0161 if (searchEngineName.isEmpty()) { 0162 searchEngineName = LocationBar::searchEngine().name; 0163 } 0164 link = tr("Search with %1").arg(searchEngineName); 0165 } 0166 } 0167 0168 if (bookmark) { 0169 link = bookmark->url().toString(); 0170 } 0171 0172 // Draw separator 0173 if (!link.isEmpty()) { 0174 QChar separator = QL1C('-'); 0175 QRect separatorRect(leftPosition, center - linkMetrics.height() / 2, linkMetrics.horizontalAdvance(separator), linkMetrics.height()); 0176 style->drawItemText(painter, separatorRect, Qt::AlignCenter, textPalette, true, separator, colorRole); 0177 leftPosition += separatorRect.width() + m_padding * 2; 0178 } 0179 0180 // Draw link 0181 const int leftLinkEdge = leftPosition; 0182 const int rightLinkEdge = rightPosition - m_padding - starPixmapWidth; 0183 QRect linkRect(leftLinkEdge, center - linkMetrics.height() / 2, rightLinkEdge - leftLinkEdge, linkMetrics.height()); 0184 painter->setFont(linkFont); 0185 0186 // Draw url (or switch to tab) 0187 int tabPos = index.data(LocationCompleterModel::TabPositionTabRole).toInt(); 0188 0189 if (qzSettings->showSwitchTab && !m_forceVisitItem && tabPos != -1) { 0190 const QIcon tabIcon = QIcon(QSL(":icons/menu/tab.svg")); 0191 QRect iconRect(linkRect); 0192 iconRect.setX(iconRect.x()); 0193 iconRect.setWidth(16); 0194 painter->drawPixmap(iconRect, tabIcon.pixmap(iconRect.size(), iconMode)); 0195 0196 QRect textRect(linkRect); 0197 textRect.setX(textRect.x() + m_padding + 16 + m_padding); 0198 viewItemDrawText(painter, &opt, textRect, tr("Switch to tab"), textPalette.color(colorLinkRole)); 0199 } else if (isVisitSearchItem || isSearchSuggestion) { 0200 viewItemDrawText(painter, &opt, linkRect, link, textPalette.color(colorLinkRole)); 0201 } else { 0202 viewItemDrawText(painter, &opt, linkRect, link, textPalette.color(colorLinkRole), searchText); 0203 } 0204 0205 // Draw line at the very bottom of item if the item is not highlighted 0206 if (!(opt.state & QStyle::State_Selected)) { 0207 QRect lineRect(opt.rect.left(), opt.rect.bottom(), opt.rect.width(), 1); 0208 painter->fillRect(lineRect, opt.palette.color(QPalette::AlternateBase)); 0209 } 0210 } 0211 0212 QSize LocationCompleterDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0213 { 0214 Q_UNUSED(index) 0215 0216 if (!m_rowHeight) { 0217 QStyleOptionViewItem opt(option); 0218 initStyleOption(&opt, index); 0219 0220 const QWidget* w = opt.widget; 0221 const QStyle* style = w ? w->style() : QApplication::style(); 0222 const int padding = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr) + 1; 0223 0224 m_padding = padding > 3 ? padding : 3; 0225 m_rowHeight = 4 * m_padding + qMax(16, opt.fontMetrics.height()); 0226 } 0227 0228 return QSize(200, m_rowHeight); 0229 } 0230 0231 void LocationCompleterDelegate::setForceVisitItem(bool enable) 0232 { 0233 m_forceVisitItem = enable; 0234 } 0235 0236 static bool sizeBiggerThan(const QString &s1, const QString &s2) 0237 { 0238 return s1.size() > s2.size(); 0239 } 0240 0241 static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth) 0242 { 0243 qreal height = 0; 0244 qreal widthUsed = 0; 0245 textLayout.beginLayout(); 0246 QTextLine line = textLayout.createLine(); 0247 if (line.isValid()) { 0248 line.setLineWidth(lineWidth); 0249 line.setPosition(QPointF(0, height)); 0250 height += line.height(); 0251 widthUsed = qMax(widthUsed, line.naturalTextWidth()); 0252 0253 textLayout.endLayout(); 0254 } 0255 return QSizeF(widthUsed, height); 0256 } 0257 0258 // most of codes taken from QCommonStylePrivate::viewItemDrawText() 0259 // added highlighting and simplified for single-line textlayouts 0260 int LocationCompleterDelegate::viewItemDrawText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect, 0261 const QString &text, const QColor &color, const QString &searchText) const 0262 { 0263 if (text.isEmpty()) { 0264 return 0; 0265 } 0266 0267 const QFontMetrics fontMetrics(p->font()); 0268 QString elidedText = fontMetrics.elidedText(text, option->textElideMode, rect.width()); 0269 QTextOption textOption; 0270 textOption.setWrapMode(QTextOption::NoWrap); 0271 textOption.setAlignment(QStyle::visualAlignment(textOption.textDirection(), option->displayAlignment)); 0272 QTextLayout textLayout; 0273 textLayout.setFont(p->font()); 0274 textLayout.setText(elidedText); 0275 textLayout.setTextOption(textOption); 0276 0277 if (!searchText.isEmpty()) { 0278 QList<int> delimiters; 0279 QStringList searchStrings = searchText.split(QLatin1Char(' '), Qt::SkipEmptyParts); 0280 // Look for longer parts first 0281 std::sort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan); 0282 0283 for (const QString &string : std::as_const(searchStrings)) { 0284 int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive); 0285 0286 while (delimiter != -1) { 0287 int start = delimiter; 0288 int end = delimiter + string.length(); 0289 bool alreadyContains = false; 0290 for (int i = 0; i < delimiters.count(); ++i) { 0291 int dStart = delimiters.at(i); 0292 int dEnd = delimiters.at(++i); 0293 0294 if (dStart <= start && dEnd >= end) { 0295 alreadyContains = true; 0296 break; 0297 } 0298 } 0299 if (!alreadyContains) { 0300 delimiters.append(start); 0301 delimiters.append(end); 0302 } 0303 0304 delimiter = text.indexOf(string, end, Qt::CaseInsensitive); 0305 } 0306 } 0307 0308 // We need to sort delimiters to properly paint all parts that user typed 0309 std::sort(delimiters.begin(), delimiters.end()); 0310 0311 // If we don't find any match, just paint it without any highlight 0312 if (!delimiters.isEmpty() && !(delimiters.count() % 2)) { 0313 QList<QTextLayout::FormatRange> highlightParts; 0314 0315 while (!delimiters.isEmpty()) { 0316 QTextLayout::FormatRange highlightedPart; 0317 int start = delimiters.takeFirst(); 0318 int end = delimiters.takeFirst(); 0319 highlightedPart.start = start; 0320 highlightedPart.length = end - start; 0321 highlightedPart.format.setFontWeight(QFont::Bold); 0322 highlightedPart.format.setUnderlineStyle(QTextCharFormat::SingleUnderline); 0323 0324 highlightParts << highlightedPart; 0325 } 0326 0327 textLayout.setFormats(highlightParts); 0328 } 0329 } 0330 0331 // do layout 0332 viewItemTextLayout(textLayout, rect.width()); 0333 0334 if (textLayout.lineCount() <= 0) { 0335 return 0; 0336 } 0337 0338 QTextLine textLine = textLayout.lineAt(0); 0339 0340 // if elidedText after highlighting is longer 0341 // than available width then re-elide it and redo layout 0342 int diff = textLine.naturalTextWidth() - rect.width(); 0343 if (diff > 0) { 0344 elidedText = fontMetrics.elidedText(elidedText, option->textElideMode, rect.width() - diff); 0345 0346 textLayout.setText(elidedText); 0347 // redo layout 0348 viewItemTextLayout(textLayout, rect.width()); 0349 0350 if (textLayout.lineCount() <= 0) { 0351 return 0; 0352 } 0353 textLine = textLayout.lineAt(0); 0354 } 0355 0356 // draw line 0357 p->setPen(color); 0358 qreal width = qMax<qreal>(rect.width(), textLayout.lineAt(0).width()); 0359 const QRect &layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, QSize(int(width), int(textLine.height())), rect); 0360 const QPointF &position = layoutRect.topLeft(); 0361 0362 textLine.draw(p, position); 0363 0364 return qMin<int>(rect.width(), textLayout.lineAt(0).naturalTextWidth()); 0365 }