File indexing completed on 2025-01-05 04:26:37
0001 /**************************************************************************************** 0002 * Copyright (c) 2007 Ian Monroe <ian@monroe.nu> * 0003 * Copyright (c) 2008-2009 Nikolaj Hald Nielsen <nhn@kde.org> * 0004 * Copyright (c) 2008 Soren Harward <stharward@gmail.com> * 0005 * Copyright (c) 2010 Nanno Langstraat <langstr@gmail.com> * 0006 * Copyright (c) 2011 Sandeep Raghuraman <sandy.8925@gmail.com> * 0007 * Copyright (c) 2013 Mark Kretschmann <kretschmann@kde.org> * 0008 * * 0009 * This program is free software; you can redistribute it and/or modify it under * 0010 * the terms of the GNU General Public License as published by the Free Software * 0011 * Foundation; either version 2 of the License, or (at your option) version 3 or * 0012 * any later version accepted by the membership of KDE e.V. (or its successor approved * 0013 * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of * 0014 * version 3 of the license. * 0015 * * 0016 * This program is distributed in the hope that it will be useful, but WITHOUT ANY * 0017 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * 0018 * PARTICULAR PURPOSE. See the GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License along with * 0021 * this program. If not, see <http://www.gnu.org/licenses/>. * 0022 ****************************************************************************************/ 0023 0024 #define DEBUG_PREFIX "Playlist::PrettyItemDelegate" 0025 0026 #include "PrettyItemDelegate.h" 0027 0028 #include "App.h" 0029 #include "EngineController.h" 0030 #include "MainWindow.h" 0031 #include "PaletteHandler.h" 0032 #include "SvgHandler.h" 0033 #include "QStringx.h" 0034 #include "core/support/Debug.h" 0035 #include "core/meta/TrackEditor.h" 0036 #include "core/capabilities/SourceInfoCapability.h" 0037 #include "core/meta/Meta.h" 0038 #include "core/meta/Statistics.h" 0039 #include "moodbar/MoodbarManager.h" 0040 #include "playlist/PlaylistModel.h" 0041 #include "playlist/layouts/LayoutManager.h" 0042 #include "playlist/proxymodels/GroupingProxy.h" 0043 #include "playlist/view/listview/InlineEditorWidget.h" 0044 0045 #include <KColorScheme> 0046 #include <KRatingPainter> // #include <KratingPainter> does not work on some distros 0047 #include <KWindowSystem> 0048 0049 #include <QAction> 0050 #include <QFontMetricsF> 0051 #include <QPainter> 0052 #include <QStyleOptionSlider> 0053 #include <QTimeLine> 0054 #include <QTimer> 0055 0056 using namespace Playlist; 0057 0058 int Playlist::PrettyItemDelegate::s_fontHeight = 0; 0059 0060 Playlist::PrettyItemDelegate::PrettyItemDelegate( QObject* parent ) 0061 : QStyledItemDelegate( parent ) 0062 { 0063 LayoutManager::instance(); 0064 0065 m_animationTimeLine = new QTimeLine( 900, this ); 0066 m_animationTimeLine->setFrameRange( 1000, 600 ); 0067 connect( m_animationTimeLine, &QTimeLine::frameChanged, this, &PrettyItemDelegate::redrawRequested ); 0068 0069 #ifdef Q_WS_X11 0070 connect( KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &PrettyItemDelegate::currentDesktopChanged ); 0071 #endif 0072 connect( EngineController::instance(), &EngineController::playbackStateChanged, this, &PrettyItemDelegate::redrawRequested ); 0073 } 0074 0075 PrettyItemDelegate::~PrettyItemDelegate() { } 0076 0077 int PrettyItemDelegate::getGroupMode( const QModelIndex &index) 0078 { 0079 return index.data( GroupRole ).toInt(); 0080 } 0081 0082 int 0083 PrettyItemDelegate::rowsForItem( const QModelIndex &index ) 0084 { 0085 PlaylistLayout layout = LayoutManager::instance()->activeLayout(); 0086 int rowCount = 0; 0087 0088 if( getGroupMode( index ) == Grouping::Head ) 0089 rowCount += layout.layoutForPart( PlaylistLayout::Head ).rows(); 0090 0091 rowCount += layout.layoutForItem( index ).rows(); 0092 0093 return rowCount; 0094 } 0095 0096 QSize 0097 PrettyItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const 0098 { 0099 s_fontHeight = option.fontMetrics.height(); 0100 0101 // -- calculate the item height 0102 int rowCount = rowsForItem( index ); 0103 0104 if( LayoutManager::instance()->activeLayout().inlineControls() && index.data( ActiveTrackRole ).toBool() ) 0105 rowCount++; //add room for extras 0106 0107 QStyle *style; 0108 if( QWidget *w = qobject_cast<QWidget*>(parent()) ) 0109 style = w->style(); 0110 else 0111 style = QApplication::style(); 0112 0113 // note: we have to be as high as the InlineEditorWidget or that would 0114 // force re-layouts or overlap 0115 // on the other hand we squeeze the line edits quite a lot. 0116 int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin ); 0117 int height = rowCount * s_fontHeight + ( rowCount + 1 ) * frameVMargin; 0118 return QSize( s_fontHeight * 20, height ); 0119 } 0120 0121 void 0122 PrettyItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const 0123 { 0124 PlaylistLayout layout = LayoutManager::instance()->activeLayout(); 0125 0126 painter->save(); 0127 QApplication::style()->drawPrimitive( QStyle::PE_PanelItemViewItem, &option, painter ); 0128 painter->translate( option.rect.topLeft() ); 0129 0130 painter->drawPixmap( 0, 0, The::svgHandler()->renderSvgWithDividers( QStringLiteral("track"), ( int )option.rect.width(), ( int )option.rect.height(), QStringLiteral("track") ) ); 0131 0132 painter->setPen( The::paletteHandler()->foregroundColor( painter, option.state & QStyle::State_Selected ) ); 0133 0134 // call paint method based on type 0135 const int groupMode = getGroupMode(index); 0136 0137 int rowCount = rowsForItem( index ); 0138 bool paintInlineControls = LayoutManager::instance()->activeLayout().inlineControls() && index.data( ActiveTrackRole ).toBool(); 0139 0140 if ( groupMode == Grouping::None || groupMode == Grouping::Body || groupMode == Grouping::Tail ) 0141 { 0142 int trackHeight = 0; 0143 int extraHeight = 0; 0144 QStyleOptionViewItem trackOption( option ); 0145 if ( paintInlineControls ) 0146 { 0147 int adjustedRowCount = rowCount + 1; 0148 trackHeight = ( option.rect.height() * rowCount ) / adjustedRowCount + 3; 0149 extraHeight = option.rect.height() - trackHeight; 0150 trackOption.rect = QRect( 0, 0, option.rect.width(), trackHeight ); 0151 } 0152 0153 paintItem( layout.layoutForItem( index ), painter, trackOption, index ); 0154 0155 if (paintInlineControls ) 0156 { 0157 QRect extrasRect( 0, trackHeight, option.rect.width(), extraHeight ); 0158 paintActiveTrackExtras( extrasRect, painter, index ); 0159 } 0160 } 0161 else if ( groupMode == Grouping::Head ) 0162 { 0163 //we need to split up the options for the actual header and the included first track 0164 0165 QFont boldfont( option.font ); 0166 boldfont.setBold( true ); 0167 0168 QStyleOptionViewItem headOption( option ); 0169 QStyleOptionViewItem trackOption( option ); 0170 0171 int headRows = layout.layoutForPart( PlaylistLayout::Head ).rows(); 0172 int trackRows = layout.layoutForItem( index ).rows(); 0173 int totalRows = headRows + trackRows; 0174 0175 //if this layout is completely empty, bail out or we will get in divide-by-zero trouble 0176 if ( totalRows == 0 ) 0177 { 0178 painter->restore(); 0179 return; 0180 } 0181 0182 if ( paintInlineControls ) 0183 { 0184 totalRows = totalRows + 1; 0185 } 0186 0187 int headHeight = ( headRows * option.rect.height() ) / totalRows - 2; 0188 int trackHeight = ( trackRows * option.rect.height() ) / totalRows + 2; 0189 0190 if ( headRows > 0 ) 0191 { 0192 headOption.rect = QRect( 0, 0, option.rect.width(), headHeight ); 0193 paintItem( layout.layoutForPart( PlaylistLayout::Head ), painter, headOption, index, true ); 0194 painter->translate( 0, headHeight ); 0195 } 0196 0197 trackOption.rect = QRect( 0, 0, option.rect.width(), trackHeight ); 0198 paintItem( layout.layoutForItem( index ), painter, trackOption, index ); 0199 0200 if ( paintInlineControls ) 0201 { 0202 int extraHeight = option.rect.height() - ( headHeight + trackHeight ); 0203 QRect extrasRect( 0, trackHeight, option.rect.width(), extraHeight ); 0204 paintActiveTrackExtras( extrasRect, painter, index ); 0205 0206 } 0207 } 0208 else 0209 QStyledItemDelegate::paint( painter, option, index ); 0210 0211 painter->restore(); 0212 } 0213 0214 bool 0215 PrettyItemDelegate::insideItemHeader( const QPoint& pt, const QRect& rect ) 0216 { 0217 QRect headerBounds = rect; 0218 headerBounds.setHeight( headerHeight() ); 0219 0220 return headerBounds.contains( pt ); 0221 } 0222 0223 int 0224 PrettyItemDelegate::headerHeight() const 0225 { 0226 int headRows = LayoutManager::instance()->activeLayout().layoutForPart( PlaylistLayout::Head ).rows(); 0227 0228 if( headRows < 1 ) 0229 return 0; 0230 0231 QStyle *style; 0232 if( QWidget *w = qobject_cast<QWidget*>(parent()) ) 0233 style = w->style(); 0234 else 0235 style = QApplication::style(); 0236 0237 int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin ); 0238 return headRows * ( s_fontHeight + frameVMargin ); 0239 } 0240 0241 QPointF 0242 PrettyItemDelegate::centerImage( const QPixmap& pixmap, const QRectF& rect ) const 0243 { 0244 qreal pixmapRatio = ( qreal )pixmap.width() / ( qreal )pixmap.height(); 0245 0246 qreal moveByX = 0.0; 0247 qreal moveByY = 0.0; 0248 0249 if ( pixmapRatio >= 1 ) 0250 moveByY = ( rect.height() - ( rect.width() / pixmapRatio ) ) / 2.0; 0251 else 0252 moveByX = ( rect.width() - ( rect.height() * pixmapRatio ) ) / 2.0; 0253 0254 return QPointF( moveByX, moveByY ); 0255 } 0256 0257 0258 void Playlist::PrettyItemDelegate::paintItem( const LayoutItemConfig &config, 0259 QPainter* painter, 0260 const QStyleOptionViewItem& option, 0261 const QModelIndex& index, 0262 bool headerRow ) const 0263 { 0264 const int rowCount = config.rows(); 0265 if( rowCount == 0 ) 0266 return; 0267 0268 QStyle *style; 0269 if( QWidget *w = qobject_cast<QWidget*>(parent()) ) 0270 style = w->style(); 0271 else 0272 style = QApplication::style(); 0273 0274 // we have to use the same metrics as the InlineEditorWidget or else 0275 // the widgets will change places when editing 0276 const int horizontalSpace = style->pixelMetric( QStyle::PM_LayoutHorizontalSpacing ); 0277 const int smallIconSize = style->pixelMetric( QStyle::PM_SmallIconSize ); 0278 const int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin ); 0279 const int frameVMargin = style->pixelMetric( QStyle::PM_FocusFrameVMargin ); 0280 const int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing ); 0281 0282 int rowOffsetX = frameHMargin; // keep the text a little bit away from the border 0283 int rowOffsetY = frameVMargin; 0284 0285 const int coverHeight = option.rect.height() - frameVMargin * 2; 0286 QRectF nominalImageRect( frameHMargin, frameVMargin, coverHeight, coverHeight ); 0287 0288 const bool showCover = config.showCover(); 0289 if( showCover ) 0290 rowOffsetX += coverHeight + horizontalSpace/* + frameHMargin * 2*/; 0291 0292 const int contentHeight = option.rect.height() - frameVMargin * 2; 0293 int rowHeight = contentHeight / rowCount; 0294 const int rowWidth = option.rect.width() - rowOffsetX - frameHMargin * 2; 0295 0296 // --- paint the active track background 0297 // We do not want to paint this for head items. 0298 if( !headerRow && index.data( ActiveTrackRole ).toBool() ) 0299 { 0300 //paint this in 3 parts to solve stretching issues with wide playlists 0301 //TODO: proper 9 part painting, but I don't want to bother with this until we 0302 //get some new graphics anyway... 0303 0304 // -- try not to highlight the indicator row 0305 int overlayXOffset = 0; 0306 int overlayYOffset = config.activeIndicatorRow() * rowHeight; 0307 int overlayHeight = option.rect.height() - overlayYOffset; 0308 int overlayLength = option.rect.width(); 0309 0310 int endWidth = overlayHeight / 4; 0311 0312 if( m_animationTimeLine->currentFrame() == m_animationTimeLine->startFrame() && EngineController::instance()->isPlaying() ) { 0313 m_animationTimeLine->setDirection( QTimeLine::Forward ); 0314 if( m_animationTimeLine->state() == QTimeLine::NotRunning ) 0315 m_animationTimeLine->start(); 0316 } 0317 else if( m_animationTimeLine->currentFrame() == m_animationTimeLine->endFrame() ) { 0318 m_animationTimeLine->setDirection( QTimeLine::Backward ); 0319 m_animationTimeLine->start(); 0320 } 0321 0322 // Opacity is used for animating the active track item 0323 const qreal opacity = qreal( m_animationTimeLine->currentFrame() ) / 1000; 0324 0325 // If opacity is not the default value we cannot render from cache 0326 const bool skipCache = opacity == 1.0 ? false : true; 0327 0328 painter->drawPixmap( overlayXOffset, overlayYOffset, 0329 The::svgHandler()->renderSvg( QStringLiteral("active_overlay_left"), 0330 endWidth, 0331 overlayHeight, 0332 QStringLiteral("active_overlay_left"), 0333 skipCache, 0334 opacity ) ); 0335 0336 painter->drawPixmap( overlayXOffset + endWidth, overlayYOffset, 0337 The::svgHandler()->renderSvg( QStringLiteral("active_overlay_mid"), 0338 overlayLength - endWidth * 2, 0339 overlayHeight, 0340 QStringLiteral("active_overlay_mid"), 0341 skipCache, 0342 opacity ) ); 0343 0344 painter->drawPixmap( overlayXOffset + ( overlayLength - endWidth ), overlayYOffset, 0345 The::svgHandler()->renderSvg( QStringLiteral("active_overlay_right"), 0346 endWidth, 0347 overlayHeight, 0348 QStringLiteral("active_overlay_right"), 0349 skipCache, 0350 opacity ) ); 0351 } 0352 0353 // --- paint the cover 0354 if( showCover ) 0355 { 0356 QModelIndex coverIndex = index.model()->index( index.row(), CoverImage ); 0357 QPixmap albumPixmap = coverIndex.data( Qt::DisplayRole ).value<QPixmap>(); 0358 0359 if( !albumPixmap.isNull() ) 0360 { 0361 //offset cover if non square 0362 QPointF offset = centerImage( albumPixmap, nominalImageRect ); 0363 QRectF imageRect( nominalImageRect.x() + offset.x(), 0364 nominalImageRect.y() + offset.y(), 0365 nominalImageRect.width() - offset.x() * 2, 0366 nominalImageRect.height() - offset.y() * 2 ); 0367 0368 painter->drawPixmap( imageRect, albumPixmap, QRectF( albumPixmap.rect() ) ); 0369 } 0370 0371 QModelIndex emblemIndex = index.model()->index( index.row(), SourceEmblem ); 0372 QPixmap emblemPixmap = emblemIndex.data( Qt::DisplayRole ).value<QPixmap>(); 0373 0374 if( !emblemPixmap.isNull() ) 0375 painter->drawPixmap( QRectF( nominalImageRect.x(), nominalImageRect.y() , 16, 16 ), emblemPixmap, QRectF( 0, 0 , 16, 16 ) ); 0376 } 0377 0378 // --- paint the markers 0379 int markerOffsetX = frameHMargin; 0380 const int rowOffsetXBeforeMarkers = rowOffsetX; 0381 if( !headerRow ) 0382 { 0383 const int queuePosition = index.data( QueuePositionRole ).toInt(); 0384 if( queuePosition > 0 ) 0385 { 0386 const int x = markerOffsetX; 0387 const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); 0388 const QString number = QString::number( queuePosition ); 0389 const int iconWidth = option.fontMetrics.boundingRect( number ).width() + smallIconSize / 2; 0390 0391 const QRect rect( x, y, iconWidth, smallIconSize ); // shift text by 1 pixel left 0392 0393 const KColorScheme colorScheme( option.palette.currentColorGroup() ); 0394 QPen rectanglePen = painter->pen(); 0395 rectanglePen.setColor( colorScheme.foreground( KColorScheme::PositiveText ).color() ); 0396 QBrush rectangleBrush = colorScheme.background( KColorScheme::PositiveBackground ); 0397 0398 painter->save(); 0399 painter->setPen( rectanglePen ); 0400 painter->setBrush( rectangleBrush ); 0401 painter->setRenderHint( QPainter::Antialiasing, true ); 0402 painter->drawRoundedRect( QRect( x, y - 1, iconWidth, smallIconSize ), smallIconSize / 3, smallIconSize / 3 ); 0403 painter->drawText( rect, Qt::AlignCenter, number ); 0404 painter->restore(); 0405 0406 markerOffsetX += ( iconWidth + iconSpacing ); 0407 0408 if ( !showCover ) 0409 rowOffsetX += ( iconWidth + iconSpacing ); 0410 else 0411 rowOffsetX += qMax( 0, iconWidth - smallIconSize - iconSpacing ); 0412 } 0413 0414 if( index.data( MultiSourceRole ).toBool() ) 0415 { 0416 const int x = markerOffsetX; 0417 const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); 0418 painter->drawPixmap( x, y, The::svgHandler()->renderSvg( QStringLiteral("multi_marker"), smallIconSize, smallIconSize, QStringLiteral("multi_marker") ) ); 0419 0420 markerOffsetX += ( smallIconSize + iconSpacing ); 0421 0422 if ( !showCover ) 0423 rowOffsetX += ( smallIconSize + iconSpacing ); 0424 } 0425 0426 if( index.data( StopAfterTrackRole ).toBool() ) 0427 { 0428 const int x = markerOffsetX; 0429 const int y = nominalImageRect.y() + ( coverHeight - smallIconSize ); 0430 painter->drawPixmap( x, y, The::svgHandler()->renderSvg( QStringLiteral("stop_button"), smallIconSize, smallIconSize, QStringLiteral("stop_button") ) ); 0431 0432 markerOffsetX += ( smallIconSize + iconSpacing ); 0433 0434 if ( !showCover ) 0435 rowOffsetX += ( smallIconSize + iconSpacing ); 0436 } 0437 } 0438 int markersWidth = rowOffsetX - rowOffsetXBeforeMarkers; 0439 0440 Meta::TrackPtr trackPtr = index.data( TrackRole ).value<Meta::TrackPtr>(); 0441 QMap<QString, QString> trackArgs; 0442 if( trackPtr ) 0443 trackArgs = buildTrackArgsMap( trackPtr ); 0444 0445 // --- paint all the rows 0446 for( int i = 0; i < rowCount; i++ ) 0447 { 0448 LayoutItemConfigRow row = config.row( i ); 0449 const int elementCount = row.count(); 0450 0451 //we need to do a quick pass to figure out how much space is left for auto sizing elements 0452 qreal spareSpace = 1.0; 0453 int autoSizeElemCount = 0; 0454 for( int k = 0; k < elementCount; ++k ) 0455 { 0456 spareSpace -= row.element( k ).size(); 0457 if( row.element( k ).size() < 0.001 ) 0458 autoSizeElemCount++; 0459 } 0460 0461 const qreal spacePerAutoSizeElem = spareSpace / (qreal)autoSizeElemCount; 0462 0463 //give left over pixels to the first rows. Widgets are doing it the same. 0464 if( i == 0 ) 0465 rowHeight++; 0466 if( i == ( contentHeight % rowCount ) ) 0467 rowHeight--; 0468 0469 int currentItemX = rowOffsetX; 0470 for( int j = 0; j < elementCount; ++j ) 0471 { 0472 LayoutItemConfigRowElement element = row.element( j ); 0473 0474 // -- calculate the size 0475 qreal size; 0476 if( element.size() < 0.001 ) 0477 size = spacePerAutoSizeElem; 0478 else 0479 size = element.size(); 0480 0481 qreal itemWidth; 0482 if( j == elementCount - 1 ) 0483 // use the full with for the last item 0484 itemWidth = rowWidth - (currentItemX - rowOffsetXBeforeMarkers); 0485 else 0486 itemWidth = rowWidth * size - markersWidth; 0487 0488 markersWidth = 0; // leave columns > 0 alone, they are unaffected by markers 0489 0490 if( itemWidth <= 1 ) 0491 continue; // no sense to paint such small items 0492 0493 // -- set font 0494 bool bold = element.bold(); 0495 bool italic = element.italic(); 0496 bool underline = element.underline(); 0497 int alignment = element.alignment(); 0498 0499 QFont font = option.font; 0500 font.setBold( bold ); 0501 font.setItalic( italic ); 0502 font.setUnderline( underline ); 0503 painter->setFont( font ); 0504 0505 int value = element.value(); 0506 QModelIndex textIndex = index.model()->index( index.row(), value ); 0507 0508 // -- paint the element 0509 if( value == Rating ) 0510 { 0511 int rating = textIndex.data( Qt::DisplayRole ).toInt(); 0512 0513 KRatingPainter::paintRating( painter, 0514 QRect( currentItemX, rowOffsetY, 0515 itemWidth, rowHeight ), 0516 element.alignment(), rating ); 0517 0518 } 0519 else if( value == Divider ) 0520 { 0521 QPixmap left = The::svgHandler()->renderSvg( QStringLiteral("divider_left"), 0522 1, rowHeight , 0523 QStringLiteral("divider_left") ); 0524 0525 QPixmap right = The::svgHandler()->renderSvg( QStringLiteral("divider_right"), 0526 1, rowHeight, 0527 QStringLiteral("divider_right") ); 0528 0529 if( alignment & Qt::AlignLeft ) 0530 { 0531 painter->drawPixmap( currentItemX, rowOffsetY, left ); 0532 painter->drawPixmap( currentItemX + 1, rowOffsetY, right ); 0533 } 0534 else if( alignment & Qt::AlignRight ) 0535 { 0536 painter->drawPixmap( currentItemX + itemWidth - 1, rowOffsetY, left ); 0537 painter->drawPixmap( currentItemX + itemWidth, rowOffsetY, right ); 0538 } 0539 else 0540 { 0541 int center = currentItemX + ( itemWidth / 2 ); 0542 painter->drawPixmap( center, rowOffsetY, left ); 0543 painter->drawPixmap( center + 1, rowOffsetY, right ); 0544 } 0545 } 0546 else if( value == Moodbar ) 0547 { 0548 //we cannot ask the model for the moodbar directly as we have no 0549 //way of asking for a specific size. Instead just get the track from 0550 //the model and ask the moodbar manager ourselves. 0551 Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>(); 0552 0553 if( The::moodbarManager()->hasMoodbar( track ) ) 0554 { 0555 QPixmap moodbar = The::moodbarManager()->getMoodbar( track, itemWidth, rowHeight - 8 ); 0556 0557 painter->drawPixmap( currentItemX, rowOffsetY + 4, moodbar ); 0558 } 0559 } 0560 //actual playlist item text is drawn here 0561 else 0562 { 0563 //TODO: get rid of passing TrackPtr as data, use custom role instead 0564 Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>(); 0565 QString text = textIndex.data( Qt::DisplayRole ).toString(); 0566 Amarok::QStringx prefix( element.prefix() ); 0567 Amarok::QStringx suffix( element.suffix() ); 0568 text = prefix.namedOptArgs( trackArgs ) + text + suffix.namedOptArgs( trackArgs ); 0569 text = QFontMetricsF( font ).elidedText( text, Qt::ElideRight, itemWidth ); 0570 0571 //if the track can't be played, it should be grayed out to show that it is unavailable 0572 if( !track->isPlayable() ) 0573 { 0574 painter->save(); 0575 QPen grayPen = painter->pen(); 0576 grayPen.setColor( QColor( 127, 127, 127 ) ); // TODO: use disabled role 0577 painter->setPen( grayPen ); 0578 painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY, 0579 itemWidth - frameHMargin * 2, rowHeight ), 0580 alignment, text ); 0581 painter->restore(); 0582 } 0583 else 0584 { 0585 painter->drawText( QRect( currentItemX + frameHMargin, rowOffsetY, 0586 itemWidth - frameHMargin * 2, rowHeight ), 0587 alignment, text ); 0588 } 0589 } 0590 currentItemX += itemWidth; 0591 } 0592 rowOffsetY += rowHeight; 0593 } 0594 } 0595 0596 void Playlist::PrettyItemDelegate::paintActiveTrackExtras( const QRect &rect, QPainter* painter, const QModelIndex& index ) const 0597 { 0598 Q_UNUSED( index ); 0599 0600 int x = rect.x(); 0601 int y = rect.y(); 0602 int width = rect.width(); 0603 int height = rect.height(); 0604 int buttonSize = height - 4; 0605 0606 QStyle *style; 0607 if( QWidget *w = qobject_cast<QWidget*>(parent()) ) 0608 style = w->style(); 0609 else 0610 style = QApplication::style(); 0611 0612 // some style margins: 0613 int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin ); 0614 int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing ); 0615 0616 //just paint some "buttons for now 0617 0618 int offset = x + frameHMargin; 0619 painter->drawPixmap( offset, y + 2, 0620 The::svgHandler()->renderSvg( QStringLiteral("back_button"), 0621 buttonSize, buttonSize, 0622 QStringLiteral("back_button") ) ); 0623 0624 if( The::engineController()->isPlaying() ) 0625 { 0626 offset += ( buttonSize + iconSpacing ); 0627 painter->drawPixmap( offset, y + 2, 0628 The::svgHandler()->renderSvg( QStringLiteral("pause_button"), 0629 buttonSize, buttonSize, 0630 QStringLiteral("pause_button") ) ); 0631 0632 } 0633 else 0634 { 0635 offset += ( buttonSize + iconSpacing ); 0636 painter->drawPixmap( offset, y + 2, 0637 The::svgHandler()->renderSvg( QStringLiteral("play_button"), 0638 buttonSize, buttonSize, 0639 QStringLiteral("play_button") ) ); 0640 } 0641 0642 offset += ( buttonSize + iconSpacing ); 0643 painter->drawPixmap( offset, y + 2, 0644 The::svgHandler()->renderSvg( QStringLiteral("stop_button"), 0645 buttonSize, buttonSize, 0646 QStringLiteral("stop_button") ) ); 0647 0648 offset += ( buttonSize + iconSpacing ); 0649 painter->drawPixmap( offset, y + 2, 0650 The::svgHandler()->renderSvg( QStringLiteral("next_button"), 0651 buttonSize, buttonSize, 0652 QStringLiteral("next_button") ) ); 0653 0654 offset += ( buttonSize + iconSpacing ); 0655 0656 long trackLength = The::engineController()->trackLength(); 0657 long trackPos = The::engineController()->trackPositionMs(); 0658 qreal trackPercentage = 0.0; 0659 0660 if ( trackLength > 0 ) 0661 trackPercentage = ( (qreal) trackPos / (qreal) trackLength ); 0662 0663 int sliderWidth = width - ( offset + frameHMargin ); 0664 QStyleOptionSlider opt; 0665 opt.rect.setRect( offset, y, sliderWidth, height ); 0666 The::svgHandler()->paintCustomSlider( painter, &opt, trackPercentage, false ); 0667 } 0668 0669 bool Playlist::PrettyItemDelegate::clicked( const QPoint &pos, const QRect &itemRect, const QModelIndex& index ) 0670 { 0671 0672 //for now, only handle clicks in the currently playing item. 0673 if ( !index.data( ActiveTrackRole ).toBool() ) 0674 return false; 0675 0676 //also, if we are not using the inline controls, we should not react to these clicks at all 0677 if( !LayoutManager::instance()->activeLayout().inlineControls() ) 0678 return false; 0679 0680 int rowCount = rowsForItem( index ); 0681 int modifiedRowCount = rowCount + 1; 0682 0683 int height = itemRect.height(); 0684 0685 int baseHeight = ( height * rowCount ) / modifiedRowCount + 3; 0686 int extrasHeight = height - baseHeight; 0687 int extrasOffsetY = height - extrasHeight; 0688 0689 int buttonSize = extrasHeight - 4; 0690 0691 QStyle *style; 0692 if( QWidget *w = qobject_cast<QWidget*>(parent()) ) 0693 style = w->style(); 0694 else 0695 style = QApplication::style(); 0696 0697 // some style margins: 0698 int frameHMargin = style->pixelMetric( QStyle::PM_FocusFrameHMargin ); 0699 int iconSpacing = style->pixelMetric( QStyle::PM_ToolBarItemSpacing ); 0700 0701 int offset = frameHMargin; 0702 QRect backRect( offset, extrasOffsetY + 2, buttonSize, buttonSize ); 0703 if( backRect.contains( pos ) ) 0704 { 0705 Amarok::actionCollection()->action( QStringLiteral("prev") )->trigger(); 0706 return true; 0707 } 0708 0709 offset += ( buttonSize + iconSpacing ); 0710 QRect playRect( offset, extrasOffsetY + 2, buttonSize, buttonSize ); 0711 if( playRect.contains( pos ) ) 0712 { 0713 Amarok::actionCollection()->action( QStringLiteral("play_pause") )->trigger(); 0714 return true; 0715 } 0716 0717 offset += ( buttonSize + iconSpacing ); 0718 QRect stopRect( offset, extrasOffsetY + 2, buttonSize, buttonSize ); 0719 if( stopRect.contains( pos ) ) 0720 { 0721 Amarok::actionCollection()->action( QStringLiteral("stop") )->trigger(); 0722 return true; 0723 } 0724 0725 0726 offset += ( buttonSize + iconSpacing ); 0727 QRect nextRect( offset, extrasOffsetY + 2, buttonSize, buttonSize ); 0728 if( nextRect.contains( pos ) ) 0729 { 0730 Amarok::actionCollection()->action( QStringLiteral("next") )->trigger(); 0731 return true; 0732 } 0733 0734 offset += ( buttonSize + iconSpacing ); 0735 0736 //handle clicks on the slider 0737 0738 int sliderWidth = itemRect.width() - ( offset + iconSpacing ); 0739 int knobSize = buttonSize - 2; 0740 0741 QRect sliderActiveRect( offset, extrasOffsetY + 3, sliderWidth, knobSize ); 0742 if( sliderActiveRect.contains( pos ) ) 0743 { 0744 int xSliderPos = pos.x() - offset; 0745 long trackLength = EngineController::instance()->trackLength(); 0746 0747 qreal percentage = (qreal) xSliderPos / (qreal) sliderWidth; 0748 EngineController::instance()->seekTo( trackLength * percentage ); 0749 return true; 0750 0751 } 0752 0753 return false; 0754 } 0755 0756 QWidget* Playlist::PrettyItemDelegate::createEditor( QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const 0757 { 0758 Q_UNUSED( option ); 0759 0760 int editorHeight = sizeHint(option, index).height(); 0761 int editorWidth = sizeHint(option, index).width(); 0762 if( getGroupMode( index ) == Grouping::Head ) 0763 editorHeight -= headerHeight(); 0764 InlineEditorWidget *editor = new InlineEditorWidget( parent, index, 0765 LayoutManager::instance()->activeLayout(), editorHeight, editorWidth ); 0766 0767 connect( editor, &InlineEditorWidget::editingDone, 0768 this, &PrettyItemDelegate::editorDone ); 0769 return editor; 0770 } 0771 0772 void Playlist::PrettyItemDelegate::setModelData( QWidget * editor, QAbstractItemModel * model, const QModelIndex &index ) const 0773 { 0774 Q_UNUSED( model ) 0775 0776 InlineEditorWidget * inlineEditor = qobject_cast<InlineEditorWidget *>( editor ); 0777 if( !inlineEditor ) 0778 return; 0779 0780 QMap<int, QString> changeMap = inlineEditor->changedValues(); 0781 0782 debug() << "got inline editor!!"; 0783 debug() << "changed values map: " << changeMap; 0784 0785 //ok, now get the track, figure out if it is editable and if so, apply new values. 0786 //It's as simple as that! :-) 0787 0788 Meta::TrackPtr track = index.data( TrackRole ).value<Meta::TrackPtr>(); 0789 if( !track ) 0790 return; 0791 0792 // this does not require TrackEditor 0793 if( changeMap.contains( Rating ) ) 0794 { 0795 int rating = changeMap.value( Rating ).toInt(); 0796 track->statistics()->setRating( rating ); 0797 changeMap.remove( Rating ); 0798 } 0799 0800 Meta::TrackEditorPtr ec = track->editor(); 0801 if( !ec ) 0802 return; 0803 0804 QList<int> columns = changeMap.keys(); 0805 0806 foreach( int column, columns ) 0807 { 0808 QString value = changeMap.value( column ); 0809 0810 switch( column ) 0811 { 0812 case Album: 0813 ec->setAlbum( value ); 0814 break; 0815 case Artist: 0816 ec->setArtist( value ); 0817 break; 0818 case Comment: 0819 ec->setComment( value ); 0820 break; 0821 case Composer: 0822 ec->setComposer( value ); 0823 break; 0824 case DiscNumber: 0825 { 0826 int discNumber = value.toInt(); 0827 ec->setDiscNumber( discNumber ); 0828 break; 0829 } 0830 case Genre: 0831 ec->setGenre( value ); 0832 break; 0833 case Rating: 0834 break; // we've already set the rating, this even shouldn't be here 0835 case Title: 0836 ec->setTitle( value ); 0837 break; 0838 case TitleWithTrackNum: 0839 { 0840 debug() << "parse TitleWithTrackNum"; 0841 //we need to parse out the track number and the track name (and check 0842 //if the string is even valid...) 0843 //QRegExp rx("(\\d+)\\s-\\s(.*))"); 0844 QRegExp rx("(\\d+)(\\s-\\s)(.*)"); 0845 if ( rx.indexIn( value ) != -1) { 0846 int trackNumber = rx.cap( 1 ).toInt(); 0847 QString trackName = rx.cap( 3 ); 0848 debug() << "split TitleWithTrackNum into " << trackNumber << " and " << trackName; 0849 ec->setTrackNumber( trackNumber ); 0850 ec->setTitle( trackName ); 0851 } 0852 break; 0853 } 0854 case TrackNumber: 0855 { 0856 int TrackNumber = value.toInt(); 0857 ec->setTrackNumber( TrackNumber ); 0858 break; 0859 } 0860 case Year: 0861 ec->setYear( value.toInt() ); 0862 break; 0863 case Bpm: 0864 ec->setBpm( value.toFloat() ); 0865 break; 0866 } 0867 } 0868 } 0869 0870 void 0871 Playlist::PrettyItemDelegate::updateEditorGeometry( QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const 0872 { 0873 Q_UNUSED( index ) 0874 0875 QRect editorRect( option.rect ); 0876 if( getGroupMode( index ) == Grouping::Head ) 0877 editorRect.setTop( editorRect.top() + headerHeight() ); 0878 0879 editor->setFixedSize( editorRect.size() ); 0880 editor->setGeometry( editorRect ); 0881 } 0882 0883 void 0884 Playlist::PrettyItemDelegate::editorDone( InlineEditorWidget * editor ) 0885 { 0886 Q_EMIT commitData( editor ); 0887 } 0888 0889 void 0890 Playlist::PrettyItemDelegate::currentDesktopChanged() 0891 { 0892 // Optimization for X11/Linux desktops: 0893 // Don't update the animation if Amarok is not on the active virtual desktop. 0894 0895 m_animationTimeLine->setPaused( !The::mainWindow()->isOnCurrentDesktop() ); 0896 } 0897 0898 QMap<QString, QString> 0899 Playlist::PrettyItemDelegate::buildTrackArgsMap( const Meta::TrackPtr &track ) const 0900 { 0901 QMap<QString, QString> args; 0902 QString artist = track->artist() ? track->artist()->name() : QString(); 0903 QString albumartist; 0904 if( track->album() && track->album()->hasAlbumArtist() ) 0905 albumartist = track->album()->albumArtist()->name(); 0906 else 0907 albumartist = artist; 0908 0909 0910 args[QStringLiteral("title")] = track->name(); 0911 args[QStringLiteral("composer")] = track->composer() ? track->composer()->name() : QString(); 0912 0913 // if year == 0 then we don't want include it 0914 QString year = track->year() ? track->year()->name() : QString(); 0915 args[QStringLiteral("year")] = year.localeAwareCompare( QStringLiteral("0") ) == 0 ? QString() : year; 0916 args[QStringLiteral("album")] = track->album() ? track->album()->name() : QString(); 0917 0918 if( track->discNumber() ) 0919 args[QStringLiteral("discnumber")] = QString::number( track->discNumber() ); 0920 0921 args[QStringLiteral("genre")] = track->genre() ? track->genre()->name() : QString(); 0922 args[QStringLiteral("comment")] = track->comment(); 0923 args[QStringLiteral("artist")] = artist; 0924 args[QStringLiteral("albumartist")] = albumartist; 0925 args[QStringLiteral("initial")] = albumartist.mid( 0, 1 ).toUpper(); //artists starting with The are already handled above 0926 args[QStringLiteral("filetype")] = track->type(); 0927 0928 args[QStringLiteral("rating")] = track->statistics()->rating(); 0929 args[QStringLiteral("filesize")] = track->filesize(); 0930 args[QStringLiteral("length")] = track->length() / 1000; 0931 0932 if ( track->trackNumber() ) 0933 { 0934 QString trackNum = QStringLiteral( "%1" ).arg( track->trackNumber(), 2, 10, QChar('0') ); 0935 args[QStringLiteral("track")] = trackNum; 0936 } 0937 0938 return args; 0939 } 0940