PageRenderTime 413ms CodeModel.GetById 81ms app.highlight 257ms RepoModel.GetById 65ms app.codeStats 0ms

/src/libtomahawk/playlist/PlaylistItemDelegate.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 882 lines | 665 code | 168 blank | 49 comment | 112 complexity | 117a01fbab0e49a23bdc31a4b721ba21 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2010-2011, Jeff Mitchell <jeff@tomahawk-player.org>
  5 *   Copyright 2013-2014, Teo Mrnjavac <teo@kde.org>
  6 *
  7 *   Tomahawk is free software: you can redistribute it and/or modify
  8 *   it under the terms of the GNU General Public License as published by
  9 *   the Free Software Foundation, either version 3 of the License, or
 10 *   (at your option) any later version.
 11 *
 12 *   Tomahawk is distributed in the hope that it will be useful,
 13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 15 *   GNU General Public License for more details.
 16 *
 17 *   You should have received a copy of the GNU General Public License
 18 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 19 */
 20
 21#include "PlaylistItemDelegate.h"
 22
 23#include "Query.h"
 24#include "Result.h"
 25#include "Artist.h"
 26#include "Album.h"
 27#include "Source.h"
 28#include "SourceList.h"
 29
 30#include "PlayableModel.h"
 31#include "PlayableItem.h"
 32#include "PlayableProxyModel.h"
 33#include "TrackView.h"
 34#include "ViewHeader.h"
 35#include "ViewManager.h"
 36
 37#include "widgets/DownloadButton.h"
 38#include "audio/AudioEngine.h"
 39#include "utils/ImageRegistry.h"
 40#include "utils/PixmapDelegateFader.h"
 41#include "utils/Closure.h"
 42#include "utils/TomahawkStyle.h"
 43#include "utils/TomahawkUtilsGui.h"
 44#include "utils/Logger.h"
 45
 46#include <QAbstractTextDocumentLayout>
 47#include <QApplication>
 48#include <QDateTime>
 49#include <QMouseEvent>
 50#include <QPainter>
 51#include <QToolTip>
 52
 53using namespace Tomahawk;
 54
 55
 56PlaylistItemDelegate::PlaylistItemDelegate( TrackView* parent, PlayableProxyModel* proxy )
 57    : QStyledItemDelegate( (QObject*)parent )
 58    , m_view( parent )
 59    , m_model( proxy )
 60{
 61    m_topOption = QTextOption( Qt::AlignTop );
 62    m_topOption.setWrapMode( QTextOption::NoWrap );
 63    m_bottomOption = QTextOption( Qt::AlignBottom );
 64    m_bottomOption.setWrapMode( QTextOption::NoWrap );
 65
 66    m_centerOption = QTextOption( Qt::AlignVCenter );
 67    m_centerOption.setWrapMode( QTextOption::NoWrap );
 68    m_centerRightOption = QTextOption( Qt::AlignVCenter | Qt::AlignRight );
 69    m_centerRightOption.setWrapMode( QTextOption::NoWrap );
 70
 71    m_demiBoldFont = parent->font();
 72    m_demiBoldFont.setPointSize( TomahawkUtils::defaultFontSize() + 1 );
 73    m_demiBoldFont.setWeight( QFont::DemiBold );
 74
 75    m_normalFont = parent->font();
 76    m_normalFont.setPointSize( TomahawkUtils::defaultFontSize() + 1 );
 77
 78    connect( this, SIGNAL( updateIndex( QModelIndex ) ), parent, SLOT( update( QModelIndex ) ) );
 79    connect( proxy, SIGNAL( modelReset() ), SLOT( modelChanged() ) );
 80    connect( parent, SIGNAL( modelChanged() ), SLOT( modelChanged() ) );
 81}
 82
 83
 84void
 85PlaylistItemDelegate::updateRowSize( const QModelIndex& index )
 86{
 87    emit sizeHintChanged( index );
 88}
 89
 90
 91QSize
 92PlaylistItemDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
 93{
 94    QSize size = QStyledItemDelegate::sizeHint( option, index );
 95
 96    {
 97        if ( m_model->style() != PlayableProxyModel::SingleColumn )
 98        {
 99            int rowHeight = option.fontMetrics.height() * 1.6;
100            size.setHeight( rowHeight );
101        }
102    }
103
104    return size;
105}
106
107
108void
109PlaylistItemDelegate::prepareStyleOption( QStyleOptionViewItem* option, const QModelIndex& index, PlayableItem* item ) const
110{
111    initStyleOption( option, index );
112
113    TomahawkUtils::prepareStyleOption( option, index, item );
114}
115
116
117QWidget*
118PlaylistItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
119{
120    PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
121    Q_ASSERT( item );
122
123    return DownloadButton::handleCreateEditor( parent, item->query(), m_view, index );
124}
125
126
127void
128PlaylistItemDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
129{
130    QStyledItemDelegate::updateEditorGeometry( editor, option, index );
131
132    DownloadButton* comboBox = static_cast<DownloadButton*>(editor);
133    comboBox->resize( option.rect.size() - QSize( 8, 0 ) );
134    comboBox->move( option.rect.x() + 4, option.rect.y() );
135
136    if ( m_downloadDropDownRects.contains( index ) )
137    {
138        editor->setGeometry( m_downloadDropDownRects.value( index ) );
139    }
140
141    if ( !comboBox->property( "shownPopup" ).toBool() )
142    {
143        comboBox->showPopup();
144        comboBox->setProperty( "shownPopup", true );
145    }
146}
147
148
149void
150PlaylistItemDelegate::setModelData( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const
151{
152}
153
154
155void
156PlaylistItemDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
157{
158    const int style = index.data( PlayableProxyModel::StyleRole ).toInt();
159    switch ( style )
160    {
161        case PlayableProxyModel::Collection:
162        case PlayableProxyModel::Locker:
163        case PlayableProxyModel::Detailed:
164            paintDetailed( painter, option, index );
165            break;
166    }
167}
168
169
170void
171PlaylistItemDelegate::paintDetailed( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
172{
173    PlayableItem* item = m_model->itemFromIndex( m_model->mapToSource( index ) );
174    Q_ASSERT( item );
175
176    QTextOption textOption( Qt::AlignVCenter | (Qt::Alignment)index.data( Qt::TextAlignmentRole ).toUInt() );
177    textOption.setWrapMode( QTextOption::NoWrap );
178
179    QStyleOptionViewItem opt = option;
180    prepareStyleOption( &opt, index, item );
181    opt.text.clear();
182    qApp->style()->drawControl( QStyle::CE_ItemViewItem, &opt, painter );
183
184    if ( m_hoveringOver == index && !index.data().toString().isEmpty() &&
185       ( index.column() == PlayableModel::Artist || index.column() == PlayableModel::Album || index.column() == PlayableModel::Track ) )
186    {
187        opt.rect.setWidth( opt.rect.width() - opt.rect.height() - 2 );
188        const QRect arrowRect( opt.rect.x() + opt.rect.width(), opt.rect.y() + 1, opt.rect.height() - 2, opt.rect.height() - 2 );
189        drawInfoButton( painter, arrowRect, index, 0.9 );
190    }
191
192    painter->save();
193
194/*    if ( index.column() == PlayableModel::Score )
195    {
196        QColor barColor( 167, 183, 211 ); // This matches the sidebar (sourcetreeview.cpp:672)
197        if ( opt.state & QStyle::State_Selected && !item->isPlaying() )
198            painter->setPen( Qt::white );
199        else
200            painter->setPen( barColor );
201
202        QRect r = opt.rect.adjusted( 3, 3, -6, -4 );
203        painter->drawRect( r );
204
205        QRect fillR = r;
206        int fillerWidth = (int)( index.data().toFloat() * (float)fillR.width() );
207        fillR.adjust( 0, 0, -( fillR.width() - fillerWidth ), 0 );
208
209        if ( opt.state & QStyle::State_Selected && !item->isPlaying() )
210            painter->setBrush( TomahawkUtils::Colors::NOW_PLAYING_ITEM.lighter() );
211        else
212            painter->setBrush( barColor );
213
214        painter->drawRect( fillR );
215    }
216    else */
217    if ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download )
218    {
219        DownloadButton::drawPrimitive( painter, opt.rect.adjusted( 4, 0, -4, 0 ), item->query(), hoveringOver() == index );
220    }
221    else if ( item->isPlaying() )
222    {
223        QRect r = opt.rect.adjusted( 3, 0, 0, 0 );
224
225        // Paint Now Playing Speaker Icon
226        if ( m_view->header()->visualIndex( index.column() ) == 0 )
227        {
228            const int pixMargin = 1;
229            const int pixHeight = r.height() - pixMargin * 2;
230            const QRect npr = r.adjusted( pixMargin, pixMargin, pixHeight - r.width() + pixMargin, -pixMargin );
231            painter->drawPixmap( npr, TomahawkUtils::defaultPixmap( TomahawkUtils::NowPlayingSpeaker, TomahawkUtils::Original, npr.size() ) );
232            r.adjust( pixHeight + 6, 0, 0, 0 );
233        }
234
235        painter->setPen( opt.palette.text().color() );
236        const QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, r.width() - 3 );
237        painter->drawText( r.adjusted( 0, 1, 0, 0 ), text, textOption );
238    }
239    else
240    {
241        painter->setPen( opt.palette.text().color() );
242        const QString text = painter->fontMetrics().elidedText( index.data().toString(), Qt::ElideRight, opt.rect.width() - 6 );
243        painter->drawText( opt.rect.adjusted( 3, 1, -3, 0 ), text, textOption );
244    }
245
246    painter->restore();
247}
248
249
250QRect
251PlaylistItemDelegate::drawInfoButton( QPainter* painter, const QRect& rect, const QModelIndex& index, float height ) const
252{
253    const int iconSize = rect.height() * height;
254    QRect pixmapRect = QRect( ( rect.height() - iconSize ) / 2 + rect.left(), rect.center().y() - iconSize / 2, iconSize, iconSize );
255
256    painter->drawPixmap( pixmapRect, TomahawkUtils::defaultPixmap( TomahawkUtils::InfoIcon, TomahawkUtils::Original, pixmapRect.size() ) );
257    m_infoButtonRects[ index ] = pixmapRect;
258
259    return rect.adjusted( rect.height(), 0, 0, 0 );
260}
261
262
263QRect
264PlaylistItemDelegate::drawCover( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const
265{
266    QRect pixmapRect = rect;
267    pixmapRect.setWidth( pixmapRect.height() );
268
269    if ( !m_pixmaps.contains( index ) )
270    {
271        m_pixmaps.insert( index, QSharedPointer< Tomahawk::PixmapDelegateFader >( new Tomahawk::PixmapDelegateFader( item->query(), pixmapRect.size(), TomahawkUtils::RoundedCorners, false ) ) );
272        _detail::Closure* closure = NewClosure( m_pixmaps[ index ], SIGNAL( repaintRequest() ), const_cast<PlaylistItemDelegate*>(this), SLOT( doUpdateIndex( const QPersistentModelIndex& ) ), QPersistentModelIndex( index ) );
273        closure->setAutoDelete( false );
274    }
275
276    const QPixmap pixmap = m_pixmaps[ index ]->currentPixmap();
277    painter->drawPixmap( pixmapRect, pixmap );
278
279    return rect.adjusted( pixmapRect.width(), 0, 0, 0 );
280}
281
282
283QRect
284PlaylistItemDelegate::drawLoveBox( QPainter* painter, const QRect& rect, PlayableItem* item, const QModelIndex& index ) const
285{
286    const int avatarSize = rect.height() - 4 * 2;
287    const int avatarMargin = 2;
288
289    QList< Tomahawk::source_ptr > sources;
290    foreach ( const Tomahawk::SocialAction& sa, item->query()->queryTrack()->socialActions( "Love", true, true ) )
291    {
292        sources << sa.source;
293    }
294    const int max = 5;
295    const unsigned int count = qMin( sources.count(), max );
296
297    QRect innerRect = rect.adjusted( rect.width() -
298                                     ( avatarSize + avatarMargin ) * ( count + 1 ) -
299                                     4 * 4,
300                                     0, 0, 0 );
301
302    if ( !sources.isEmpty() )
303        drawRectForBox( painter, innerRect );
304
305    QRect avatarsRect = innerRect.adjusted( 4, 4, -4, -4 );
306
307    drawAvatarsForBox( painter, avatarsRect, avatarSize, avatarMargin, count, sources, index );
308
309    TomahawkUtils::ImageType type = item->query()->queryTrack()->loved() ? TomahawkUtils::Loved : TomahawkUtils::NotLoved;
310    QRect r = innerRect.adjusted( innerRect.width() - rect.height() + 4, 4, -4, -4 );
311    painter->drawPixmap( r, TomahawkUtils::defaultPixmap( type, TomahawkUtils::Original, QSize( r.height(), r.height() ) ) );
312    m_loveButtonRects[ index ] = r;
313
314    return rect;
315}
316
317
318QRect
319PlaylistItemDelegate::drawGenericBox( QPainter* painter,
320                                      const QStyleOptionViewItem& option,
321                                      const QRect& rect, const QString& text,
322                                      const QList< Tomahawk::source_ptr >& sources,
323                                      const QModelIndex& index ) const
324{
325    const int avatarSize = rect.height() - 4 * 2;
326    const int avatarMargin = 2;
327
328    const int max = 5;
329    const unsigned int count = qMin( sources.count(), max );
330
331    QTextDocument textDoc;
332    textDoc.setHtml( QString( "<b>%1</b>" ).arg( text ) );
333    textDoc.setDocumentMargin( 0 );
334    textDoc.setDefaultFont( painter->font() );
335    textDoc.setDefaultTextOption( m_bottomOption );
336
337    QRect innerRect = rect.adjusted( rect.width() - ( avatarSize + avatarMargin ) * count - 4 * 4 -
338                                     textDoc.idealWidth(),
339                                     0, 0, 0 );
340
341    QRect textRect = innerRect.adjusted( 4, 4, - innerRect.width() + textDoc.idealWidth() + 2*4, -4 );
342
343    drawRichText( painter, option, textRect, Qt::AlignVCenter|Qt::AlignRight, textDoc );
344
345    if ( !sources.isEmpty() )
346        drawRectForBox( painter, innerRect );
347
348    QRect avatarsRect = innerRect.adjusted( textDoc.idealWidth() + 3*4, 4, -4, -4 );
349    drawAvatarsForBox( painter, avatarsRect, avatarSize, avatarMargin, count, sources, index );
350
351    return rect;
352}
353
354
355void
356PlaylistItemDelegate::drawRectForBox( QPainter* painter, const QRect& rect ) const
357{
358    painter->save();
359
360    painter->setRenderHint( QPainter::Antialiasing, true );
361    painter->setBrush( Qt::transparent );
362    QPen pen = painter->pen().color();
363    pen.setWidthF( 0.2 );
364    painter->setPen( pen );
365
366    painter->drawRoundedRect( rect, 4, 4, Qt::RelativeSize );
367
368    painter->restore();
369}
370
371
372void
373PlaylistItemDelegate::drawAvatarsForBox( QPainter* painter,
374                                         const QRect& avatarsRect,
375                                         int avatarSize,
376                                         int avatarMargin,
377                                         int count,
378                                         const QList< Tomahawk::source_ptr >& sources,
379                                         const QModelIndex& index ) const
380{
381    painter->save();
382
383    QHash< Tomahawk::source_ptr, QRect > rectsToSave;
384
385    int i = 0;
386    foreach ( const Tomahawk::source_ptr& s, sources )
387    {
388        if ( i >= count )
389            break;
390
391        QRect r = avatarsRect.adjusted( ( avatarSize + avatarMargin ) * i, 0, 0, 0 );
392        r.setWidth( avatarSize + avatarMargin );
393
394        QPixmap pixmap = s->avatar( TomahawkUtils::Original, QSize( avatarSize, avatarSize ), true );
395        painter->drawPixmap( r.adjusted( avatarMargin / 2, 0, -( avatarMargin / 2 ), 0 ), pixmap );
396
397        rectsToSave.insert( s, r );
398
399        i++;
400    }
401
402    if ( !rectsToSave.isEmpty() )
403        m_avatarBoxRects.insert( index, rectsToSave );
404
405    painter->restore();
406}
407
408
409void
410PlaylistItemDelegate::drawRichText( QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, int flags, QTextDocument& text ) const
411{
412    Q_UNUSED( option );
413
414    text.setPageSize( QSize( rect.width(), QWIDGETSIZE_MAX ) );
415    QAbstractTextDocumentLayout* layout = text.documentLayout();
416
417    const int height = qRound( layout->documentSize().height() );
418    int y = rect.y();
419    if ( flags & Qt::AlignBottom )
420        y += ( rect.height() - height );
421    else if ( flags & Qt::AlignVCenter )
422        y += ( rect.height() - height ) / 2;
423
424    QAbstractTextDocumentLayout::PaintContext context;
425    context.palette.setColor( QPalette::Text, painter->pen().color() );
426
427    painter->save();
428    painter->translate( rect.x(), y );
429    layout->draw( painter, context );
430    painter->restore();
431}
432
433
434QRect
435PlaylistItemDelegate::drawSourceIcon( QPainter* painter, const QRect& rect, PlayableItem* item, float height ) const
436{
437    const int sourceIconSize = rect.height() * height;
438    QRect resultRect = rect.adjusted( 0, 0, -( sourceIconSize + 8 ), 0 );
439    if ( item->query()->numResults( true ) == 0 )
440        return resultRect;
441
442    const QPixmap sourceIcon = item->query()->results().first()->sourceIcon( TomahawkUtils::RoundedCorners, QSize( sourceIconSize, sourceIconSize ) );
443    if ( sourceIcon.isNull() )
444        return resultRect;
445
446    painter->setOpacity( 0.8 );
447    painter->drawPixmap( QRect( rect.right() - sourceIconSize, rect.center().y() - sourceIconSize / 2, sourceIcon.width(), sourceIcon.height() ), sourceIcon );
448    painter->setOpacity( 1.0 );
449
450    return resultRect;
451}
452
453
454QRect
455PlaylistItemDelegate::drawSource( QPainter* painter, const QStyleOptionViewItem& /* option */, const QModelIndex& /* index */, const QRect& rect, PlayableItem* item ) const
456{
457    painter->save();
458    painter->setRenderHint( QPainter::TextAntialiasing );
459    painter->setRenderHint( QPainter::SmoothPixmapTransform );
460
461    QRect avatarRect = rect.adjusted( 22, rect.height() - 48, 0, -16 );
462    QRect textRect = avatarRect.adjusted( avatarRect.height() + 24, 0, -32, 0 );
463    avatarRect.setWidth( avatarRect.height() );
464
465    QPixmap avatar = item->source()->avatar( TomahawkUtils::RoundedCorners, avatarRect.size(), true ) ;
466    painter->drawPixmap( avatarRect, avatar );
467
468    QTextOption to = QTextOption( Qt::AlignVCenter );
469    to.setWrapMode( QTextOption::NoWrap );
470    QFont f = painter->font();
471    f.setPointSize( TomahawkUtils::defaultFontSize() + 2 );
472    painter->setFont( f );
473
474    painter->setOpacity( 0.8 );
475    painter->setPen( QColor( "#000000" ) );
476    painter->drawText( textRect, painter->fontMetrics().elidedText( item->source()->friendlyName(), Qt::ElideRight, textRect.width() ), to );
477
478    painter->setOpacity( 0.15 );
479    painter->setBrush( QColor( "#000000" ) );
480    painter->drawRect( rect.adjusted( 0, rect.height() - 8, -32, -8 ) );
481
482    painter->restore();
483
484    return rect;
485}
486
487
488QRect
489PlaylistItemDelegate::drawTrack( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QRect& rect, PlayableItem* item ) const
490{
491    const track_ptr track = item->query()->track();
492    const bool hasOnlineResults = ( item->query()->numResults( true ) > 0 );
493
494    painter->save();
495    painter->setRenderHint( QPainter::TextAntialiasing );
496
497    int rightMargin = 32;
498    if ( !index.parent().isValid() )
499        rightMargin = 0;
500
501    if ( option.state & QStyle::State_Selected )
502    {
503        painter->setPen( TomahawkStyle::SELECTION_BACKGROUND );
504        painter->setBrush( TomahawkStyle::SELECTION_BACKGROUND );
505        painter->drawRect( rect.adjusted( 0, 4, -rightMargin, -4 ) );
506    }
507    painter->setPen( TomahawkStyle::SELECTION_FOREGROUND );
508    painter->setFont( m_demiBoldFont );
509
510    QRect r = rect.adjusted( 32, 6, -32 -rightMargin, -6 );
511    const int margin = 8;
512
513    const int numberWidth = painter->fontMetrics().width( "00" ) + 32;
514    const int durationWidth = painter->fontMetrics().width( "00:00" ) + 32;
515    int stateWidth = 0;
516
517    QRect numberRect = QRect( r.x(), r.y(), numberWidth, r.height() );
518    QRect extraRect = QRect( r.x() + r.width() - durationWidth, r.y(), durationWidth, r.height() );
519    QRect stateRect = extraRect.adjusted( 0, 0, 0, 0 );
520
521   if ( option.state & QStyle::State_Selected || hoveringOver() == index )
522    {
523        int h = extraRect.height() / 3;
524
525        if ( track->loved() )
526        {
527            painter->save();
528            painter->setOpacity( 0.5 );
529            QRect r = stateRect.adjusted( -16, extraRect.height() / 2 - h / 2, 0, 0 );
530            r.setHeight( h );
531            r.setWidth( r.height() );
532            painter->drawPixmap( r, ImageRegistry::instance()->pixmap( RESPATH "images/love.svg", r.size() ) );
533            painter->restore();
534
535            stateWidth += r.width() + 16;
536        }
537    }
538
539    QRect downloadButtonRect = stateRect.adjusted( -stateWidth -144, 6, 0, -6 );
540    downloadButtonRect.setWidth( 144 );
541    stateWidth += downloadButtonRect.width() + 16;
542    if ( DownloadButton::drawPrimitive( painter, downloadButtonRect, item->query(), m_hoveringOverDownloadButton == index ) )
543    {
544        m_downloadDropDownRects[ index ] = downloadButtonRect;
545    }
546
547    const int remWidth = r.width() - numberWidth - durationWidth;
548
549    QRect titleRect = QRect( numberRect.x() + numberRect.width(), r.y(), (double)remWidth * 0.5, r.height() );
550    QRect artistRect = QRect( titleRect.x() + titleRect.width(), r.y(), (double)remWidth * 0.5, r.height() );
551    if ( stateWidth > 0 )
552    {
553        // Make sure we don't draw over the state icons
554        artistRect.setWidth( artistRect.width() - stateWidth );
555    }
556
557    // draw title
558    qreal opacityCo = 1.0;
559    if ( !item->query()->playable() )
560        opacityCo = 0.5;
561
562    painter->setOpacity( 1.0 * opacityCo );
563    QString text = painter->fontMetrics().elidedText( track->track(), Qt::ElideRight, titleRect.width() - margin );
564    painter->drawText( titleRect, text, m_centerOption );
565
566    // draw artist
567    painter->setOpacity( 0.8 * opacityCo );
568    painter->setFont( m_normalFont );
569    text = painter->fontMetrics().elidedText( track->artist(), Qt::ElideRight, artistRect.width() - margin );
570
571    painter->save();
572    if ( m_hoveringOverArtist == index )
573    {
574        QFont f = painter->font();
575        f.setUnderline( true );
576        painter->setFont( f );
577    }
578    painter->drawText( artistRect, text, m_centerOption );
579    m_artistNameRects[ index ] = painter->fontMetrics().boundingRect( artistRect, Qt::AlignLeft | Qt::AlignVCenter, text );
580    painter->restore();
581
582    // draw number or source icon
583    if ( ( option.state & QStyle::State_Selected || hoveringOver() == index ) && item->query()->playable() )
584    {
585        const int iconHeight = numberRect.size().height() / 2;
586        const QRect sourceIconRect( numberRect.x(), numberRect.y() + ( numberRect.size().height() - iconHeight ) / 2, iconHeight, iconHeight );
587        painter->drawPixmap( sourceIconRect, item->query()->results().first()->sourceIcon( TomahawkUtils::Original, sourceIconRect.size() ) );
588    }
589    else
590    {
591        painter->setOpacity( 0.6 * opacityCo );
592        QString number = QString::number( index.row() + 1 );
593        if ( number.length() < 2 )
594            number = "0" + number;
595        painter->drawText( numberRect, number, m_centerOption );
596    }
597
598    if ( item->isPlaying() )
599    {
600        if ( m_nowPlaying != index )
601        {
602            connect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), SLOT( onPlaybackChange() ), Qt::UniqueConnection );
603            connect( AudioEngine::instance(), SIGNAL( stopped() ), SLOT( onPlaybackChange() ), Qt::UniqueConnection );
604            connect( AudioEngine::instance(), SIGNAL( timerMilliSeconds( qint64 ) ), SLOT( onAudioEngineTick( qint64 ) ), Qt::UniqueConnection );
605            m_nowPlaying = QPersistentModelIndex( index );
606        }
607
608        int h = extraRect.height() / 2;
609        QRect playIconRect = extraRect.adjusted( extraRect.width() - h - 8, h / 2, -8, -h / 2 );
610        playIconRect.setWidth( playIconRect.height() );
611        painter->drawPixmap( playIconRect, ImageRegistry::instance()->pixmap( RESPATH "images/play.svg", playIconRect.size() ) );
612
613        double duration = (double)AudioEngine::instance()->currentTrackTotalTime();
614        if ( duration <= 0 )
615            duration = item->query()->track()->duration() * 1000;
616
617        if ( duration > 0 )
618        {
619            painter->save();
620            painter->setPen( Qt::transparent );
621            painter->setBrush( QColor( "#ff004c" ));
622
623            QRect playBar = r.adjusted( 0, r.height() + 2, 0, 0 );
624            playBar.setHeight( 2 );
625            painter->setOpacity( 0.1 );
626            painter->drawRect( playBar );
627
628            playBar.setWidth( ( (double)AudioEngine::instance()->currentTime() / duration ) * (double)playBar.width() );
629            painter->setOpacity( 1 );
630            painter->drawRect( playBar );
631
632            painter->restore();
633        }
634    }
635    else if ( track->duration() > 0 )
636    {
637        painter->setOpacity( 0.5 * opacityCo );
638        painter->drawText( extraRect, TomahawkUtils::timeToString( track->duration() ), m_centerRightOption );
639    }
640
641    painter->restore();
642
643    return r;
644}
645
646
647bool
648PlaylistItemDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
649{
650    QStyledItemDelegate::editorEvent( event, model, option, index );
651
652    if ( event->type() != QEvent::MouseButtonRelease &&
653         event->type() != QEvent::MouseMove &&
654         event->type() != QEvent::Leave )
655    {
656        return false;
657    }
658
659    bool hoveringArtist = false;
660    bool hoveringInfo = false;
661    bool hoveringLove = false;
662    bool hoveringDownloadDropDown = false;
663    Tomahawk::source_ptr hoveredAvatar;
664    QRect hoveredAvatarRect;
665
666    if ( m_infoButtonRects.contains( index ) )
667    {
668        const QRect infoRect = m_infoButtonRects[ index ];
669        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
670        hoveringInfo = infoRect.contains( ev->pos() );
671    }
672    if ( m_artistNameRects.contains( index ) )
673    {
674        const QRect nameRect = m_artistNameRects[ index ];
675        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
676        hoveringArtist = nameRect.contains( ev->pos() );
677    }
678    if ( m_loveButtonRects.contains( index ) )
679    {
680        const QRect loveRect = m_loveButtonRects[ index ];
681        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
682        hoveringLove = loveRect.contains( ev->pos() );
683    }
684    if ( m_downloadDropDownRects.contains( index ) )
685    {
686        const QRect downloadDropDownRect = m_downloadDropDownRects[ index ];
687        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
688        hoveringDownloadDropDown = downloadDropDownRect.contains( ev->pos() );
689    }
690    if ( m_avatarBoxRects.contains( index ) )
691    {
692        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
693        for ( QHash< Tomahawk::source_ptr, QRect >::const_iterator it = m_avatarBoxRects[ index ].constBegin();
694              it != m_avatarBoxRects[ index ].constEnd(); ++it )
695        {
696            if ( it.value().contains( ev->pos() ) )
697            {
698                hoveredAvatar = it.key();
699                hoveredAvatarRect = it.value();
700                break;
701            }
702        }
703    }
704
705    if ( event->type() == QEvent::MouseMove )
706    {
707        if ( hoveringInfo || hoveringLove || hoveringArtist || hoveringDownloadDropDown )
708            m_view->setCursor( Qt::PointingHandCursor );
709        else
710            m_view->setCursor( Qt::ArrowCursor );
711
712        if ( !hoveredAvatar.isNull() )
713        {
714            QToolTip::showText( m_view->mapToGlobal( hoveredAvatarRect.bottomLeft() ),
715                                hoveredAvatar->friendlyName(),
716                                m_view,
717                                hoveredAvatarRect );
718        }
719
720        if ( hoveringArtist && m_hoveringOverArtist != index )
721        {
722            emit updateIndex( m_hoveringOverArtist );
723            emit updateIndex( index );
724            m_hoveringOverArtist = index;
725        }
726        if ( !hoveringArtist && m_hoveringOverArtist.isValid() )
727        {
728            emit updateIndex( m_hoveringOverArtist );
729            m_hoveringOverArtist = QModelIndex();
730        }
731        if ( hoveringDownloadDropDown && m_hoveringOverDownloadButton != index )
732        {
733            QPersistentModelIndex ti = m_hoveringOverDownloadButton;
734            m_hoveringOverDownloadButton = index;
735
736            PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
737            item->requestRepaint();
738            emit updateIndex( m_hoveringOverDownloadButton );
739        }
740        if ( !hoveringDownloadDropDown && m_hoveringOverDownloadButton.isValid() )
741        {
742            QPersistentModelIndex ti = m_hoveringOverDownloadButton;
743            m_hoveringOverDownloadButton = QModelIndex();
744
745            PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( ti ) );
746            item->requestRepaint();
747        }
748
749        if ( m_hoveringOver != index )
750        {
751            emit updateIndex( m_hoveringOver );
752            emit updateIndex( index );
753            m_hoveringOver = index;
754        }
755
756        // We return false here so the view can still decide to process/trigger things like D&D events
757        return false;
758    }
759
760    // reset mouse cursor. we switch to a pointing hand cursor when hovering a button
761    m_view->setCursor( Qt::ArrowCursor );
762
763    if ( event->type() == QEvent::MouseButtonRelease )
764    {
765        PlayableItem* item = m_model->sourceModel()->itemFromIndex( m_model->mapToSource( index ) );
766        if ( !item )
767            return false;
768
769        if ( hoveringArtist )
770        {
771            ViewManager::instance()->show( item->query()->track()->artistPtr() );
772        }
773        else if ( hoveringLove )
774        {
775            item->query()->queryTrack()->setLoved( !item->query()->queryTrack()->loved() );
776        }
777        else if ( hoveringDownloadDropDown || ( m_view->proxyModel()->style() == PlayableProxyModel::Locker && index.column() == PlayableModel::Download ) )
778        {
779            if ( DownloadButton::handleEditorEvent( event , m_view, m_model, index ) )
780                return true;
781        }
782        else if ( hoveringInfo )
783        {
784            if ( m_model->style() == PlayableProxyModel::SingleColumn )
785            {
786                if ( item->query() )
787                    ViewManager::instance()->show( item->query()->track()->toQuery() );
788            }
789            else
790            {
791                switch ( index.column() )
792                {
793                    case PlayableModel::Artist:
794                    {
795                        ViewManager::instance()->show( item->query()->track()->artistPtr() );
796                        break;
797                    }
798
799                    case PlayableModel::Album:
800                    {
801                        ViewManager::instance()->show( item->query()->track()->albumPtr() );
802                        break;
803                    }
804
805                    case PlayableModel::Track:
806                    {
807                        ViewManager::instance()->show( item->query()->track()->toQuery() );
808                        break;
809                    }
810
811                    default:
812                        break;
813                }
814            }
815        }
816
817        event->accept();
818        return true;
819    }
820
821    return false;
822}
823
824
825void
826PlaylistItemDelegate::resetHoverIndex()
827{
828    if ( !m_model || !m_hoveringOver.isValid() )
829        return;
830
831    QPersistentModelIndex idx = m_hoveringOver;
832
833    m_hoveringOver = QModelIndex();
834    m_hoveringOverArtist = QModelIndex();
835    m_hoveringOverDownloadButton = QModelIndex();
836    m_infoButtonRects.clear();
837    m_loveButtonRects.clear();
838    m_artistNameRects.clear();
839
840    QModelIndex itemIdx = m_model->mapToSource( idx );
841    if ( itemIdx.isValid() )
842    {
843        PlayableItem* item = m_model->sourceModel()->itemFromIndex( itemIdx );
844        if ( item )
845            item->requestRepaint();
846    }
847
848    emit updateIndex( idx );
849}
850
851
852void
853PlaylistItemDelegate::modelChanged()
854{
855    m_pixmaps.clear();
856}
857
858
859void
860PlaylistItemDelegate::doUpdateIndex( const QPersistentModelIndex& index )
861{
862    if ( index.isValid() )
863        emit updateIndex( index );
864}
865
866
867void
868PlaylistItemDelegate::onAudioEngineTick( qint64 /* ms */ )
869{
870    doUpdateIndex( m_nowPlaying );
871}
872
873
874void
875PlaylistItemDelegate::onPlaybackChange()
876{
877    disconnect( AudioEngine::instance(), SIGNAL( started( Tomahawk::result_ptr ) ), this, SLOT( onPlaybackChange() ) );
878    disconnect( AudioEngine::instance(), SIGNAL( stopped() ), this, SLOT( onPlaybackChange() ) );
879    disconnect( AudioEngine::instance(), SIGNAL( timerMilliSeconds( qint64 ) ), this, SLOT( onAudioEngineTick( qint64 ) ) );
880    doUpdateIndex( m_nowPlaying );
881    m_nowPlaying = QModelIndex();
882}