File indexing completed on 2024-05-05 04:49:28

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0003  * Copyright (c) 2008 Mark Kretschmann <kretschmann@kde.org>                            *
0004  * Copyright (c) 2009 Seb Ruiz <ruiz@kde.org>                                           *
0005  * Copyright (c) 2013 Ralf Engels <ralf-engels@gmx.de>                                  *
0006  *                                                                                      *
0007  * This program is free software; you can redistribute it and/or modify it under        *
0008  * the terms of the GNU General Public License as published by the Free Software        *
0009  * Foundation; either version 2 of the License, or (at your option) any later           *
0010  * version.                                                                             *
0011  *                                                                                      *
0012  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0013  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0014  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0015  *                                                                                      *
0016  * You should have received a copy of the GNU General Public License along with         *
0017  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0018  ****************************************************************************************/
0019 
0020 #include "PrettyTreeDelegate.h"
0021 
0022 #include "App.h"
0023 #include "widgets/PrettyTreeRoles.h"
0024 #include "widgets/PrettyTreeView.h"
0025 
0026 #include <KCapacityBar>
0027 #include <KFormat>
0028 #include <KLocalizedString>
0029 
0030 #include <QAction>
0031 #include <QApplication>
0032 #include <QFontMetrics>
0033 #include <QIcon>
0034 #include <QPainter>
0035 #include <QTreeView>
0036 #include <QHeaderView>
0037 
0038 Q_DECLARE_METATYPE( QAction* )
0039 Q_DECLARE_METATYPE( QList<QAction*> )
0040 
0041 #define CAPACITYRECT_MIN_HEIGHT 12
0042 #define CAPACITYRECT_MAX_HEIGHT 18
0043 
0044 using namespace Amarok;
0045 
0046 /**
0047  * A structure to hold some pixel metrics from style, to allow code sharing
0048  */
0049 struct PixelSizes
0050 {
0051     explicit PixelSizes( const QStyle *style )
0052         : verticalSpace( qMax( style->pixelMetric( QStyle::PM_LayoutVerticalSpacing ), 1 ) )
0053         , largeIconSize( style->pixelMetric( QStyle::PM_LargeIconSize ) )
0054         , expanderIconSize( style->pixelMetric( QStyle::PM_MenuButtonIndicator ) )
0055         , smallIconSize( style->pixelMetric( QStyle::PM_ListViewIconSize ) )
0056         , frameHMargin( style->pixelMetric( QStyle::PM_FocusFrameHMargin ) )
0057         , frameVMargin( style->pixelMetric( QStyle::PM_FocusFrameVMargin ) )
0058         , frameExtraMargin( largeIconSize / 4 ) // to give top items a little more space
0059         , iconSpacing( style->pixelMetric( QStyle::PM_FocusFrameHMargin ) )
0060     {}
0061 
0062     const int verticalSpace;
0063     const int largeIconSize;
0064     const int expanderIconSize;
0065     const int smallIconSize;
0066     const int frameHMargin;
0067     const int frameVMargin;
0068     const int frameExtraMargin;
0069     const int iconSpacing;
0070 };
0071 
0072 PrettyTreeDelegate::PrettyTreeDelegate( PrettyTreeView *view )
0073     : QStyledItemDelegate( view )
0074     , m_view( view )
0075     , m_normalFm( nullptr )
0076     , m_bigFm( nullptr )
0077     , m_smallFm( nullptr )
0078 {
0079     Q_ASSERT( m_view );
0080 }
0081 
0082 PrettyTreeDelegate::~PrettyTreeDelegate()
0083 {
0084     delete m_normalFm;
0085     delete m_bigFm;
0086     delete m_smallFm;
0087 }
0088 
0089 void
0090 PrettyTreeDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option,
0091                            const QModelIndex &index ) const
0092 {
0093     const bool hasCover = index.data( PrettyTreeRoles::HasCoverRole ).toBool();
0094 
0095     if( hasCover ||
0096         index.parent().isValid() ) // not a root item
0097     {
0098         QStyledItemDelegate::paint( painter, option, index );
0099         return;
0100     }
0101 
0102     updateFonts( option );
0103     QStyle *style = m_view->style();
0104 
0105     PixelSizes s( style );
0106     const bool isRTL = QApplication::isRightToLeft();
0107     const bool hasCapacity = index.data( PrettyTreeRoles::HasCapacityRole ).toBool();
0108     const int actionCount = index.data( PrettyTreeRoles::DecoratorRoleCount ).toInt();
0109 
0110     QRect remainingRect( option.rect );
0111     remainingRect.adjust( s.frameHMargin,   s.frameVMargin + s.frameExtraMargin,
0112                           -s.frameHMargin, -s.frameVMargin - s.frameExtraMargin );
0113 
0114     painter->save();
0115     style->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter );
0116 
0117     if ( option.state & QStyle::State_Selected )
0118         painter->setPen( pApp->palette().highlightedText().color() );
0119     else
0120         painter->setPen( pApp->palette().text().color() );
0121 
0122     painter->setRenderHint( QPainter::Antialiasing );
0123 
0124     // -- the icon
0125     const int iconYPadding = ( remainingRect.height() - s.largeIconSize ) / 2;
0126     QPoint iconPos( remainingRect.topLeft() + QPoint( 0, iconYPadding ) );
0127     if( isRTL )
0128         iconPos.setX( remainingRect.right() - s.largeIconSize );
0129 
0130     painter->drawPixmap( iconPos,
0131                          index.data( Qt::DecorationRole ).value<QIcon>().pixmap( s.largeIconSize, s.largeIconSize ) );
0132 
0133     if( isRTL )
0134         remainingRect.adjust( 0, 0, - s.largeIconSize - s.iconSpacing, 0 );
0135     else
0136         remainingRect.adjust( s.largeIconSize + s.iconSpacing, 0, 0, 0 );
0137 
0138     // -- expander option (the small arrow)
0139     QStyleOption expanderOption( option );
0140     expanderOption.rect = remainingRect;
0141     QStyle::PrimitiveElement expandedPrimitive;
0142     if( isRTL )
0143     {
0144         expandedPrimitive = QStyle::PE_IndicatorArrowRight;
0145     }
0146     else
0147     {
0148         expandedPrimitive = QStyle::PE_IndicatorArrowLeft;
0149         expanderOption.rect.setLeft( expanderOption.rect.right() - s.expanderIconSize );
0150     }
0151 
0152     expanderOption.rect.setWidth( s.expanderIconSize );
0153     if( m_view->model()->hasChildren( index ) )
0154     {
0155         if( m_view->isExpanded( index ) )
0156             style->drawPrimitive( QStyle::PE_IndicatorArrowDown, &expanderOption, painter );
0157         else
0158             style->drawPrimitive( expandedPrimitive, &expanderOption, painter );
0159     }
0160 
0161     // always subtract the expander size in order to align all the rest
0162     if( isRTL )
0163         remainingRect.adjust( s.expanderIconSize + s.iconSpacing, 0, 0, 0 );
0164     else
0165         remainingRect.adjust( 0, 0, - s.expanderIconSize - s.iconSpacing, 0 );
0166 
0167     // -- title
0168     QRect titleRect( remainingRect );
0169     titleRect.setHeight( qMax( m_bigFm->height(), s.smallIconSize ) + s.verticalSpace );
0170 
0171     QString titleText = index.data( Qt::DisplayRole ).toString();
0172     titleText = m_bigFm->elidedText( titleText, Qt::ElideRight, titleRect.width() );
0173 
0174     painter->setFont( m_bigFont );
0175     painter->drawText( titleRect, titleText );
0176 
0177     const bool isHover = option.state & QStyle::State_MouseOver;
0178 
0179     // -- actions icons
0180     if( isHover && ( actionCount > 0 ) )
0181     {
0182         const QList<QAction*> actions =
0183                 index.data( PrettyTreeRoles::DecoratorRole ).value<QList<QAction*> >();
0184 
0185         /* The views that have models with action icons set mouse tracking to true, their
0186          * mouseMoveEvent calls update() which triggers repaints if the mouse moves so
0187          * that we always get updated mouse positions */
0188         QPoint cursorPos = m_view->viewport()->mapFromGlobal( QCursor::pos() );
0189         QAction *pressedDecoratorAction = m_view->pressedDecoratorAction();
0190 
0191         for( int i = 0; i < actions.count(); i++ )
0192         {
0193             QRect iconRect( decoratorRect( option.rect, i ) );
0194             QIcon::State state = ( actions.at( i ) == pressedDecoratorAction ) ? QIcon::On : QIcon::Off;
0195             QIcon::Mode mode;
0196             if( pressedDecoratorAction ) // if currently inside mouse press
0197             {
0198                 mode = ( state == QIcon::On ) ? QIcon::Active : QIcon::Normal;
0199                 const int shrinkBy = iconRect.contains( cursorPos ) && state == QIcon::On
0200                                    ? iconRect.width() / 16 : 0;
0201                 iconRect.adjust( shrinkBy, shrinkBy,  -2 * shrinkBy, -2 * shrinkBy );
0202             }
0203             else
0204                 mode = iconRect.contains( cursorPos ) ? QIcon::Active : QIcon::Normal;
0205             actions[i]->icon().paint( painter, iconRect, Qt::AlignCenter, mode, state );
0206         }
0207     }
0208 
0209     painter->setFont( m_smallFont );  // we want smaller font for both subtitle and capacity bar
0210     //show the bylinetext or the capacity (if available) when hovering
0211     if( isHover && hasCapacity )
0212     {
0213         qreal bytesUsed = index.data( PrettyTreeRoles::UsedCapacityRole ).toReal();
0214         qreal bytesTotal = index.data( PrettyTreeRoles::TotalCapacityRole ).toReal();
0215         const int percentage = (bytesTotal > 0.0) ? qRound( 100.0 * bytesUsed / bytesTotal ) : 100;
0216 
0217         KCapacityBar capacityBar( KCapacityBar::DrawTextInline );
0218         capacityBar.setValue( percentage );
0219         capacityBar.setText( i18nc( "Example: 3.5 GB free (unit is part of %1)", "%1 free",
0220                                     KFormat().formatByteSize( bytesTotal - bytesUsed ) ) );
0221 
0222         QRect capacityRect( remainingRect );
0223         capacityRect.setTop( titleRect.bottom() );
0224         capacityRect.setHeight( qBound( CAPACITYRECT_MIN_HEIGHT,
0225                                         capacityBar.minimumSizeHint().height(),
0226                                         qMin( CAPACITYRECT_MAX_HEIGHT, capacityRect.height() ) ) );
0227 
0228         capacityBar.drawCapacityBar( painter, capacityRect );
0229     }
0230     else
0231     {
0232         QRectF textRect( remainingRect );
0233         textRect.setTop( titleRect.bottom() );
0234         textRect.setHeight( remainingRect.height() - titleRect.height() );
0235 
0236         QString byLineText = index.data( PrettyTreeRoles::ByLineRole ).toString();
0237         byLineText = m_smallFm->elidedText( byLineText, Qt::ElideRight, textRect.width() );
0238 
0239         painter->drawText( textRect, byLineText );
0240     }
0241 
0242     painter->restore();
0243 }
0244 
0245 QSize
0246 PrettyTreeDelegate::sizeHint( const QStyleOptionViewItem &option,
0247                               const QModelIndex &index ) const
0248 {
0249     // note: the QStyledItemDelegage::sizeHint seems to be extremely slow. don't call it
0250 
0251     updateFonts( option );
0252     QStyle *style = m_view->style();
0253 
0254     PixelSizes s( style );
0255     int viewportWidth = m_view->viewport()->width();
0256     int normalHeight = s.frameVMargin + qMax( s.smallIconSize, m_normalFm->height() ) + s.frameVMargin;
0257 
0258     const bool hasCover = index.data( PrettyTreeRoles::HasCoverRole ).toBool();
0259 
0260     // -- determine if we have an album
0261     if( hasCover )
0262         return QSize( viewportWidth, qMax( normalHeight, s.largeIconSize + s.frameVMargin * 2 ) );
0263 
0264     // -- not top level. this is a normal item
0265     if( index.parent().isValid() )
0266         return QSize( viewportWidth, normalHeight );
0267 
0268     // -- ok, we have a top level item
0269     const bool hasCapacity = index.data( PrettyTreeRoles::HasCapacityRole ).toBool();
0270 
0271     QSize iconSize( s.largeIconSize, s.largeIconSize );
0272     QSize expanderSize( s.expanderIconSize, s.expanderIconSize );
0273     QSize titleSize( m_bigFm->boundingRect( index.data( Qt::DisplayRole ).toString() ).size() );
0274     QSize byLineSize( m_smallFm->boundingRect( index.data( PrettyTreeRoles::ByLineRole ).toString() ).size() );
0275     QSize capacitySize( hasCapacity ? 10 : 0, hasCapacity ? CAPACITYRECT_MIN_HEIGHT : 0 );
0276 
0277     int layoutWidth = s.frameHMargin +
0278         iconSize.width() + s.iconSpacing +
0279         qMax( titleSize.width(), byLineSize.width() ) + expanderSize.width() +
0280         s.frameHMargin;
0281 
0282     layoutWidth = qMax( viewportWidth, layoutWidth );
0283 
0284     int layoutHeight = s.frameVMargin + s.frameExtraMargin +
0285         qMax( titleSize.height(), s.smallIconSize ) + s.verticalSpace +
0286         qMax( capacitySize.height(), byLineSize.height() ) +
0287         s.frameExtraMargin + s.frameVMargin;
0288 
0289     return QSize( layoutWidth, layoutHeight );
0290 }
0291 
0292 QRect
0293 PrettyTreeDelegate::decoratorRect( const QRect &itemRect, int nr ) const
0294 {
0295     PixelSizes s( m_view->style() );
0296     const bool isRTL = QApplication::isRightToLeft();
0297 
0298     int y = itemRect.top() + s.frameVMargin + s.frameExtraMargin;
0299     int xOffset = s.frameHMargin + s.expanderIconSize + s.iconSpacing + ( s.smallIconSize + s.iconSpacing ) * nr;
0300     int x;
0301 
0302     if( isRTL )
0303         x = itemRect.left() + xOffset;
0304     else
0305         x = itemRect.right() - xOffset - s.smallIconSize;
0306 
0307     return QRect( QPoint( x, y ), QSize( s.smallIconSize, s.smallIconSize ) );
0308 }
0309 
0310 void
0311 PrettyTreeDelegate::updateFonts( const QStyleOptionViewItem &option ) const
0312 {
0313     if( m_normalFm && m_bigFm && m_smallFm && option.font == m_originalFont )
0314         return;
0315 
0316     m_originalFont = option.font;
0317 
0318     delete m_normalFm;
0319     m_normalFm = new QFontMetrics( m_originalFont );
0320 
0321     m_bigFont = m_originalFont;
0322     m_bigFont.setBold( true );
0323     delete m_bigFm;
0324     m_bigFm = new QFontMetrics( m_bigFont );
0325 
0326     m_smallFont = m_originalFont;
0327     m_smallFont.setPointSize( m_smallFont.pointSize() - 1 );
0328     delete m_smallFm;
0329     m_smallFm = new QFontMetrics( m_smallFont );
0330 }