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