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 }