PageRenderTime 375ms CodeModel.GetById 61ms app.highlight 218ms RepoModel.GetById 60ms app.codeStats 1ms

/src/sourcetree/sourcedelegate.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 852 lines | 672 code | 142 blank | 38 comment | 172 complexity | 4f8ca34a37d00a784da53056f3687fc9 MD5 | raw file
  1/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2 *
  3 *   Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4 *   Copyright 2011-2012, Leo Franchi <lfranchi@kde.org>
  5 *   Copyright 2011, Michael Zanetti <mzanetti@kde.org>
  6 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  7 *
  8 *   Tomahawk is free software: you can redistribute it and/or modify
  9 *   it under the terms of the GNU General Public License as published by
 10 *   the Free Software Foundation, either version 3 of the License, or
 11 *   (at your option) any later version.
 12 *
 13 *   Tomahawk is distributed in the hope that it will be useful,
 14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16 *   GNU General Public License for more details.
 17 *
 18 *   You should have received a copy of the GNU General Public License
 19 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 20 */
 21
 22#include "SourceDelegate.h"
 23
 24#include "items/SourceTreeItem.h"
 25#include "items/SourceItem.h"
 26#include "items/PlaylistItems.h"
 27#include "items/CategoryItems.h"
 28#include "items/TemporaryPageItem.h"
 29
 30#include "utils/TomahawkUtilsGui.h"
 31#include "audio/AudioEngine.h"
 32#include "AnimationHelper.h"
 33#include "Source.h"
 34#include "TomahawkSettings.h"
 35#include "ActionCollection.h"
 36#include "ViewManager.h"
 37#include "ContextMenu.h"
 38
 39#include <QApplication>
 40#include <QPainter>
 41#include <QMouseEvent>
 42
 43
 44#define TREEVIEW_INDENT_ADD 12
 45
 46SourceDelegate::SourceDelegate( QAbstractItemView* parent )
 47    : QStyledItemDelegate( parent )
 48    , m_parent( parent )
 49    , m_lastClicked( -1 )
 50{
 51    m_dropTypeMap.insert( 0, SourceTreeItem::DropTypeThisTrack );
 52    m_dropTypeMap.insert( 1, SourceTreeItem::DropTypeThisAlbum );
 53    m_dropTypeMap.insert( 2, SourceTreeItem::DropTypeAllFromArtist );
 54    m_dropTypeMap.insert( 3, SourceTreeItem::DropTypeLocalItems );
 55    m_dropTypeMap.insert( 4, SourceTreeItem::DropTypeTop50 );
 56
 57    m_dropTypeTextMap.insert( 0, tr( "Track" ) );
 58    m_dropTypeTextMap.insert( 1, tr( "Album" ) );
 59    m_dropTypeTextMap.insert( 2, tr( "Artist" ) );
 60    m_dropTypeTextMap.insert( 3, tr( "Local" ) );
 61    m_dropTypeTextMap.insert( 4, tr( "Top 10" ) );
 62
 63    m_dropTypeImageMap.insert( 0, QPixmap( RESPATH "images/drop-song.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
 64    m_dropTypeImageMap.insert( 1, QPixmap( RESPATH "images/drop-album.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
 65    m_dropTypeImageMap.insert( 2, QPixmap( RESPATH "images/drop-all-songs.png" ).scaledToHeight( 32, Qt::SmoothTransformation ) );
 66    m_dropTypeImageMap.insert( 3, QPixmap( RESPATH "images/drop-local-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
 67    m_dropTypeImageMap.insert( 4, QPixmap( RESPATH "images/drop-top-songs.png" ).scaledToWidth( 32, Qt::SmoothTransformation ) );
 68
 69    m_dropMimeData = new QMimeData();
 70
 71    m_headphonesOff.load( RESPATH "images/headphones-off.png" );
 72    m_headphonesOn.load( RESPATH "images/headphones-sidebar.png" );
 73    m_realtimeLocked.load( RESPATH "images/closed-padlock.png" );
 74    m_realtimeUnlocked.load( RESPATH "images/open-padlock.png" );
 75    m_nowPlayingSpeaker.load( RESPATH "images/now-playing-speaker.png" );
 76    m_nowPlayingSpeakerDark.load( RESPATH "images/now-playing-speaker-dark.png" );
 77    m_collaborativeOn.load( RESPATH "images/green-dot.png" );
 78}
 79
 80
 81SourceDelegate::~SourceDelegate()
 82{
 83    delete m_dropMimeData;
 84}
 85
 86
 87QSize
 88SourceDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
 89{
 90    SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
 91    SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
 92
 93    if ( type == SourcesModel::Collection )
 94    {
 95        return QSize( option.rect.width(), option.fontMetrics.height() * 3.0 );
 96    }
 97    else if ( type == SourcesModel::Divider )
 98    {
 99        return QSize( option.rect.width(), 6 );
100    }
101    else if ( type == SourcesModel::Group )
102    {
103        int groupSpacer = index.row() > 0 ? option.fontMetrics.height() * 0.6 : option.fontMetrics.height() * 0.2;
104        return QSize( option.rect.width(), option.fontMetrics.height() + groupSpacer );
105    }
106    else if ( m_expandedMap.contains( index ) )
107    {
108        if ( !m_expandedMap.value( index )->initialized() )
109        {
110            int dropTypes = dropTypeCount( item );
111            QSize originalSize = QSize( option.rect.width(), option.fontMetrics.height() * 1.4 );
112            QSize targetSize = originalSize + QSize( 0, dropTypes == 0 ? 0 : 38 + option.fontMetrics.height() * 1.4 );
113            m_expandedMap.value( index )->initialize( originalSize, targetSize, 300 );
114            m_expandedMap.value( index )->expand();
115        }
116        QMetaObject::invokeMethod( m_parent, "update", Qt::QueuedConnection, Q_ARG( QModelIndex, index ) );
117        return m_expandedMap.value( index )->size();
118    }
119    else
120        return QSize( option.rect.width(), option.fontMetrics.height() * 1.4 ); //QStyledItemDelegate::sizeHint( option, index ) );
121}
122
123
124void
125SourceDelegate::paintDecorations( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
126{
127    SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
128    SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
129
130    // Paint the speaker icon next to the currently-playing playlist
131    const bool playable = ( type == SourcesModel::StaticPlaylist ||
132        type == SourcesModel::AutomaticPlaylist ||
133        type == SourcesModel::Station ||
134        type == SourcesModel::TemporaryPage ||
135        type == SourcesModel::LovedTracksPage ||
136        type == SourcesModel::GenericPage );
137    const bool playing = ( AudioEngine::instance()->isPlaying() || AudioEngine::instance()->isPaused() );
138
139    if ( playable && playing && item->isBeingPlayed() )
140    {
141        int iconW = option.rect.height() - 4;
142        if ( m_expandedMap.contains( index ) )
143        {
144            AnimationHelper* ah = m_expandedMap.value( index );
145            if ( ah->initialized() )
146            {
147                iconW = ah->originalSize().height() - 4;
148            }
149        }
150
151        QRect iconRect = QRect( 4, option.rect.y() + 2, iconW, iconW );
152        QPixmap speaker = option.state & QStyle::State_Selected ? m_nowPlayingSpeaker : m_nowPlayingSpeakerDark;
153        speaker = speaker.scaledToHeight( iconW, Qt::SmoothTransformation );
154        painter->drawPixmap( iconRect, speaker );
155    }
156}
157
158
159void
160SourceDelegate::paintCollection( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
161{
162    painter->save();
163
164    QFont normal = option.font;
165    QFont bold = option.font;
166    bold.setBold( true );
167
168    QFont figFont = bold;
169    figFont.setFamily( "Arial Bold" );
170    figFont.setWeight( QFont::Black );
171    figFont.setPointSize( normal.pointSize() - 1 );
172
173    SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
174    SourceItem* colItem = qobject_cast< SourceItem* >( item );
175    Q_ASSERT( colItem );
176    bool status = !( !colItem || colItem->source().isNull() || !colItem->source()->isOnline() );
177
178    QString tracks;
179    QString name = index.data().toString();
180    int figWidth = 0;
181
182    if ( status && colItem && !colItem->source().isNull() )
183    {
184        tracks = QString::number( colItem->source()->trackCount() );
185        figWidth = QFontMetrics( figFont ).width( tracks );
186        name = colItem->source()->friendlyName();
187    }
188
189    QRect iconRect = option.rect.adjusted( 4, 6, -option.rect.width() + option.rect.height() - 12 + 4, -6 );
190    QPixmap avatar = colItem->pixmap( iconRect.size() );
191    painter->drawPixmap( iconRect, avatar );
192
193    if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
194    {
195        painter->setPen( option.palette.color( QPalette::HighlightedText ) );
196    }
197
198    QRect textRect = option.rect.adjusted( iconRect.width() + 8, 6, -figWidth - 28, 0 );
199    if ( status || colItem->source().isNull() )
200        painter->setFont( bold );
201    QString text = painter->fontMetrics().elidedText( name, Qt::ElideRight, textRect.width() );
202    painter->drawText( textRect, text );
203
204    bool isPlaying = !( colItem->source()->currentTrack().isNull() );
205    QString desc = colItem->source()->textStatus();
206    if ( colItem->source().isNull() )
207        desc = tr( "All available tracks" );
208
209    painter->setFont( normal );
210    textRect = option.rect.adjusted( iconRect.width() + 8, option.rect.height() / 2, -figWidth - 24, -6 );
211
212    bool privacyOn = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::FullyPrivate;
213    if ( !colItem->source().isNull() && colItem->source()->isLocal() && privacyOn )
214    {
215        QRect pmRect = textRect;
216        pmRect.setRight( pmRect.left() + pmRect.height() );
217        ActionCollection::instance()->getAction( "togglePrivacy" )->icon().paint( painter, pmRect );
218        textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
219    }
220    if ( isPlaying || ( !colItem->source().isNull() && colItem->source()->isLocal() ) )
221    {
222        // Show a listen icon
223        QPixmap listenAlongPixmap;
224        QPixmap realtimeListeningAlongPixmap;
225        if ( index.data( SourcesModel::LatchedOnRole ).toBool() )
226        {
227            // Currently listening along
228            listenAlongPixmap = m_headphonesOn;
229            if ( !colItem->source()->isLocal() )
230            {
231                realtimeListeningAlongPixmap =
232                    colItem->source()->playlistInterface()->latchMode() == Tomahawk::PlaylistModes::RealTime ?
233                        m_realtimeLocked : m_realtimeUnlocked;
234            }
235        }
236        else if ( !colItem->source()->isLocal() )
237        {
238            listenAlongPixmap = m_headphonesOff;
239        }
240
241        if ( !listenAlongPixmap.isNull() )
242        {
243            QRect pmRect = textRect;
244            pmRect.setRight( pmRect.left() + pmRect.height() );
245            painter->drawPixmap( pmRect, listenAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
246            textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
247
248            m_headphoneRects[ index ] = pmRect;
249        }
250        else
251            m_headphoneRects.remove( index );
252
253        if ( !realtimeListeningAlongPixmap.isNull() )
254        {
255            QRect pmRect = textRect;
256            pmRect.setRight( pmRect.left() + pmRect.height() );
257            painter->drawPixmap( pmRect, realtimeListeningAlongPixmap.scaledToHeight( pmRect.height(), Qt::SmoothTransformation ) );
258            textRect.adjust( pmRect.width() + 3, 0, 0, 0 );
259
260            m_lockRects[ index ] = pmRect;
261        }
262        else
263            m_lockRects.remove( index );
264    }
265
266    textRect.adjust( 0, 0, 0, 2 );
267    text = painter->fontMetrics().elidedText( desc, Qt::ElideRight, textRect.width() - 8 );
268    QTextOption to( Qt::AlignVCenter );
269    to.setWrapMode( QTextOption::NoWrap );
270    painter->drawText( textRect, text, to );
271
272    if ( colItem->source() && colItem->source()->currentTrack() )
273        m_trackRects[ index ] = textRect;
274    else
275        m_trackRects.remove( index );
276
277    if ( status )
278    {
279        painter->setRenderHint( QPainter::Antialiasing );
280
281        QRect figRect = option.rect.adjusted( option.rect.width() - figWidth - 13, 0, -14, -option.rect.height() + option.fontMetrics.height() * 1.1 );
282        int hd = ( option.rect.height() - figRect.height() ) / 2;
283        figRect.adjust( 0, hd, 0, hd );
284
285        painter->setFont( figFont );
286
287        QColor figColor( 167, 183, 211 );
288        painter->setPen( figColor );
289        painter->setBrush( figColor );
290
291        TomahawkUtils::drawBackgroundAndNumbers( painter, tracks, figRect );
292    }
293
294    painter->restore();
295}
296
297
298void
299SourceDelegate::paintCategory( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
300{
301    painter->save();
302
303    QTextOption to( Qt::AlignVCenter );
304
305    painter->setPen( option.palette.color( QPalette::Base ) );
306    painter->setBrush( option.palette.color( QPalette::Base ) );
307    painter->drawRect( option.rect );
308    painter->setRenderHint( QPainter::Antialiasing );
309
310    painter->setPen( Qt::white );
311    painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
312    painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
313    painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
314
315    if ( option.state & QStyle::State_MouseOver )
316    {
317        QString text = tr( "Show" );
318        if ( option.state & QStyle::State_Open )
319            text = tr( "Hide" );
320
321        QFont font = option.font;
322        font.setBold( true );
323        painter->setFont( font );
324        QTextOption to( Qt::AlignVCenter | Qt::AlignRight );
325
326        // draw close icon
327        painter->setPen( Qt::white );
328        painter->drawText( option.rect.translated( -4, 1 ), text, to );
329        painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
330        painter->drawText( option.rect.translated( -4, 0 ), text, to );
331    }
332
333    painter->restore();
334}
335
336
337void
338SourceDelegate::paintGroup( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
339{
340    painter->save();
341
342    QFont font = painter->font();
343    font.setPointSize( option.font.pointSize() + 1 );
344    font.setBold( true );
345    painter->setFont( font );
346
347    QTextOption to( Qt::AlignBottom );
348
349    painter->setPen( option.palette.color( QPalette::Base ) );
350    painter->setBrush( option.palette.color( QPalette::Base ) );
351    painter->drawRect( option.rect );
352    painter->setRenderHint( QPainter::Antialiasing );
353
354    painter->setPen( Qt::white );
355    painter->drawText( option.rect.translated( 4, 1 ), index.data().toString().toUpper(), to );
356    painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
357    painter->drawText( option.rect.translated( 4, 0 ), index.data().toString().toUpper(), to );
358
359    if ( option.state & QStyle::State_MouseOver )
360    {
361        QString text = tr( "Show" );
362        if ( option.state & QStyle::State_Open )
363            text = tr( "Hide" );
364
365        QFont font = option.font;
366        font.setBold( true );
367        painter->setFont( font );
368        QTextOption to( Qt::AlignBottom | Qt::AlignRight );
369
370        // draw close icon
371        painter->setPen( Qt::white );
372        painter->drawText( option.rect.translated( -4, 1 ), text, to );
373        painter->setPen( TomahawkUtils::Colors::GROUP_HEADER );
374        painter->drawText( option.rect.translated( -4, 0 ), text, to );
375    }
376
377    painter->restore();
378}
379
380
381void
382SourceDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
383{
384    QStyleOptionViewItem o = option;
385    QStyleOptionViewItemV4 o3 = option;
386
387    painter->save();
388
389    SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
390    SourceTreeItem* item = index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >();
391    Q_ASSERT( item );
392
393    if ( ( option.state & QStyle::State_Enabled ) == QStyle::State_Enabled )
394    {
395        o.state = QStyle::State_Enabled;
396
397        if ( ( option.state & QStyle::State_MouseOver ) == QStyle::State_MouseOver )
398        {
399            o.state |= QStyle::State_MouseOver;
400            o3.state |= QStyle::State_MouseOver;
401        }
402
403        if ( ( option.state & QStyle::State_Open ) == QStyle::State_Open )
404        {
405            o.state |= QStyle::State_Open;
406        }
407
408        if ( ( option.state & QStyle::State_Selected ) == QStyle::State_Selected )
409        {
410            if ( type != SourcesModel::Collection )
411                o3.state |= QStyle::State_Selected;
412            else
413                o3.state &= ~QStyle::State_Selected;
414
415            o.palette.setColor( QPalette::Base, QColor( 0, 0, 0, 0 ) );
416            o.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
417            o3.palette.setColor( QPalette::Text, o.palette.color( QPalette::HighlightedText ) );
418        }
419    }
420
421    // shrink the indentations
422    {
423        int indentMult = 0;
424        QModelIndex counter = index;
425        while ( counter.parent().isValid() )
426        {
427            indentMult++;
428            counter = counter.parent();
429        }
430
431        int indentDelta = o.rect.x() - m_parent->viewport()->x();
432        o.rect.setX( o.rect.x() - indentDelta + indentMult * TREEVIEW_INDENT_ADD );
433        o3.rect.setX( 0 );
434    }
435
436    if ( type != SourcesModel::Group && type != SourcesModel::Category && type != SourcesModel::Divider )
437        QApplication::style()->drawControl( QStyle::CE_ItemViewItem, &o3, painter );
438
439    if ( type == SourcesModel::Collection )
440    {
441        paintCollection( painter, o, index );
442    }
443    else if ( ( type == SourcesModel::StaticPlaylist || type == SourcesModel::CategoryAdd ) &&
444              m_expandedMap.contains( index ) && m_expandedMap.value( index )->partlyExpanded() && dropTypeCount( item ) > 0 )
445    {
446        // Let Qt paint the original item. We add our stuff after it
447        o.state &= ~QStyle::State_Selected;
448        o.showDecorationSelected = false;
449        o.rect.adjust( 0, 0, 0, - option.rect.height() + m_expandedMap.value( index )->originalSize().height() );
450        QStyledItemDelegate::paint( painter, o, index );
451
452        // Get whole rect for the menu
453        QRect itemsRect = option.rect.adjusted( -option.rect.x(), m_expandedMap.value( index )->originalSize().height(), 0, 0 );
454        QPoint cursorPos = m_parent->mapFromGlobal( QCursor::pos() );
455        bool cursorInRect = itemsRect.contains( cursorPos );
456
457        // draw the background
458        if ( m_gradient.finalStop() != itemsRect.bottomLeft() )
459        {
460            m_gradient = QLinearGradient( itemsRect.topLeft(), itemsRect.bottomLeft() );
461            m_gradient.setColorAt( 0.0, Qt::white );
462            m_gradient.setColorAt( 0.9, QColor( 0x88, 0x88, 0x88 ) );
463            m_gradient.setColorAt( 1.0, QColor( 0x99, 0x99, 0x99 ) ); // dark grey
464        }
465
466        QPen pen = painter->pen();
467        painter->setPen( QPen( Qt::NoPen ) );
468        painter->setBrush( m_gradient );
469        painter->drawRect( itemsRect );
470
471        // calculate sizes for the icons
472        int totalCount = dropTypeCount( item );
473        int itemWidth = itemsRect.width() / totalCount;
474        int iconSpacing = ( itemWidth - 32 ) / 2;
475
476        // adjust to one single entry
477        itemsRect.adjust( 0, 0, -itemsRect.width() + itemWidth, 0 );
478
479        pen.setColor( Qt::white );
480        painter->setPen( pen );
481
482        QFont font = painter->font();
483        font.setPointSize( option.font.pointSize() - 1 );
484        painter->setFont( font );
485        QFont fontBold = painter->font();
486        fontBold.setBold( true );
487
488        QRect textRect;
489        QRect imageRect;
490        SourceTreeItem::DropTypes dropTypes = item->supportedDropTypes( m_dropMimeData );
491
492        int count = 0;
493        for ( int i = 0; i < 5; ++i )
494        {
495            if ( !dropTypes.testFlag( m_dropTypeMap.value( i ) ) )
496                continue;
497
498            if ( count > 0 )
499                itemsRect.adjust( itemWidth, 0, itemWidth, 0 );
500
501            if ( itemsRect.contains( cursorPos ) | !cursorInRect )
502            {
503                painter->setFont( fontBold );
504                m_hoveredDropType = m_dropTypeMap.value( i );
505                cursorInRect = true;
506            }
507            else
508                painter->setFont( font );
509
510            int textSpacing = ( itemWidth - painter->fontMetrics().width( m_dropTypeTextMap.value( i ) ) ) / 2;
511            textRect = itemsRect.adjusted( textSpacing - 1, itemsRect.height() - painter->fontMetrics().height() - 2, 0, 0 );
512            painter->drawText( textRect, m_dropTypeTextMap.value( i ) );
513
514            int maxHeight = itemsRect.height() - textRect.height() - 2;
515            int verticalOffset = qMax( 0, maxHeight - 32 );
516            if ( itemsRect.bottom() - textRect.height() - 2 > itemsRect.top() )
517            {
518                imageRect = itemsRect.adjusted( iconSpacing, verticalOffset, -iconSpacing, -textRect.height() - 2 );
519                painter->drawPixmap( imageRect.x(), imageRect.y(), m_dropTypeImageMap.value( i ).copy( 0, 32 - imageRect.height(), 32, imageRect.height() ) );
520            }
521
522            count++;
523        }
524    }
525    else if ( type == SourcesModel::Group )
526    {
527        paintGroup( painter, o3, index );
528    }
529    else if ( type == SourcesModel::Category )
530    {
531        paintCategory( painter, o, index );
532    }
533    else if ( type == SourcesModel::Divider )
534    {
535        QRect middle = o.rect.adjusted( 0, 2, 0, -2 );
536        painter->setRenderHint( QPainter::Antialiasing, false );
537
538        QColor bgcolor = o3.palette.color( QPalette::Base );
539
540        painter->setPen( bgcolor.darker( 120 ) );
541        painter->drawLine( middle.topLeft(), middle.topRight() );
542        painter->setPen( bgcolor.lighter( 120 ) );
543        painter->drawLine( middle.bottomLeft(), middle.bottomRight() );
544    }
545    else
546    {
547        o.state &= ~QStyle::State_MouseOver;
548        if ( !index.parent().parent().isValid() )
549            o.rect.adjust( 7, 0, 0, 0 );
550
551        if ( type == SourcesModel::TemporaryPage )
552        {
553            TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( item );
554            Q_ASSERT( gpi );
555
556            if ( gpi && o3.state & QStyle::State_MouseOver )
557            {
558                int padding = 3;
559                m_iconHeight = ( o3.rect.height() - 2 * padding );
560
561                o.rect.adjust( 0, 0, -( padding + m_iconHeight ), 0 );
562                QStyledItemDelegate::paint( painter, o, index );
563
564                // draw close icon
565                QPixmap p( RESPATH "images/list-remove.png" );
566                p = p.scaledToHeight( m_iconHeight, Qt::SmoothTransformation );
567
568                QRect r( o3.rect.right() - padding - m_iconHeight, padding + o3.rect.y(), m_iconHeight, m_iconHeight );
569                painter->drawPixmap( r, p );
570            }
571            else
572                QStyledItemDelegate::paint( painter, o, index );
573        }
574        else if ( type == SourcesModel::StaticPlaylist )
575        {
576            QStyledItemDelegate::paint( painter, o, index );
577
578            PlaylistItem* plItem = qobject_cast< PlaylistItem* >( item );
579            if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
580            {
581                const int padding = 2;
582                const int imgWidth = o.rect.height() - 2*padding;
583
584                const QPixmap icon = plItem->subscribedIcon().scaled( imgWidth, imgWidth, Qt::KeepAspectRatio, Qt::SmoothTransformation );
585
586                const QRect subRect( o.rect.right() - padding - imgWidth, o.rect.top() + padding, imgWidth, imgWidth );
587                painter->drawPixmap( subRect, icon );
588            }
589
590            if ( plItem->collaborative() )
591            {
592                const int imgWidth = m_collaborativeOn.size().width();
593                const QRect subRect( o.rect.left(), o.rect.top(), imgWidth, imgWidth );
594                painter->drawPixmap( subRect, m_collaborativeOn );
595
596            }
597        }
598        else
599            QStyledItemDelegate::paint( painter, o, index );
600    }
601
602    paintDecorations( painter, o3, index );
603
604    painter->restore();
605}
606
607
608void
609SourceDelegate::updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index ) const
610{
611    if ( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist )
612        editor->setGeometry( option.rect.adjusted( 20, 0, 0, 0 ) );
613    else
614        QStyledItemDelegate::updateEditorGeometry( editor, option, index );
615
616    editor->setGeometry( editor->geometry().adjusted( 2 * TREEVIEW_INDENT_ADD, 0, 0, 0 ) );
617}
618
619
620bool
621SourceDelegate::editorEvent( QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index )
622{
623    bool hoveringTrack = false;
624    if ( m_trackRects.contains( index ) )
625    {
626        const QRect trackRect = m_trackRects[ index ];
627        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
628        hoveringTrack = trackRect.contains( ev->pos() );
629    }
630
631    bool lockRectContainsClick = false, headphonesRectContainsClick = false;
632    if ( m_headphoneRects.contains( index ) )
633    {
634        const QRect headphoneRect = m_headphoneRects[ index ];
635        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
636        headphonesRectContainsClick = headphoneRect.contains( ev->pos() );
637    }
638    if ( m_lockRects.contains( index ) )
639    {
640        const QRect lockRect = m_lockRects[ index ];
641        const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
642        lockRectContainsClick = lockRect.contains( ev->pos() );
643    }
644
645    if ( event->type() == QEvent::MouseMove )
646    {
647        if ( hoveringTrack || lockRectContainsClick || headphonesRectContainsClick )
648            m_parent->setCursor( Qt::PointingHandCursor );
649        else
650            m_parent->setCursor( Qt::ArrowCursor );
651    }
652
653    if ( event->type() == QEvent::MouseButtonRelease || event->type() == QEvent::MouseButtonPress )
654    {
655        SourcesModel::RowType type = static_cast< SourcesModel::RowType >( index.data( SourcesModel::SourceTreeItemTypeRole ).toInt() );
656        if ( type == SourcesModel::TemporaryPage )
657        {
658            TemporaryPageItem* gpi = qobject_cast< TemporaryPageItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
659            Q_ASSERT( gpi );
660            QMouseEvent* ev = static_cast< QMouseEvent* >( event );
661
662            QStyleOptionViewItemV4 o = option;
663            initStyleOption( &o, index );
664            int padding = 3;
665            QRect r ( o.rect.right() - padding - m_iconHeight, padding + o.rect.y(), m_iconHeight, m_iconHeight );
666
667            if ( r.contains( ev->pos() ) )
668            {
669                if ( event->type() == QEvent::MouseButtonRelease )
670                {
671                    gpi->removeFromList();
672
673                    // Send a new mouse event to the view, since if the mouse is now over another item's delete area we want it to show up
674                    QMouseEvent* ev = new QMouseEvent( QEvent::MouseMove, m_parent->viewport()->mapFromGlobal( QCursor::pos() ), Qt::NoButton, Qt::NoButton, Qt::NoModifier );
675                    QApplication::postEvent( m_parent->viewport(), ev );
676                }
677
678                return true;
679            }
680        }
681        else if ( type == SourcesModel::Collection )
682        {
683            SourceItem* colItem = qobject_cast< SourceItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
684            Q_ASSERT( colItem );
685
686            if ( hoveringTrack && colItem->source() && colItem->source()->currentTrack() )
687            {
688                const QMouseEvent* ev = static_cast< QMouseEvent* >( event );
689                if ( event->type() == QEvent::MouseButtonRelease && ev->button() == Qt::LeftButton )
690                {
691                    ViewManager::instance()->show( colItem->source()->currentTrack() );
692                    return true;
693                }
694                else if ( event->type() == QEvent::MouseButtonPress && ev->button() == Qt::RightButton )
695                {
696                    Tomahawk::ContextMenu* contextMenu = new Tomahawk::ContextMenu( m_parent );
697                    contextMenu->setQuery( colItem->source()->currentTrack() );
698                    contextMenu->exec( QCursor::pos() );
699                    return true;
700                }
701            }
702
703            if ( !colItem->source().isNull() && !colItem->source()->currentTrack().isNull() && !colItem->source()->isLocal() )
704            {
705                if ( headphonesRectContainsClick || lockRectContainsClick )
706                {
707                    if ( event->type() == QEvent::MouseButtonRelease )
708                    {
709                        if ( headphonesRectContainsClick )
710                        {
711                            if ( index.data( SourcesModel::LatchedOnRole ).toBool() )
712                                // unlatch
713                                emit latchOff( colItem->source() );
714                            else
715                                emit latchOn( colItem->source() );
716                        }
717                        else // it's in the lock rect
718                            emit toggleRealtimeLatch( colItem->source(), !index.data( SourcesModel::LatchedRealtimeRole ).toBool() );
719                    }
720                    return true;
721                }
722            }
723        }
724        else if ( event->type() == QEvent::MouseButtonRelease && type == SourcesModel::StaticPlaylist )
725        {
726            PlaylistItem* plItem = qobject_cast< PlaylistItem* >( index.data( SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
727            Q_ASSERT( plItem );
728
729            QMouseEvent* mev = static_cast< QMouseEvent* >( event );
730            if ( plItem->canSubscribe() && !plItem->subscribedIcon().isNull() )
731            {
732                const int padding = 2;
733                const int imgWidth = option.rect.height() - 2*padding;
734                const QRect subRect( option.rect.right() - padding - imgWidth, option.rect.top() + padding, imgWidth, imgWidth );
735
736                if ( subRect.contains( mev->pos() ) )
737                {
738                    // Toggle playlist subscription
739                    plItem->setSubscribed( !plItem->subscribed() );
740                }
741            }
742        }
743    }
744
745    // We emit our own clicked() signal instead of relying on QTreeView's, because that is fired whether or not a delegate accepts
746    // a mouse press event. Since we want to swallow click events when they are on headphones other action items, here we make sure we only
747    // emit if we really want to
748    if ( event->type() == QEvent::MouseButtonRelease )
749    {
750        if ( m_lastClicked == -1 )
751        {
752            m_lastClicked = QDateTime::currentMSecsSinceEpoch();
753            emit clicked( index );
754        }
755        else
756        {
757            qint64 elapsed = QDateTime::currentMSecsSinceEpoch() - m_lastClicked;
758            if ( elapsed < QApplication::doubleClickInterval() )
759            {
760                m_lastClicked = -1;
761                emit doubleClicked( index );
762            } else
763            {
764                m_lastClicked = QDateTime::currentMSecsSinceEpoch();
765                emit clicked( index );
766            }
767        }
768    }
769
770    return QStyledItemDelegate::editorEvent( event, model, option, index );
771}
772
773
774int
775SourceDelegate::dropTypeCount( SourceTreeItem* item ) const
776{
777    int menuCount = 0;
778    if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisTrack ) )
779        menuCount++;
780
781    if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeThisAlbum ) )
782        menuCount++;
783
784    if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeAllFromArtist ) )
785        menuCount++;
786
787    if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeLocalItems ) )
788        menuCount++;
789
790    if ( item->supportedDropTypes( m_dropMimeData ).testFlag( SourceTreeItem::DropTypeTop50 ) )
791        menuCount++;
792
793    return menuCount;
794}
795
796
797SourceTreeItem::DropType
798SourceDelegate::hoveredDropType() const
799{
800    return m_hoveredDropType;
801}
802
803
804void
805SourceDelegate::hovered( const QModelIndex& index, const QMimeData* mimeData )
806{
807    if ( !index.isValid() )
808    {
809        foreach ( AnimationHelper *helper, m_expandedMap )
810        {
811            helper->collapse();
812        }
813        return;
814    }
815
816    if ( !m_expandedMap.contains( index ) )
817    {
818        foreach ( AnimationHelper *helper, m_expandedMap )
819        {
820            helper->collapse();
821        }
822
823        m_newDropHoverIndex = index;
824        m_dropMimeData->clear();
825        foreach ( const QString &mimeDataFormat, mimeData->formats() )
826        {
827            m_dropMimeData->setData( mimeDataFormat, mimeData->data( mimeDataFormat ) );
828        }
829
830        m_expandedMap.insert( m_newDropHoverIndex, new AnimationHelper( m_newDropHoverIndex ) );
831        connect( m_expandedMap.value( m_newDropHoverIndex ), SIGNAL( finished( QModelIndex ) ), SLOT( animationFinished( QModelIndex ) ) );
832    }
833    else
834        qDebug() << "expandedMap already contains index" << index;
835}
836
837
838void
839SourceDelegate::dragLeaveEvent()
840{
841    foreach ( AnimationHelper* helper, m_expandedMap )
842    {
843        helper->collapse( true );
844    }
845}
846
847
848void
849SourceDelegate::animationFinished( const QModelIndex& index )
850{
851    delete m_expandedMap.take( index );
852}