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 }