PageRenderTime 300ms CodeModel.GetById 81ms app.highlight 170ms RepoModel.GetById 41ms app.codeStats 0ms

/src/sourcetree/sourcesmodel.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 683 lines | 485 code | 140 blank | 58 comment | 66 complexity | 5aebd121ef316f3fcadee48d9b3bab13 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 2010-2011, Leo Franchi <lfranchi@kde.org>
  5 *   Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.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 "sourcetree/SourcesModel.h"
 22
 23#include "sourcetree/items/SourceTreeItem.h"
 24#include "sourcetree/items/SourceItem.h"
 25#include "sourcetree/items/GroupItem.h"
 26#include "sourcetree/items/GenericPageItems.h"
 27#include "sourcetree/items/HistoryItem.h"
 28#include "sourcetree/items/LovedTracksItem.h"
 29#include "SourceList.h"
 30#include "Playlist.h"
 31#include "Collection.h"
 32#include "Source.h"
 33#include "ViewManager.h"
 34
 35#include "utils/Logger.h"
 36#include "GlobalActionManager.h"
 37#include "DropJob.h"
 38#include "items/PlaylistItems.h"
 39#include "playlist/TreeView.h"
 40#include "playlist/PlaylistView.h"
 41#include "playlist/dynamic/widgets/DynamicWidget.h"
 42
 43#include <QMimeData>
 44#include <QSize>
 45
 46#include <boost/bind.hpp>
 47
 48using namespace Tomahawk;
 49
 50
 51SourcesModel::SourcesModel( QObject* parent )
 52    : QAbstractItemModel( parent )
 53    , m_rootItem( 0 )
 54    , m_viewPageDelayedCacheItem( 0 )
 55{
 56    m_rootItem = new SourceTreeItem( this, 0, Invalid );
 57
 58    appendGroups();
 59    onSourcesAdded( SourceList::instance()->sources() );
 60
 61    connect( SourceList::instance(), SIGNAL( sourceAdded( Tomahawk::source_ptr ) ), SLOT( onSourceAdded( Tomahawk::source_ptr ) ) );
 62    connect( SourceList::instance(), SIGNAL( sourceRemoved( Tomahawk::source_ptr ) ), SLOT( onSourceRemoved( Tomahawk::source_ptr ) ) );
 63    connect( ViewManager::instance(), SIGNAL( viewPageActivated( Tomahawk::ViewPage* ) ), this, SLOT( viewPageActivated( Tomahawk::ViewPage* ) ) );
 64}
 65
 66
 67SourcesModel::~SourcesModel()
 68{
 69    delete m_rootItem;
 70}
 71
 72
 73QString
 74SourcesModel::rowTypeToString( RowType type )
 75{
 76    switch ( type )
 77    {
 78        case Group:
 79            return tr( "Group" );
 80
 81        case Collection:
 82            return tr( "Collection" );
 83
 84        case StaticPlaylist:
 85            return tr( "Playlist" );
 86
 87        case AutomaticPlaylist:
 88            return tr( "Automatic Playlist" );
 89
 90        case Station:
 91            return tr( "Station" );
 92
 93        default:
 94            return QString( "Unknown" );
 95    }
 96}
 97
 98
 99QVariant
100SourcesModel::data( const QModelIndex& index, int role ) const
101{
102    if ( !index.isValid() )
103        return QVariant();
104
105    SourceTreeItem* item = itemFromIndex( index );
106    if ( !item )
107        return QVariant();
108
109    switch ( role )
110    {
111    case Qt::SizeHintRole:
112        return QSize( 0, 18 );
113    case SourceTreeItemRole:
114        return QVariant::fromValue< SourceTreeItem* >( item );
115    case SourceTreeItemTypeRole:
116        return item->type();
117    case Qt::DisplayRole:
118    case Qt::EditRole:
119        return item->text();
120    case Qt::DecorationRole:
121        return item->icon();
122    case SourcesModel::SortRole:
123        return item->peerSortValue();
124    case SourcesModel::IDRole:
125        return item->IDValue();
126    case SourcesModel::LatchedOnRole:
127    {
128        if ( item->type() == Collection )
129        {
130            SourceItem* cItem = qobject_cast< SourceItem* >( item );
131            return cItem->localLatchedOn();
132        }
133        return false;
134    }
135    case SourcesModel::LatchedRealtimeRole:
136    {
137        if ( item->type() == Collection )
138        {
139            SourceItem* cItem = qobject_cast< SourceItem* >( item );
140            return cItem->localLatchMode() == Tomahawk::PlaylistModes::RealTime;
141        }
142        return false;
143    }
144    case SourcesModel::CustomActionRole:
145    {
146        return QVariant::fromValue< QList< QAction* > >( item->customActions() );
147    }
148    case Qt::ToolTipRole:
149        if ( !item->tooltip().isEmpty() )
150            return item->tooltip();
151    }
152    return QVariant();
153}
154
155
156int
157SourcesModel::columnCount( const QModelIndex& ) const
158{
159    return 1;
160}
161
162
163int
164SourcesModel::rowCount( const QModelIndex& parent ) const
165{
166    if( !parent.isValid() )
167    {
168        return m_rootItem->children().count();
169    }
170
171    return itemFromIndex( parent )->children().count();
172}
173
174
175QModelIndex
176SourcesModel::parent( const QModelIndex& child ) const
177{
178    if( !child.isValid() )
179    {
180        return QModelIndex();
181    }
182
183    SourceTreeItem* node = itemFromIndex( child );
184    SourceTreeItem* parent = node->parent();
185    if( parent == m_rootItem )
186        return QModelIndex();
187
188    return createIndex( rowForItem( parent ), 0, parent );
189}
190
191
192QModelIndex
193SourcesModel::index( int row, int column, const QModelIndex& parent ) const
194{
195    if( row < 0 || column < 0 )
196        return QModelIndex();
197
198    if( hasIndex( row, column, parent ) )
199    {
200        SourceTreeItem *parentNode = itemFromIndex( parent );
201        SourceTreeItem *childNode = parentNode->children().at( row );
202        return createIndex( row, column, childNode );
203    }
204
205    return QModelIndex();
206
207}
208
209
210bool
211SourcesModel::setData( const QModelIndex& index, const QVariant& value, int role )
212{
213    SourceTreeItem* item = itemFromIndex( index );
214    return item->setData( value, role );
215}
216
217
218QStringList
219SourcesModel::mimeTypes() const
220{
221    return DropJob::mimeTypes();
222}
223
224
225QMimeData*
226SourcesModel::mimeData( const QModelIndexList& ) const
227{
228    // TODO
229    return new QMimeData();
230}
231
232
233bool
234SourcesModel::dropMimeData( const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent )
235{
236    SourceTreeItem* item = 0;
237//    qDebug() << "Got mime data dropped:" << row << column << parent << itemFromIndex( parent )->text();
238    if( row == -1 && column == -1 )
239        item = itemFromIndex( parent );
240    else if( column == 0 )
241        item = itemFromIndex( index( row, column, parent ) );
242    else if( column == -1 ) // column is -1, that means the drop is happening "below" the indices. that means we actually want the one before it
243        item = itemFromIndex( index( row - 1, 0, parent ) );
244
245    Q_ASSERT( item );
246
247//    qDebug() << "Dropping on:" << item->text();
248    return item->dropMimeData( data, action );
249}
250
251
252Qt::DropActions
253SourcesModel::supportedDropActions() const
254{
255#ifdef Q_WS_MAC
256    return Qt::CopyAction | Qt::MoveAction;
257#else
258    return Qt::CopyAction;
259#endif
260}
261
262
263Qt::ItemFlags
264SourcesModel::flags( const QModelIndex& index ) const
265{
266    if ( index.isValid() )
267    {
268        return itemFromIndex( index )->flags();
269    }
270    else
271        return 0;
272}
273
274
275void
276SourcesModel::appendGroups()
277{
278    beginInsertRows( QModelIndex(), rowCount(), rowCount() + 3 );
279
280    GroupItem* browse = new GroupItem( this, m_rootItem, tr( "Browse" ), 0 );
281    new HistoryItem( this, m_rootItem, tr( "Search History" ), 1 );
282//    new SourceTreeItem( this, m_rootItem, SourcesModel::Divider, 2 );
283    m_myMusicGroup = new GroupItem( this, m_rootItem, tr( "My Music" ), 3 );
284
285    GenericPageItem* dashboard = new GenericPageItem( this, browse, tr( "Dashboard" ), QIcon( RESPATH "images/dashboard.png" ),
286                                                      boost::bind( &ViewManager::showWelcomePage, ViewManager::instance() ),
287                                                      boost::bind( &ViewManager::welcomeWidget, ViewManager::instance() ) );
288    dashboard->setSortValue( 0 );
289
290    // super collection
291    GenericPageItem* sc = new GenericPageItem( this, browse, tr( "SuperCollection" ), QIcon( RESPATH "images/supercollection.png" ),
292                                                  boost::bind( &ViewManager::showSuperCollection, ViewManager::instance() ),
293                                                  boost::bind( &ViewManager::superCollectionView, ViewManager::instance() ) );
294    sc->setSortValue( 1 );
295
296    // browse section
297    LovedTracksItem* loved = new LovedTracksItem( this, browse );
298    loved->setSortValue( 2 );
299
300    GenericPageItem* recent = new GenericPageItem( this, browse, tr( "Recently Played" ), QIcon( RESPATH "images/recently-played.png" ),
301                                                   boost::bind( &ViewManager::showRecentPlaysPage, ViewManager::instance() ),
302                                                   boost::bind( &ViewManager::recentPlaysWidget, ViewManager::instance() ) );
303    recent->setSortValue( 3 );
304
305    GenericPageItem* hot = new GenericPageItem( this, browse, tr( "Charts" ), QIcon( RESPATH "images/charts.png" ),
306                                                boost::bind( &ViewManager::showWhatsHotPage, ViewManager::instance() ),
307                                                boost::bind( &ViewManager::whatsHotWidget, ViewManager::instance() ) );
308    hot->setSortValue( 4 );
309
310    GenericPageItem* newReleases = new GenericPageItem( this, browse, tr( "New Releases" ), QIcon( RESPATH "images/new-releases.png" ),
311                                                boost::bind( &ViewManager::showNewReleasesPage, ViewManager::instance() ),
312                                                boost::bind( &ViewManager::newReleasesWidget, ViewManager::instance() ) );
313    newReleases->setSortValue( 5 );
314
315    m_collectionsGroup = new GroupItem( this, m_rootItem, tr( "Friends" ), 4 );
316
317    endInsertRows();
318}
319
320
321void
322SourcesModel::appendItem( const Tomahawk::source_ptr& source )
323{
324    GroupItem* parent;
325    if ( !source.isNull() && source->isLocal() )
326    {
327        parent = m_myMusicGroup;
328    }
329    else
330    {
331        parent = m_collectionsGroup;
332    }
333
334    QModelIndex idx = indexFromItem( parent );
335    beginInsertRows( idx, rowCount( idx ), rowCount( idx ) );
336    new SourceItem( this, parent, source );
337    endInsertRows();
338
339    parent->checkExpandedState();
340}
341
342
343bool
344SourcesModel::removeItem( const Tomahawk::source_ptr& source )
345{
346//    qDebug() << "Removing source item from SourceTree:" << source->friendlyName();
347
348    QModelIndex idx;
349    int rows = rowCount();
350    for ( int row = 0; row < rows; row++ )
351    {
352        QModelIndex idx = index( row, 0, QModelIndex() );
353        SourceItem* item = static_cast< SourceItem* >( idx.internalPointer() );
354        if ( item && item->source() == source )
355        {
356//            qDebug() << "Found removed source item:" << item->source()->userName();
357            beginRemoveRows( QModelIndex(), row, row );
358            m_rootItem->removeChild( item );
359            endRemoveRows();
360
361//             onItemOffline( idx );
362
363            delete item;
364            return true;
365        }
366    }
367
368    return false;
369}
370
371
372void
373SourcesModel::viewPageActivated( Tomahawk::ViewPage* page )
374{
375    if ( !m_sourcesWithViewPage.isEmpty() )
376    {
377        // Hide again any offline sources we exposed, since we're showing a different page now. they'll be re-shown if the user selects a playlist that is from an offline user
378        QList< source_ptr > temp = m_sourcesWithViewPage;
379        m_sourcesWithViewPage.clear();
380        foreach ( const source_ptr& s, temp )
381        {
382            QModelIndex idx = indexFromItem( m_sourcesWithViewPageItems.value( s ) );
383            emit dataChanged( idx, idx );
384        }
385        m_sourcesWithViewPageItems.clear();
386    }
387
388    if ( m_sourceTreeLinks.contains( page ) )
389    {
390        Q_ASSERT( m_sourceTreeLinks[ page ] );
391//        qDebug() << "Got view page activated for item:" << m_sourceTreeLinks[ page ]->text();
392        QModelIndex idx = indexFromItem( m_sourceTreeLinks[ page ] );
393
394        if ( !idx.isValid() )
395            m_sourceTreeLinks.remove( page );
396        else
397            emit selectRequest( QPersistentModelIndex( idx ) );
398    }
399    else
400    {
401        playlist_ptr p = ViewManager::instance()->playlistForPage( page );
402        // HACK
403        // try to find it if it is a playlist. not pretty at all.... but this happens when ViewManager loads a playlist or dynplaylist NOT from the sidebar but from somewhere else
404        // we don't know which sourcetreeitem is related to it, so we have to find it. we also don't know if this page is a playlist or dynplaylist or not, but we can't check as we can't
405        // include DynamicWidget.h here (so can't dynamic_cast).
406        // this could also be fixed by keeping a master list of playlists/sourcetreeitems... but that's even uglier i think. this is only called the first time a certain viewpage is clicked from external
407        // sources.
408        SourceTreeItem* item = activatePlaylistPage( page, m_rootItem );
409        m_viewPageDelayedCacheItem = page;
410
411        if ( !p.isNull() )
412        {
413            source_ptr s= p->author();
414            if ( !s.isNull() && !s->isOnline() && item )
415            {
416                m_sourcesWithViewPage << s;
417
418                // show the collection now... yeah.
419                if ( !item->parent() || !item->parent()->parent() )
420                {
421                    tLog() << "Found playlist item with no category parent or collection parent!" << item->text();
422                    return;
423                }
424
425                SourceTreeItem* collectionOfPlaylist = item->parent()->parent();
426                if ( !m_rootItem->children().contains( collectionOfPlaylist ) ) // verification to make sure we're not stranded
427                {
428                    tLog() << "Got what we assumed to be a parent col of a playlist not as a child of our root node...:" << collectionOfPlaylist;
429                    return;
430                }
431
432                QModelIndex idx = indexFromItem( collectionOfPlaylist );
433                m_sourcesWithViewPageItems[ s ] = collectionOfPlaylist;
434                tDebug() << "Emitting dataChanged for offline source:" << idx << idx.isValid() << collectionOfPlaylist << collectionOfPlaylist->text();
435                emit dataChanged( idx, idx );
436            }
437        }
438    }
439}
440
441
442SourceTreeItem*
443SourcesModel::activatePlaylistPage( ViewPage* p, SourceTreeItem* i )
444{
445    if( !i )
446        return 0;
447
448    if( qobject_cast< PlaylistItem* >( i ) &&
449        qobject_cast< PlaylistItem* >( i )->activateCurrent() )
450        return i;
451
452    SourceTreeItem* ret = 0;
453    for( int k = 0; k < i->children().size(); k++ )
454    {
455        if( SourceTreeItem* retItem = activatePlaylistPage( p, i->children().at( k ) ) )
456            ret = retItem;
457    }
458
459    return ret;
460}
461
462
463void
464SourcesModel::loadSources()
465{
466    QList<source_ptr> sources = SourceList::instance()->sources();
467
468    foreach( const source_ptr& source, sources )
469        appendItem( source );
470}
471
472
473void
474SourcesModel::onSourcesAdded( const QList<source_ptr>& sources )
475{
476    foreach( const source_ptr& source, sources )
477        appendItem( source );
478}
479
480
481void
482SourcesModel::onSourceAdded( const source_ptr& source )
483{
484    appendItem( source );
485}
486
487
488void
489SourcesModel::onSourceRemoved( const source_ptr& source )
490{
491    removeItem( source );
492}
493
494
495void
496SourcesModel::itemUpdated()
497{
498    Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) );
499    SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() );
500
501    if( !item )
502        return;
503
504    QModelIndex idx = indexFromItem( item );
505    if( idx.isValid() )
506        emit dataChanged( idx, idx );
507}
508
509
510void
511SourcesModel::onItemRowsAddedBegin( int first, int last )
512{
513    Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) );
514    SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() );
515
516    if( !item )
517        return;
518
519    QModelIndex idx = indexFromItem( item );
520    beginInsertRows( idx, first, last );
521}
522
523
524void
525SourcesModel::onItemRowsAddedDone()
526{
527    Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) );
528
529    endInsertRows();
530}
531
532
533void
534SourcesModel::onItemRowsRemovedBegin( int first, int last )
535{
536    Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) );
537    SourceTreeItem* item = qobject_cast< SourceTreeItem* >( sender() );
538
539    if( !item )
540        return;
541
542    QModelIndex idx = indexFromItem( item );
543    beginRemoveRows( idx, first, last );
544}
545
546
547void
548SourcesModel::onItemRowsRemovedDone()
549{
550    Q_ASSERT( qobject_cast< SourceTreeItem* >( sender() ) );
551
552    endRemoveRows();
553}
554
555
556void
557SourcesModel::linkSourceItemToPage( SourceTreeItem* item, ViewPage* p )
558{
559    // TODO handle removal
560    m_sourceTreeLinks[ p ] = item;
561
562    if ( p && m_viewPageDelayedCacheItem == p )
563        emit selectRequest( QPersistentModelIndex( indexFromItem( item ) ) );
564
565    if ( QObject* obj = dynamic_cast< QObject* >( p ) )
566    {
567        if( obj->metaObject()->indexOfSignal( "destroyed(QWidget*)" ) > -1 )
568            connect( obj, SIGNAL( destroyed( QWidget* ) ), SLOT( onWidgetDestroyed( QWidget* ) ), Qt::UniqueConnection );
569    }
570    m_viewPageDelayedCacheItem = 0;
571}
572
573
574void
575SourcesModel::onWidgetDestroyed( QWidget* w )
576{
577    int ret = m_sourceTreeLinks.remove( dynamic_cast< Tomahawk::ViewPage* > ( w ) );
578    qDebug() << "REMOVED STALE SOURCE PAGE?" << ret;
579}
580
581
582void
583SourcesModel::removeSourceItemLink( SourceTreeItem* item )
584{
585    QList< ViewPage* > pages = m_sourceTreeLinks.keys( item );
586    foreach( ViewPage* p, pages )
587        m_sourceTreeLinks.remove( p );
588}
589
590
591SourceTreeItem*
592SourcesModel::itemFromIndex( const QModelIndex& idx ) const
593{
594    if( !idx.isValid() )
595        return m_rootItem;
596
597    Q_ASSERT( idx.internalPointer() );
598
599    return reinterpret_cast< SourceTreeItem* >( idx.internalPointer() );
600}
601
602
603QModelIndex
604SourcesModel::indexFromItem( SourceTreeItem* item ) const
605{
606    if( !item || !item->parent() ) // should never happen..
607        return QModelIndex();
608
609    // reconstructs a modelindex from a sourcetreeitem that is somewhere in the tree
610    // traverses the item to the root node, then rebuilds the qmodeindices from there back down
611    // each int is the row of that item in the parent.
612    /**
613     * In this diagram, if the \param item is G, childIndexList will contain [0, 2, 0]
614     *
615     *    A
616     *      D
617     *      E
618     *      F
619     *        G
620     *        H
621     *    B
622     *    C
623     *
624     **/
625    QList< int > childIndexList;
626    SourceTreeItem* curItem = item;
627    while( curItem != m_rootItem ) {
628        int row  = rowForItem( curItem );
629        if( row < 0 ) // something went wrong, bail
630            return QModelIndex();
631
632        childIndexList << row;
633
634        curItem = curItem->parent();
635    }
636//     qDebug() << "build child index list:" << childIndexList;
637    // now rebuild the qmodelindex we need
638    QModelIndex idx;
639    for( int i = childIndexList.size() - 1; i >= 0 ; i-- ) {
640        idx = index( childIndexList[ i ], 0, idx );
641    }
642//     qDebug() << "Got index from item:" << idx << idx.data( Qt::DisplayRole ).toString();
643//     qDebug() << "parent:" << idx.parent();
644    return idx;
645}
646
647
648int
649SourcesModel::rowForItem( SourceTreeItem* item ) const
650{
651    if ( !item || !item->parent() || !item->parent()->children().contains( item ) )
652        return -1;
653
654    return item->parent()->children().indexOf( item );
655}
656
657
658void
659SourcesModel::itemSelectRequest( SourceTreeItem* item )
660{
661    emit selectRequest( QPersistentModelIndex( indexFromItem( item ) ) );
662}
663
664
665void
666SourcesModel::itemExpandRequest( SourceTreeItem *item )
667{
668    emit expandRequest( QPersistentModelIndex( indexFromItem( item ) ) );
669}
670
671
672void
673SourcesModel::itemToggleExpandRequest( SourceTreeItem *item )
674{
675    emit toggleExpandRequest( QPersistentModelIndex( indexFromItem( item ) ) );
676}
677
678
679QList< source_ptr >
680SourcesModel::sourcesWithViewPage() const
681{
682    return m_sourcesWithViewPage;
683}