File indexing completed on 2024-05-19 04:59:19
0001 /* ============================================================ 0002 * Falkon - Qt web browser 0003 * Copyright (C) 2016-2017 S. Razi Alavizadeh <s.r.alavizadeh@gmail.com> 0004 * Copyright (C) 2017 David Rosca <nowrep@gmail.com> 0005 * 0006 * This program is free software: you can redistribute it and/or modify 0007 * it under the terms of the GNU General Public License as published by 0008 * the Free Software Foundation, either version 3 of the License, or 0009 * (at your option) any later version. 0010 * 0011 * This program is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0014 * GNU General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU General Public License 0017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0018 * ============================================================ */ 0019 #include "tabmanagerdelegate.h" 0020 #include "tabmanagerwidget.h" 0021 0022 #include <QPainter> 0023 #include <QApplication> 0024 #include <QTextLayout> 0025 0026 TabManagerDelegate::TabManagerDelegate(QObject* parent) 0027 : QStyledItemDelegate(parent) 0028 { 0029 } 0030 0031 // most of codes taken from QCommonStyle::drawControl() and add our custom text drawer 0032 void TabManagerDelegate::paint(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const 0033 { 0034 QStyleOptionViewItem opt = option; 0035 initStyleOption(&opt, index); 0036 0037 const QWidget* w = opt.widget; 0038 const QStyle* style = w ? w->style() : QApplication::style(); 0039 const Qt::LayoutDirection direction = w ? w->layoutDirection() : QApplication::layoutDirection(); 0040 const bool isActiveOrCaption = index.data(TabItem::ActiveOrCaptionRole).toBool(); 0041 const bool isSavedTab = index.data(TabItem::SavedRole).toBool(); 0042 0043 const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; 0044 0045 QPalette::ColorGroup cg = (opt.state & QStyle::State_Enabled) && !isSavedTab ? QPalette::Normal : QPalette::Disabled; 0046 if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) { 0047 cg = QPalette::Inactive; 0048 } 0049 0050 #ifdef Q_OS_WIN 0051 opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text)); 0052 opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108)); 0053 #endif 0054 0055 QPalette textPalette = opt.palette; 0056 textPalette.setCurrentColorGroup(cg); 0057 0058 painter->save(); 0059 painter->setClipRect(opt.rect); 0060 0061 QRect checkRect = style->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt, w); 0062 QRect iconRect = style->subElementRect(QStyle::SE_ItemViewItemDecoration, &opt, w); 0063 QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt, w); 0064 0065 // draw the background 0066 style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w); 0067 0068 // draw close button 0069 if (index.column() == 1) { 0070 if (opt.state & QStyle::State_MouseOver) { 0071 static const int buttonSize = 16; 0072 static const QPixmap closeTabButton(QStringLiteral(":tabmanager/data/closetab.png")); 0073 static const QPixmap addTabButton(QStringLiteral(":tabmanager/data/addtab.png")); 0074 0075 const QRect rect(opt.rect.right() - buttonSize, (opt.rect.height() - buttonSize) / 2 + opt.rect.y(), buttonSize, buttonSize); 0076 painter->drawPixmap(style->visualRect(direction, opt.rect, rect), (index.parent().isValid() ? closeTabButton : addTabButton)); 0077 } 0078 0079 painter->restore(); 0080 return; 0081 } 0082 0083 // draw the check mark 0084 if (opt.features & QStyleOptionViewItem::HasCheckIndicator) { 0085 QStyleOptionViewItem opt2(opt); 0086 opt2.rect = checkRect; 0087 opt2.state = opt2.state & ~QStyle::State_HasFocus; 0088 0089 switch (opt.checkState) { 0090 case Qt::Unchecked: 0091 opt2.state |= QStyle::State_Off; 0092 break; 0093 case Qt::PartiallyChecked: 0094 opt2.state |= QStyle::State_NoChange; 0095 break; 0096 case Qt::Checked: 0097 opt2.state |= QStyle::State_On; 0098 break; 0099 } 0100 style->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &opt2, painter, w); 0101 } 0102 0103 // draw the icon 0104 QIcon::Mode mode = QIcon::Normal; 0105 if (!(opt.state & QStyle::State_Enabled)) 0106 mode = QIcon::Disabled; 0107 else if (opt.state & QStyle::State_Selected) 0108 mode = QIcon::Selected; 0109 QIcon::State state = opt.state & QStyle::State_Open ? QIcon::On : QIcon::Off; 0110 opt.icon.paint(painter, iconRect, opt.decorationAlignment, mode, state); 0111 0112 // draw the text 0113 if (!opt.text.isEmpty()) { 0114 const QString filterText = property("filterText").toString(); 0115 0116 if (opt.state & QStyle::State_Selected) { 0117 painter->setPen(opt.palette.color(cg, QPalette::HighlightedText)); 0118 } else { 0119 painter->setPen(opt.palette.color(cg, QPalette::Text)); 0120 } 0121 if (opt.state & QStyle::State_Editing) { 0122 painter->setPen(opt.palette.color(cg, QPalette::Text)); 0123 painter->drawRect(textRect.adjusted(0, 0, -1, -1)); 0124 } 0125 0126 if (isSavedTab) 0127 opt.font.setItalic(true); 0128 else if (isActiveOrCaption) 0129 opt.font.setBold(true); 0130 0131 painter->setFont(opt.font); 0132 viewItemDrawText(painter, &opt, textRect, opt.text, textPalette.color(colorRole), filterText); 0133 } 0134 0135 painter->restore(); 0136 } 0137 0138 static bool sizeBiggerThan(const QString &s1, const QString &s2) 0139 { 0140 return s1.size() > s2.size(); 0141 } 0142 0143 static QSizeF viewItemTextLayout(QTextLayout &textLayout, int lineWidth) 0144 { 0145 qreal height = 0; 0146 qreal widthUsed = 0; 0147 textLayout.beginLayout(); 0148 QTextLine line = textLayout.createLine(); 0149 if (line.isValid()) { 0150 line.setLineWidth(lineWidth); 0151 line.setPosition(QPointF(0, height)); 0152 height += line.height(); 0153 widthUsed = qMax(widthUsed, line.naturalTextWidth()); 0154 0155 textLayout.endLayout(); 0156 } 0157 return QSizeF(widthUsed, height); 0158 } 0159 0160 // most of codes taken from QCommonStylePrivate::viewItemDrawText() 0161 // added highlighting and simplified for single-line textlayouts 0162 void TabManagerDelegate::viewItemDrawText(QPainter *p, const QStyleOptionViewItem *option, const QRect &rect, 0163 const QString &text, const QColor &color, const QString &searchText) const 0164 { 0165 if (text.isEmpty()) { 0166 return; 0167 } 0168 0169 const QWidget* widget = option->widget; 0170 const bool isRtlLayout = widget ? widget->isRightToLeft() : QApplication::isRightToLeft(); 0171 const QStyle* proxyStyle = widget ? widget->style()->proxy() : QApplication::style()->proxy(); 0172 const int textMargin = proxyStyle->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr, widget) + 1; 0173 0174 QRect textRect = rect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding 0175 const QFontMetrics fontMetrics(p->font()); 0176 QString elidedText = fontMetrics.elidedText(text, option->textElideMode, textRect.width()); 0177 QTextOption textOption; 0178 textOption.setWrapMode(QTextOption::NoWrap); 0179 textOption.setTextDirection(text.isRightToLeft() ? Qt::RightToLeft : Qt::LeftToRight); 0180 textOption.setAlignment(Qt::AlignVCenter | (isRtlLayout ? Qt::AlignRight : Qt::AlignLeft)); 0181 QTextLayout textLayout; 0182 textLayout.setFont(p->font()); 0183 textLayout.setText(elidedText); 0184 textLayout.setTextOption(textOption); 0185 0186 if (!searchText.isEmpty()) { 0187 QList<int> delimiters; 0188 QStringList searchStrings = searchText.split(QLatin1Char(' '), Qt::SkipEmptyParts); 0189 // Look for longer parts first 0190 std::sort(searchStrings.begin(), searchStrings.end(), sizeBiggerThan); 0191 0192 for (const QString &string : std::as_const(searchStrings)) { 0193 int delimiter = text.indexOf(string, 0, Qt::CaseInsensitive); 0194 0195 while (delimiter != -1) { 0196 int start = delimiter; 0197 int end = delimiter + string.length(); 0198 bool alreadyContains = false; 0199 for (int i = 0; i < delimiters.count(); ++i) { 0200 int dStart = delimiters.at(i); 0201 int dEnd = delimiters.at(++i); 0202 0203 if (dStart <= start && dEnd >= end) { 0204 alreadyContains = true; 0205 break; 0206 } 0207 } 0208 if (!alreadyContains) { 0209 delimiters.append(start); 0210 delimiters.append(end); 0211 } 0212 0213 delimiter = text.indexOf(string, end, Qt::CaseInsensitive); 0214 } 0215 } 0216 0217 // We need to sort delimiters to properly paint all parts that user typed 0218 std::sort(delimiters.begin(), delimiters.end()); 0219 0220 // If we don't find any match, just paint it without any highlight 0221 if (!delimiters.isEmpty() && !(delimiters.count() % 2)) { 0222 QList<QTextLayout::FormatRange> highlightParts; 0223 0224 QTextLayout::FormatRange lighterWholeLine; 0225 lighterWholeLine.start = 0; 0226 lighterWholeLine.length = elidedText.size(); 0227 QColor lighterColor = color.lighter(130); 0228 if (lighterColor == color) { 0229 lighterColor = QColor(Qt::gray).darker(180); 0230 } 0231 lighterWholeLine.format.setForeground(lighterColor); 0232 highlightParts << lighterWholeLine; 0233 0234 while (!delimiters.isEmpty()) { 0235 QTextLayout::FormatRange highlightedPart; 0236 int start = delimiters.takeFirst(); 0237 int end = delimiters.takeFirst(); 0238 highlightedPart.start = start; 0239 highlightedPart.length = end - start; 0240 highlightedPart.format.setFontWeight(QFont::Bold); 0241 highlightedPart.format.setUnderlineStyle(QTextCharFormat::SingleUnderline); 0242 highlightedPart.format.setForeground(color); 0243 0244 highlightParts << highlightedPart; 0245 } 0246 0247 textLayout.setFormats(highlightParts); 0248 } 0249 } 0250 0251 // do layout 0252 viewItemTextLayout(textLayout, textRect.width()); 0253 0254 if (textLayout.lineCount() <= 0) { 0255 return; 0256 } 0257 0258 QTextLine textLine = textLayout.lineAt(0); 0259 0260 // if elidedText after highlighting is longer 0261 // than available width then re-elide it and redo layout 0262 int diff = textLine.naturalTextWidth() - textRect.width(); 0263 if (diff > 0) { 0264 elidedText = fontMetrics.elidedText(elidedText, option->textElideMode, textRect.width() - diff); 0265 0266 textLayout.setText(elidedText); 0267 // redo layout 0268 viewItemTextLayout(textLayout, textRect.width()); 0269 0270 if (textLayout.lineCount() <= 0) { 0271 return; 0272 } 0273 textLine = textLayout.lineAt(0); 0274 } 0275 0276 // draw line 0277 p->setPen(color); 0278 qreal width = qMax<qreal>(textRect.width(), textLayout.lineAt(0).width()); 0279 const QRect &layoutRect = QStyle::alignedRect(option->direction, option->displayAlignment, QSize(int(width), int(textLine.height())), textRect); 0280 const QPointF &position = layoutRect.topLeft(); 0281 0282 textLine.draw(p, position); 0283 }