File indexing completed on 2024-05-19 04:59:21

0001 /* ============================================================
0002 * VerticalTabs plugin for Falkon
0003 * Copyright (C) 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 "tabtreedelegate.h"
0019 #include "tabtreeview.h"
0020 #include "loadinganimator.h"
0021 
0022 #include "tabmodel.h"
0023 #include "tabicon.h"
0024 
0025 #include <QTabBar>
0026 #include <QPainter>
0027 #include <QPushButton>
0028 #include <QApplication>
0029 
0030 // TabTreeCloseButton
0031 TabTreeCloseButton::TabTreeCloseButton(QWidget *parent)
0032     : QAbstractButton(parent)
0033 {
0034     int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, this);
0035     int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, this);
0036     resize(width, height);
0037 }
0038 
0039 int TabTreeCloseButton::showOnNormal() const
0040 {
0041     return m_showOnNormal;
0042 }
0043 
0044 void TabTreeCloseButton::setShowOnNormal(int show)
0045 {
0046     m_showOnNormal = show;
0047 }
0048 
0049 int TabTreeCloseButton::showOnHovered() const
0050 {
0051     return m_showOnHovered;
0052 }
0053 
0054 void TabTreeCloseButton::setShowOnHovered(int show)
0055 {
0056     m_showOnHovered = show;
0057 }
0058 
0059 int TabTreeCloseButton::showOnSelected() const
0060 {
0061     return m_showOnSelected;
0062 }
0063 
0064 void TabTreeCloseButton::setShowOnSelected(int show)
0065 {
0066     m_showOnSelected = show;
0067 }
0068 
0069 bool TabTreeCloseButton::isVisible(bool hovered, bool selected) const
0070 {
0071     if (hovered && selected) {
0072         return m_showOnHovered || m_showOnSelected;
0073     } else if (selected) {
0074         return m_showOnSelected;
0075     } else if (hovered) {
0076         return m_showOnHovered;
0077     } else {
0078         return m_showOnNormal;
0079     }
0080 }
0081 
0082 void TabTreeCloseButton::paintEvent(QPaintEvent *)
0083 {
0084 }
0085 
0086 // TabTreeDelegate
0087 TabTreeDelegate::TabTreeDelegate(TabTreeView *view)
0088     : QStyledItemDelegate()
0089     , m_view(view)
0090 {
0091     m_padding = qMax(5, m_view->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1);
0092     m_indentation = 15;
0093 
0094     m_loadingAnimator = new LoadingAnimator(this);
0095     connect(m_loadingAnimator, &LoadingAnimator::updateIndex, m_view, &TabTreeView::updateIndex);
0096 
0097     // Needed to make it stylable the same way as real tabbar close button
0098     auto *tabBar = new QTabBar(m_view);
0099     tabBar->setObjectName(QSL("tabtree_tabbar"));
0100     tabBar->lower();
0101 
0102     m_closeButton = new TabTreeCloseButton(tabBar);
0103     m_closeButton->lower();
0104 }
0105 
0106 static int indexDepth(QModelIndex index)
0107 {
0108     int i = 0;
0109     while (index.parent().isValid()) {
0110         index = index.parent();
0111         i++;
0112     }
0113     return i;
0114 }
0115 
0116 QRect TabTreeDelegate::expandButtonRect(const QModelIndex &index) const
0117 {
0118     const QRect rect = m_view->visualRect(index);
0119     const int depth = indexDepth(index);
0120     return QRect(m_indentation * depth, rect.y(), m_indentation, rect.height());
0121 }
0122 
0123 QRect TabTreeDelegate::audioButtonRect(const QModelIndex &index) const
0124 {
0125     if (!index.data(TabModel::AudioPlayingRole).toBool() && !index.data(TabModel::AudioMutedRole).toBool()) {
0126         return QRect();
0127     }
0128     const QRect rect = m_view->visualRect(index);
0129     const int center = rect.height() / 2 + rect.top();
0130     const int rightPosition = rect.right() - m_padding * 2 - 16;
0131     return QRect(rightPosition - 16, center - 16 / 2, 16, 16);
0132 }
0133 
0134 QRect TabTreeDelegate::closeButtonRect(const QModelIndex &index) const
0135 {
0136     const QRect rect = m_view->visualRect(index);
0137     const int center = rect.height() / 2 + rect.top();
0138     QSize size = m_closeButton->size();
0139     size.setHeight(qMin(rect.height() - m_padding, size.height()));
0140     return QRect(QPoint(rect.right() - m_padding - size.width(), center - size.height() / 2), size);
0141 }
0142 
0143 void TabTreeDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0144 {
0145     const QWidget *w = option.widget;
0146     const QStyle *style = w ? w->style() : m_view->style();
0147 
0148     const bool expanded = m_view->isExpanded(index);
0149     const bool children = m_view->model()->rowCount(index) > 0;
0150     const int depth = indexDepth(index);
0151     const bool isRestoredTab = index.data(TabModel::RestoredRole).toBool();
0152 
0153     QStyleOptionViewItem opt = option;
0154     initStyleOption(&opt, index);
0155     m_view->adjustStyleOption(&opt);
0156 
0157     const int height = opt.rect.height();
0158     const int center = height / 2 + opt.rect.top();
0159 
0160     int leftPosition = opt.rect.left() + m_indentation + m_indentation * depth + m_padding;
0161     int rightPosition = opt.rect.right() - m_padding * 2 - m_closeButton->size().width();
0162 
0163     const QPalette::ColorRole colorRole = opt.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text;
0164 
0165     QPalette::ColorGroup cg = opt.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled;
0166     if (!isRestoredTab) {
0167         cg = QPalette::Disabled;
0168     }
0169     if (cg == QPalette::Normal && !(opt.state & QStyle::State_Active)) {
0170         cg = QPalette::Inactive;
0171     }
0172 
0173 #ifdef Q_OS_WIN
0174     opt.palette.setColor(QPalette::All, QPalette::HighlightedText, opt.palette.color(QPalette::Active, QPalette::Text));
0175     opt.palette.setColor(QPalette::All, QPalette::Highlight, opt.palette.base().color().darker(108));
0176 #endif
0177 
0178     QPalette textPalette = opt.palette;
0179     textPalette.setCurrentColorGroup(cg);
0180 
0181     const bool hovered = opt.state.testFlag(QStyle::State_MouseOver);
0182     const bool selected = opt.state.testFlag(QStyle::State_Selected);
0183 
0184     // Draw background
0185     if (m_view->backgroundIndentation()) {
0186         opt.rect.moveLeft(m_indentation * depth);
0187         opt.rect.setWidth(opt.rect.width() - m_indentation * depth);
0188     }
0189     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, w);
0190 
0191     // Draw expand button
0192     if (children) {
0193         QStyleOptionViewItem o = opt;
0194         o.state &= ~QStyle::State_MouseOver;
0195         o.rect.moveLeft(m_indentation * depth);
0196         o.rect.setWidth(m_indentation);
0197         style->drawPrimitive(expanded ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight, &o, painter, w);
0198     }
0199 
0200     // Draw icon
0201     const int iconSize = 16;
0202     const int iconYPos = center - (iconSize / 2);
0203     QRect iconRect(leftPosition, iconYPos, iconSize, iconSize);
0204     QPixmap pixmap;
0205     if (index.data(TabModel::LoadingRole).toBool()) {
0206         pixmap = m_loadingAnimator->pixmap(index);
0207     } else {
0208         pixmap = index.data(Qt::DecorationRole).value<QIcon>().pixmap(iconSize);
0209     }
0210     painter->drawPixmap(iconRect, pixmap);
0211     leftPosition += iconRect.width() + m_padding;
0212 
0213     // Draw close button
0214     if (m_closeButton->isVisible(hovered, selected)) {
0215         QStyleOptionButton o;
0216         o.initFrom(m_closeButton);
0217 
0218         const bool hovered = closeButtonRect(index).contains(m_view->viewport()->mapFromGlobal(QCursor::pos()));
0219         const bool pressed = hovered && QApplication::mouseButtons() == Qt::LeftButton;
0220 
0221         QSize closeSize = QSize(o.rect.size().width(), qMin(height - m_padding, o.rect.size().height()));
0222         QPoint pos(opt.rect.right() - m_padding - closeSize.width(), center - closeSize.height() / 2);
0223         o.rect = QRect(pos, closeSize);
0224         o.state |= QStyle::State_AutoRaise | QStyle::State_Enabled | QStyle::State_Selected;
0225         o.state.setFlag(QStyle::State_Raised, hovered && !pressed);
0226         o.state.setFlag(QStyle::State_Sunken, pressed);
0227         o.state.setFlag(QStyle::State_MouseOver, hovered);
0228         style->drawPrimitive(QStyle::PE_IndicatorTabClose, &o, painter, m_closeButton);
0229     }
0230 
0231     // Draw audio icon
0232     const bool audioMuted = index.data(TabModel::AudioMutedRole).toBool();
0233     const bool audioPlaying = index.data(TabModel::AudioPlayingRole).toBool();
0234     if (audioMuted || audioPlaying) {
0235         QSize audioSize(16, 16);
0236         QPoint pos(rightPosition - audioSize.width(), center - audioSize.height() / 2);
0237         QRect audioRect(pos, audioSize);
0238         painter->drawPixmap(audioRect, audioMuted ? TabIcon::data()->audioMutedPixmap : TabIcon::data()->audioPlayingPixmap);
0239         rightPosition -= audioSize.width() + m_padding;
0240     }
0241 
0242     // Draw title
0243     QRect titleRect(leftPosition, center - opt.fontMetrics.height() / 2, opt.rect.width(), opt.fontMetrics.height());
0244     titleRect.setRight(rightPosition - m_padding);
0245     QString title = opt.fontMetrics.elidedText(index.data().toString(), Qt::ElideRight, titleRect.width());
0246     style->drawItemText(painter, titleRect, Qt::AlignLeft, textPalette, true, title, colorRole);
0247 }
0248 
0249 QSize TabTreeDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0250 {
0251     QStyleOptionViewItem opt(option);
0252     initStyleOption(&opt, index);
0253 
0254     return QSize(200, m_padding * 2 + opt.fontMetrics.height());
0255 }