PageRenderTime 201ms CodeModel.GetById 53ms app.highlight 102ms RepoModel.GetById 39ms app.codeStats 0ms

/src/sourcetree/items/playlistitems.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 632 lines | 465 code | 133 blank | 34 comment | 90 complexity | a4d043da5c2b5a22aa739c5131105bd9 MD5 | raw file
  1/*
  2 *
  3 *    Copyright 2010-2012, Leo Franchi <lfranchi@kde.org>
  4 *
  5 *    This program is free software; you can redistribute it and/or modify
  6 *    it under the terms of the GNU General Public License as published by
  7 *    the Free Software Foundation; either version 2 of the License, or
  8 *    (at your option) any later version.
  9 *
 10 *    This program is distributed in the hope that it will be useful,
 11 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 12 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13 *    GNU General Public License for more details.
 14 *
 15 *    You should have received a copy of the GNU General Public License along
 16 *    with this program; if not, write to the Free Software Foundation, Inc.,
 17 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 18 */
 19
 20#include "PlaylistItems.h"
 21
 22#include "Query.h"
 23#include "ViewManager.h"
 24#include "playlist/dynamic/GeneratorInterface.h"
 25#include "playlist/PlaylistView.h"
 26#include "CategoryItems.h"
 27#include "SourceItem.h"
 28#include "utils/TomahawkUtils.h"
 29#include "utils/Logger.h"
 30#include "DropJob.h"
 31#include "Source.h"
 32#include "audio/AudioEngine.h"
 33
 34#include <QMimeData>
 35#include <QPainter>
 36
 37using namespace Tomahawk;
 38
 39
 40PlaylistItem::PlaylistItem( SourcesModel* mdl, SourceTreeItem* parent, const playlist_ptr& pl, int index )
 41    : SourceTreeItem( mdl, parent, SourcesModel::StaticPlaylist, index )
 42    , m_loaded( false )
 43    , m_canSubscribe( false )
 44    , m_showSubscribed( false )
 45    , m_playlist( pl )
 46{
 47    connect( pl.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ),
 48             SLOT( onPlaylistLoaded( Tomahawk::PlaylistRevision ) ), Qt::QueuedConnection );
 49    connect( pl.data(), SIGNAL( changed() ),
 50             SLOT( onUpdated() ), Qt::QueuedConnection );
 51
 52    m_icon = QIcon( RESPATH "images/playlist-icon.png" );
 53
 54    if( ViewManager::instance()->pageForPlaylist( pl ) )
 55        model()->linkSourceItemToPage( this, ViewManager::instance()->pageForPlaylist( pl ) );
 56
 57    if ( !m_playlist->updaters().isEmpty() )
 58        createOverlay();
 59}
 60
 61
 62QString
 63PlaylistItem::text() const
 64{
 65    return m_playlist->title();
 66}
 67
 68
 69Tomahawk::playlist_ptr
 70PlaylistItem::playlist() const
 71{
 72    return m_playlist;
 73}
 74
 75
 76void
 77PlaylistItem::onPlaylistLoaded( Tomahawk::PlaylistRevision revision )
 78{
 79    Q_UNUSED( revision );
 80
 81    m_loaded = true;
 82    emit updated();
 83}
 84
 85
 86void
 87PlaylistItem::onPlaylistChanged()
 88{
 89    emit updated();
 90}
 91
 92
 93int
 94PlaylistItem::peerSortValue() const
 95{
 96//    return m_playlist->createdOn();
 97    return 0;
 98}
 99
100
101int
102PlaylistItem::IDValue() const
103{
104    return m_playlist->createdOn();
105}
106
107
108bool
109PlaylistItem::isBeingPlayed() const
110{
111    if ( ViewPage* page = ViewManager::instance()->pageForPlaylist( m_playlist ) )
112    {
113        if ( AudioEngine::instance()->currentTrackPlaylist() == page->playlistInterface() )
114            return true;
115        if ( page->playlistInterface()->hasChildInterface( AudioEngine::instance()->currentTrackPlaylist() ) )
116            return true;
117    }
118    return false;
119}
120
121
122Qt::ItemFlags
123PlaylistItem::flags() const
124{
125    Qt::ItemFlags flags = SourceTreeItem::flags();
126    flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
127
128    if ( !m_loaded )
129        flags &= !Qt::ItemIsEnabled;
130    if ( playlist()->author()->isLocal() )
131        flags |= Qt::ItemIsEditable;
132
133    if ( playlist()->busy() )
134    {
135        flags &= !Qt::ItemIsEnabled;
136        flags &= !Qt::ItemIsEditable;
137        flags &= !Qt::ItemIsDropEnabled;
138    }
139
140    return flags;
141}
142
143
144void
145PlaylistItem::activate()
146{
147    ViewPage* p = ViewManager::instance()->show( m_playlist );
148    model()->linkSourceItemToPage( this, p );
149}
150
151
152void
153PlaylistItem::doubleClicked()
154{
155    ViewPage* p = ViewManager::instance()->currentPage();
156    if ( PlaylistView* view = dynamic_cast< PlaylistView* >( p ) )
157    {
158        view->startPlayingFromStart();
159    }
160}
161
162
163void
164PlaylistItem::setLoaded( bool loaded )
165{
166    m_loaded = loaded;
167}
168
169
170bool
171PlaylistItem::willAcceptDrag( const QMimeData* data ) const
172{
173    Q_UNUSED( data );
174    return !m_playlist.isNull() && m_playlist->author()->isLocal() && DropJob::acceptsMimeData( data, DropJob::Track ) && !m_playlist->busy();
175}
176
177PlaylistItem::DropTypes
178PlaylistItem::supportedDropTypes( const QMimeData* data ) const
179{
180    if ( data->hasFormat( "application/tomahawk.mixed" ) )
181    {
182        // If this is mixed but only queries/results, we can still handle them
183        bool mixedQueries = true;
184
185        QByteArray itemData = data->data( "application/tomahawk.mixed" );
186        QDataStream stream( &itemData, QIODevice::ReadOnly );
187        QString mimeType;
188        qlonglong val;
189
190        while ( !stream.atEnd() )
191        {
192            stream >> mimeType;
193            if ( mimeType != "application/tomahawk.query.list" &&
194                 mimeType != "application/tomahawk.result.list" )
195            {
196                mixedQueries = false;
197                break;
198            }
199            stream >> val;
200        }
201
202        if ( mixedQueries )
203            return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
204        else
205            return DropTypesNone;
206    }
207
208    if ( data->hasFormat( "application/tomahawk.query.list" ) )
209        return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
210    else if ( data->hasFormat( "application/tomahawk.result.list" ) )
211        return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
212    else if ( data->hasFormat( "application/tomahawk.metadata.album" ) )
213        return DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
214    else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) )
215        return DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
216    else if ( data->hasFormat( "text/plain" ) )
217    {
218        return DropTypesNone;
219    }
220    return DropTypesNone;
221}
222
223
224bool
225PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action )
226{
227    Q_UNUSED( action );
228
229    if ( m_playlist->busy() )
230        return false;
231
232    QList< Tomahawk::query_ptr > queries;
233
234    if ( data->hasFormat( "application/tomahawk.playlist.id" ) &&
235        data->data( "application/tomahawk.playlist.id" ) == m_playlist->guid() )
236        return false; // don't allow dropping on ourselves
237
238    if ( !DropJob::acceptsMimeData( data, DropJob::Track ) )
239        return false;
240
241    DropJob *dj = new DropJob();
242    dj->setDropTypes( DropJob::Track );
243    dj->setDropAction( DropJob::Append );
244
245    connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) );
246
247    if ( dropType() == DropTypeAllFromArtist )
248        dj->setGetWholeArtists( true );
249    if ( dropType() == DropTypeThisAlbum )
250        dj->setGetWholeAlbums( true );
251
252    if ( dropType() == DropTypeLocalItems )
253    {
254        dj->setGetWholeArtists( true );
255        dj->tracksFromMimeData( data, false, true );
256    }
257    else if ( dropType() == DropTypeTop50 )
258    {
259        dj->setGetWholeArtists( true );
260        dj->tracksFromMimeData( data, false, false, true );
261    }
262    else
263        dj->tracksFromMimeData( data, false, false );
264
265    // TODO can't know if it works or not yet...
266    return true;
267}
268
269
270void
271PlaylistItem::parsedDroppedTracks( const QList< query_ptr >& tracks )
272{
273    qDebug() << "adding" << tracks.count() << "tracks";
274    if ( tracks.count() && !m_playlist.isNull() && m_playlist->author()->isLocal() )
275    {
276        qDebug() << "on playlist:" << m_playlist->title() << m_playlist->guid() << m_playlist->currentrevision();
277
278        m_playlist->addEntries( tracks, m_playlist->currentrevision() );
279    }
280}
281
282
283void
284PlaylistItem::onUpdated()
285{
286    const bool newOverlay = createOverlay();
287    if ( !newOverlay && !m_overlaidIcon.isNull() )
288        m_overlaidIcon = QIcon();
289
290
291    emit updated();
292}
293
294bool
295PlaylistItem::collaborative() const
296{
297    Q_ASSERT( !m_playlist.isNull() );
298
299    if ( m_playlist->updaters().isEmpty() )
300        return false;
301
302    bool collaborative = false;
303
304    foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
305    {
306        if( !updater->collaborative() )
307            continue;
308        /// @note:  We only care for collaborations if in sync
309        if( !updater->sync() )
310            continue;
311        collaborative = updater->collaborative();
312    }
313
314    return collaborative;
315}
316
317
318bool
319PlaylistItem::createOverlay()
320{
321    Q_ASSERT( !m_playlist.isNull() );
322
323    if ( m_playlist->updaters().isEmpty() )
324        return false;
325
326    m_showSubscribed = false;
327    m_canSubscribe = false;
328
329    foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
330    {
331        if ( updater->canSubscribe() )
332        {
333            m_canSubscribe = true;
334            m_showSubscribed = updater->subscribed();
335            break;
336        }
337    }
338
339    if ( m_canSubscribe && m_showSubscribed && m_subscribedOnIcon.isNull() )
340        m_subscribedOnIcon = QPixmap( RESPATH "images/subscribe-on.png" );
341    else if ( m_canSubscribe && !m_showSubscribed && m_subscribedOffIcon.isNull() )
342        m_subscribedOffIcon = QPixmap( RESPATH "images/subscribe-off.png" );
343
344    QList< QPixmap > icons;
345    foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
346    {
347        if ( updater->sync() && !updater->typeIcon().isNull() )
348            icons << updater->typeIcon();
349    }
350
351    m_overlaidIcon = QIcon();
352    m_overlaidUpdaters = m_playlist->updaters();
353
354    if ( icons.isEmpty() )
355        return false;
356
357    // For now we only support up to 2 overlaid updater icons,
358    // we need to add smarter scaling etc to manage more at once
359    if ( icons.size() > 2 )
360        icons = icons.mid( 0, 2 );
361
362
363    QPixmap base = m_icon.pixmap( 48, 48 );
364    QPainter p( &base );
365    const int w = base.width() / 2;
366    QRect overlayRect( base.rect().right() - w, base.rect().height() - w, w, w );
367
368    foreach ( const QPixmap& overlay, icons )
369    {
370        p.drawPixmap( overlayRect, overlay );
371
372        // NOTE only works if icons.size == 2 as ensured above
373        overlayRect.moveLeft( 0 );
374    }
375
376
377    p.end();
378
379    m_overlaidIcon.addPixmap( base );
380
381    return true;
382}
383
384
385QIcon
386PlaylistItem::icon() const
387{
388    if ( !m_overlaidIcon.isNull() )
389        return m_overlaidIcon;
390    else
391        return m_icon;
392}
393
394
395bool
396PlaylistItem::setData( const QVariant& v, bool role )
397{
398    Q_UNUSED( role );
399
400    if ( m_playlist->author()->isLocal() )
401    {
402        m_playlist->rename( v.toString() );
403
404        return true;
405    }
406    return false;
407}
408
409SourceTreeItem*
410PlaylistItem::activateCurrent()
411{
412    if( ViewManager::instance()->pageForPlaylist( m_playlist ) == ViewManager::instance()->currentPage() )
413    {
414        model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() );
415        emit selectRequest( this );
416
417        return this;
418    }
419
420    return 0;
421}
422
423
424void
425PlaylistItem::setSubscribed( bool subscribed )
426{
427    Q_ASSERT( !m_overlaidUpdaters.isEmpty() );
428    if ( m_overlaidUpdaters.isEmpty() )
429    {
430        qWarning() << "NO playlist updater but got a toggle subscribed action on the playlist item!?";
431        return;
432    }
433    else if ( m_overlaidUpdaters.size() > 1 )
434    {
435        qWarning() << "Got TWO subscribed updaters at the same time? Toggling both... wtf";
436    }
437
438    foreach( PlaylistUpdaterInterface* updater, m_overlaidUpdaters )
439    {
440        updater->setSubscribed( subscribed );
441    }
442}
443
444
445DynamicPlaylistItem::DynamicPlaylistItem( SourcesModel* mdl, SourceTreeItem* parent, const dynplaylist_ptr& pl, int index )
446    : PlaylistItem( mdl, parent, pl.staticCast< Playlist >(), index )
447    , m_dynplaylist( pl )
448{
449    setRowType( m_dynplaylist->mode() == Static ? SourcesModel::AutomaticPlaylist : SourcesModel::Station );
450
451    connect( pl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ),
452             SLOT( onDynamicPlaylistLoaded( Tomahawk::DynamicPlaylistRevision ) ), Qt::QueuedConnection );
453
454    m_stationIcon = QIcon( RESPATH "images/station.png" );
455    m_automaticPlaylistIcon = QIcon( RESPATH "images/automatic-playlist.png" );
456
457    if( ViewManager::instance()->pageForDynPlaylist( pl ) )
458        model()->linkSourceItemToPage( this, ViewManager::instance()->pageForDynPlaylist( pl ) );
459}
460
461
462DynamicPlaylistItem::~DynamicPlaylistItem()
463{
464}
465
466
467void
468DynamicPlaylistItem::activate()
469{
470    ViewPage* p = ViewManager::instance()->show( m_dynplaylist );
471    model()->linkSourceItemToPage( this, p );
472}
473
474
475void
476DynamicPlaylistItem::onDynamicPlaylistLoaded( DynamicPlaylistRevision revision )
477{
478    setLoaded( true );
479    checkReparentHackNeeded( revision );
480    // END HACK
481    emit updated();
482}
483
484
485int
486DynamicPlaylistItem::peerSortValue() const
487{
488//    return m_dynplaylist->createdOn();
489    return 0;
490}
491
492
493int
494DynamicPlaylistItem::IDValue() const
495{
496    return m_dynplaylist->createdOn();
497}
498
499
500void
501DynamicPlaylistItem::checkReparentHackNeeded( const DynamicPlaylistRevision& revision )
502{
503    // HACK HACK HACK  workaround for an ugly hack where we have to be compatible with older tomahawks (pre-0.1.0) that created dynplaylists as OnDemand then changed them to Static if they were static.
504    //  we need to reparent this playlist to the correct category :-/.
505    CategoryItem* cat = qobject_cast< CategoryItem* >( parent() );
506
507//    qDebug() << "with category" << cat;
508//    if( cat ) qDebug() << "and cat type:" << cat->categoryType();
509    if( cat )
510    {
511        CategoryItem* from = cat;
512        CategoryItem* to = 0;
513        if( cat->categoryType() == SourcesModel::PlaylistsCategory && revision.mode == OnDemand ) { // WRONG
514            SourceItem* col = qobject_cast< SourceItem* >( cat->parent() );
515            to = col->stationsCategory();
516            if( !to ) { // you have got to be fucking kidding me
517                int fme = col->children().count();
518                col->beginRowsAdded( fme, fme );
519                to = new CategoryItem( model(), col, SourcesModel::StationsCategory, false );
520                col->appendChild( to ); // we f'ing know it's not local b/c we're not getting into this mess ourselves
521                col->endRowsAdded();
522                col->setStationsCategory( to );
523            }
524        } else if( cat->categoryType() == SourcesModel::StationsCategory && revision.mode == Static ) { // WRONG
525            SourceItem* col = qobject_cast< SourceItem* >( cat->parent() );
526            to = col->playlistsCategory();
527//            qDebug() << "TRYING TO HACK TO:" << to;
528            if( !to ) { // you have got to be fucking kidding me
529                int fme = col->children().count();
530                col->beginRowsAdded( fme, fme );
531                to = new CategoryItem( model(), col, SourcesModel::PlaylistsCategory, false );
532                col->appendChild( to ); // we f'ing know it's not local b/c we're not getting into this mess ourselves
533                col->endRowsAdded();
534                col->setPlaylistsCategory( to );
535            }
536        }
537        if( to ) {
538//            qDebug() << "HACKING! moving dynamic playlist from" << from->text() << "to:" << to->text();
539            // remove and add
540            int idx = from->children().indexOf( this );
541            from->beginRowsRemoved( idx, idx );
542            from->removeChild( this );
543            from->endRowsRemoved();
544
545            idx = to->children().count();
546            to->beginRowsAdded( idx, idx );
547            to->appendChild( this );
548            to->endRowsAdded();
549
550            setParentItem( to );
551        }
552    }
553}
554
555
556dynplaylist_ptr
557DynamicPlaylistItem::dynPlaylist() const
558{
559    return m_dynplaylist;
560}
561
562
563QString
564DynamicPlaylistItem::text() const
565{
566    return m_dynplaylist->title();
567}
568
569
570bool
571DynamicPlaylistItem::willAcceptDrag( const QMimeData* data ) const
572{
573    Q_UNUSED( data );
574    return false;
575}
576
577
578QIcon
579DynamicPlaylistItem::icon() const
580{
581    if ( m_dynplaylist->mode() == OnDemand )
582    {
583        return m_stationIcon;
584    }
585    else
586    {
587        return m_automaticPlaylistIcon;
588    }
589}
590
591SourceTreeItem*
592DynamicPlaylistItem::activateCurrent()
593{
594    if( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) == ViewManager::instance()->currentPage() )
595    {
596        model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() );
597        emit selectRequest( this );
598
599        return this;
600    }
601
602    return 0;
603}
604
605bool
606DynamicPlaylistItem::isBeingPlayed() const
607{
608    if ( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) )
609        return AudioEngine::instance()->currentTrackPlaylist() == ViewManager::instance()->pageForDynPlaylist( m_dynplaylist )->playlistInterface();
610    return false;
611}
612
613
614bool
615PlaylistItem::canSubscribe() const
616{
617    return m_canSubscribe;
618}
619
620
621bool
622PlaylistItem::subscribed() const
623{
624    return m_showSubscribed;
625}
626
627
628QPixmap
629PlaylistItem::subscribedIcon() const
630{
631    return m_showSubscribed ? m_subscribedOnIcon : m_subscribedOffIcon;
632}