PageRenderTime 256ms CodeModel.GetById 47ms app.highlight 101ms RepoModel.GetById 70ms app.codeStats 1ms

/src/libtomahawk/widgets/whatshotwidget.cpp

http://github.com/tomahawk-player/tomahawk
C++ | 590 lines | 436 code | 120 blank | 34 comment | 64 complexity | 271e3eb4c597e6aecd8699ed4da44a02 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, Leo Franchi <lfranchi@kde.org>
  5 *   Copyright 2011, Jeff Mitchell <jeff@tomahawk-player.org>
  6 *   Copyright 2012, Hugo Lindstr??m <hugolm84@gmail.com>
  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 "WhatsHotWidget.h"
 23#include "WhatsHotWidget_p.h"
 24#include "ui_WhatsHotWidget.h"
 25
 26
 27#include "ViewManager.h"
 28#include "SourceList.h"
 29#include "TomahawkSettings.h"
 30#include "RecentPlaylistsModel.h"
 31#include "ChartDataLoader.h"
 32
 33#include "audio/AudioEngine.h"
 34#include "playlist/dynamic/GeneratorInterface.h"
 35#include "playlist/PlayableModel.h"
 36#include "playlist/PlaylistModel.h"
 37#include "playlist/TreeProxyModel.h"
 38#include "playlist/PlaylistChartItemDelegate.h"
 39#include "utils/TomahawkUtilsGui.h"
 40#include "utils/Logger.h"
 41#include "Pipeline.h"
 42#include "utils/AnimatedSpinner.h"
 43
 44#include <QPainter>
 45#include <QStandardItemModel>
 46#include <QStandardItem>
 47
 48
 49#define HISTORY_TRACK_ITEMS 25
 50#define HISTORY_PLAYLIST_ITEMS 10
 51#define HISTORY_RESOLVING_TIMEOUT 2500
 52
 53using namespace Tomahawk;
 54
 55static QString s_whatsHotIdentifier = QString( "WhatsHotWidget" );
 56
 57
 58WhatsHotWidget::WhatsHotWidget( QWidget* parent )
 59    : QWidget( parent )
 60    , ui( new Ui::WhatsHotWidget )
 61    , m_sortedProxy( 0 )
 62    , m_workerThread( 0 )
 63{
 64    ui->setupUi( this );
 65
 66    TomahawkUtils::unmarginLayout( layout() );
 67    TomahawkUtils::unmarginLayout( ui->stackLeft->layout() );
 68    TomahawkUtils::unmarginLayout( ui->horizontalLayout->layout() );
 69    TomahawkUtils::unmarginLayout( ui->horizontalLayout_2->layout() );
 70    TomahawkUtils::unmarginLayout( ui->breadCrumbLeft->layout() );
 71    TomahawkUtils::unmarginLayout( ui->verticalLayout->layout() );
 72
 73    m_crumbModelLeft = new QStandardItemModel( this );
 74    m_sortedProxy = new QSortFilterProxyModel( this );
 75    m_sortedProxy->setDynamicSortFilter( true );
 76    m_sortedProxy->setFilterCaseSensitivity( Qt::CaseInsensitive );
 77
 78    ui->breadCrumbLeft->setRootIcon( QPixmap( RESPATH "images/charts.png" ) );
 79
 80    connect( ui->breadCrumbLeft, SIGNAL( activateIndex( QModelIndex ) ), SLOT( leftCrumbIndexChanged( QModelIndex ) ) );
 81
 82    ui->tracksViewLeft->setHeaderHidden( true );
 83    ui->tracksViewLeft->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
 84    PlaylistChartItemDelegate* del = new PlaylistChartItemDelegate( ui->tracksViewLeft, ui->tracksViewLeft->proxyModel() );
 85    connect( del, SIGNAL( updateRequest( QModelIndex ) ), ui->tracksViewLeft, SLOT( update( QModelIndex ) ) );
 86    ui->tracksViewLeft->setItemDelegate( del );
 87    ui->tracksViewLeft->setUniformRowHeights( false );
 88
 89    TreeProxyModel* artistsProxy = new TreeProxyModel( ui->artistsViewLeft );
 90    artistsProxy->setFilterCaseSensitivity( Qt::CaseInsensitive );
 91    artistsProxy->setDynamicSortFilter( true );
 92
 93    ui->artistsViewLeft->setProxyModel( artistsProxy );
 94
 95    ui->artistsViewLeft->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
 96    ui->artistsViewLeft->header()->setVisible( true );
 97
 98    m_playlistInterface = Tomahawk::playlistinterface_ptr( new ChartsPlaylistInterface( this ) );
 99
100    m_workerThread = new QThread( this );
101    m_workerThread->start();
102
103    connect( Tomahawk::InfoSystem::InfoSystem::instance(),
104             SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
105             SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
106
107    connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) );
108
109    // Read last viewed charts, to be used as defaults
110    m_currentVIds = TomahawkSettings::instance()->lastChartIds();
111    qDebug() << "Got last chartIds:" << m_currentVIds;
112
113    // TracksView is first shown, show spinner on that
114    // After fadeOut, charts are loaded
115    m_loadingSpinner =  new AnimatedSpinner( ui->tracksViewLeft );
116    m_loadingSpinner->fadeIn();
117
118}
119
120
121WhatsHotWidget::~WhatsHotWidget()
122{
123    qDebug() << "Deleting whatshot";
124    // Write the settings
125    qDebug() << "Writing chartIds to settings: " << m_currentVIds;
126    TomahawkSettings::instance()->setLastChartIds( m_currentVIds );
127    qDeleteAll( m_workers );
128    m_workers.clear();
129    m_workerThread->exit( 0 );
130    m_playlistInterface.clear();
131    delete ui;
132}
133
134
135Tomahawk::playlistinterface_ptr
136WhatsHotWidget::playlistInterface() const
137{
138    return m_playlistInterface;
139}
140
141
142bool
143WhatsHotWidget::isBeingPlayed() const
144{
145    if ( AudioEngine::instance()->currentTrackPlaylist() == ui->artistsViewLeft->playlistInterface() )
146        return true;
147
148    if ( AudioEngine::instance()->currentTrackPlaylist() == ui->tracksViewLeft->playlistInterface() )
149        return true;
150
151    if ( ui->albumsView->isBeingPlayed() )
152        return true;
153
154    return false;
155}
156
157
158bool
159WhatsHotWidget::jumpToCurrentTrack()
160{
161    if ( ui->artistsViewLeft->model() && ui->artistsViewLeft->jumpToCurrentTrack() )
162        return true;
163
164    if ( ui->tracksViewLeft->model() && ui->tracksViewLeft->jumpToCurrentTrack() )
165        return true;
166
167    if ( ui->albumsView->model() && ui->albumsView->jumpToCurrentTrack() )
168        return true;
169
170    return false;
171}
172
173
174void
175WhatsHotWidget::fetchData()
176{
177
178    Tomahawk::InfoSystem::InfoStringHash artistInfo;
179
180    Tomahawk::InfoSystem::InfoRequestData requestData;
181    requestData.caller = s_whatsHotIdentifier;
182    requestData.customData = QVariantMap();
183    requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( artistInfo );
184    requestData.type = Tomahawk::InfoSystem::InfoChartCapabilities;
185    requestData.timeoutMillis = 20000;
186    requestData.allSources = true;
187    Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
188
189    tDebug( LOGVERBOSE ) << "WhatsHot: requested InfoChartCapabilities";
190}
191
192
193void
194WhatsHotWidget::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
195{
196    if ( requestData.caller != s_whatsHotIdentifier )
197        return;
198
199    if ( output.isNull() )
200    {
201        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Info came back empty";
202        return;
203    }
204
205    if ( !output.canConvert< QVariantMap >() )
206    {
207        tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "WhatsHot: Could not parse output into a map";
208        return;
209    }
210
211    QVariantMap returnedData = output.toMap();
212    switch ( requestData.type )
213    {
214        case InfoSystem::InfoChartCapabilities:
215        {
216            QStandardItem *rootItem= m_crumbModelLeft->invisibleRootItem();
217            QVariantMap defaults;
218            if ( returnedData.contains( "defaults" ) )
219                defaults = returnedData.take( "defaults" ).toMap();
220
221            // We need to take this from data
222            QString defaultSource = returnedData.take( "defaultSource" ).toString();
223            // Here, we dont want current sessions last view, but rather what was current on previus quit
224            QString lastSeen = TomahawkSettings::instance()->lastChartIds().value( "lastseen" ).toString();
225            if( !lastSeen.isEmpty() )
226                defaultSource = lastSeen;
227
228            // Merge defaults with current defaults, split the value in to a list
229            foreach( const QString&key, m_currentVIds.keys() )
230                defaults[ key ] = m_currentVIds.value( key ).toString().split( "/" );
231            qDebug() << "Defaults after merge" << defaults;
232            foreach ( const QString label, returnedData.keys() )
233            {
234                QStandardItem *childItem = parseNode( rootItem, label, returnedData[ label ] );
235                rootItem->appendRow( childItem );
236            }
237
238            // Set the default source
239            // Set the default chart for each source
240            if( !defaults.empty() )
241            {
242                for ( int i = 0; i < rootItem->rowCount(); i++ )
243                {
244                    QStandardItem* source = rootItem->child( i, 0 );
245                    if ( defaultSource.toLower() == source->text().toLower() )
246                    {
247                        source->setData( true, Breadcrumb::DefaultRole );
248                    }
249
250                    if ( defaults.contains( source->text().toLower() ) )
251                    {
252                        QStringList defaultIndices = defaults[ source->text().toLower() ].toStringList();
253                        QStandardItem* cur = source;
254
255                        foreach( const QString& index, defaultIndices )
256                        {
257                            // Go through the children of the current item, marking the default one as default
258                            for ( int k = 0; k < cur->rowCount(); k++ )
259                            {
260                                if ( cur->child( k, 0 )->text().toLower() == index.toLower() )
261                                {
262                                    cur = cur->child( k, 0 ); // this is the default, drill down into the default to pick the next default
263                                    cur->setData( true, Breadcrumb::DefaultRole );
264                                    break;
265                                }
266                            }
267                        }
268                    }
269                }
270            }
271
272            m_sortedProxy->setSourceModel( m_crumbModelLeft );
273            m_sortedProxy->sort( 0, Qt::AscendingOrder );
274            ui->breadCrumbLeft->setModel( m_sortedProxy );
275            break;
276        }
277
278        case InfoSystem::InfoChart:
279        {
280            if ( !returnedData.contains( "type" ) )
281                break;
282            const QString type = returnedData[ "type" ].toString();
283            if ( !returnedData.contains( type ) )
284                break;
285
286            const QString chartId = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash >().value( "chart_id" );
287
288            m_queuedFetches.remove( chartId );
289
290            ChartDataLoader* loader = new ChartDataLoader();
291            loader->setProperty( "chartid", chartId );
292            loader->moveToThread( m_workerThread );
293
294            if ( type == "artists" )
295            {
296                loader->setType( ChartDataLoader::Artist );
297                loader->setData( returnedData[ "artists" ].value< QStringList >() );
298
299                connect( loader, SIGNAL( artists( Tomahawk::ChartDataLoader*, QList< Tomahawk::artist_ptr > ) ), this, SLOT( chartArtistsLoaded( Tomahawk::ChartDataLoader*, QList< Tomahawk::artist_ptr > ) ) );
300
301                TreeModel* artistsModel = new TreeModel( ui->artistsViewLeft );
302                artistsModel->setMode( InfoSystemMode );
303                artistsModel->startLoading();
304
305                m_artistModels[ chartId ] = artistsModel;
306
307                if ( m_queueItemToShow == chartId )
308                    setLeftViewArtists( artistsModel );
309            }
310            else if ( type == "albums" )
311            {
312                loader->setType( ChartDataLoader::Album );
313                loader->setData( returnedData[ "albums" ].value< QList< Tomahawk::InfoSystem::InfoStringHash > >() );
314
315                connect( loader, SIGNAL( albums( Tomahawk::ChartDataLoader*, QList< Tomahawk::album_ptr > ) ), this, SLOT( chartAlbumsLoaded( Tomahawk::ChartDataLoader*, QList< Tomahawk::album_ptr > ) ) );
316
317                PlayableModel* albumModel = new PlayableModel( ui->albumsView );
318                albumModel->startLoading();
319
320                m_albumModels[ chartId ] = albumModel;
321
322                if ( m_queueItemToShow == chartId )
323                    setLeftViewAlbums( albumModel );
324            }
325            else if ( type == "tracks" )
326            {
327                loader->setType( ChartDataLoader::Track );
328                loader->setData( returnedData[ "tracks" ].value< QList< Tomahawk::InfoSystem::InfoStringHash > >() );
329
330                connect( loader, SIGNAL( tracks( Tomahawk::ChartDataLoader*, QList< Tomahawk::query_ptr > ) ), this, SLOT( chartTracksLoaded( Tomahawk::ChartDataLoader*, QList< Tomahawk::query_ptr > ) ) );
331
332                PlaylistModel* trackModel = new PlaylistModel( ui->tracksViewLeft );
333                trackModel->startLoading();
334
335                m_trackModels[ chartId ] = trackModel;
336
337                if ( m_queueItemToShow == chartId )
338                    setLeftViewTracks( trackModel );
339
340            }
341
342            QMetaObject::invokeMethod( loader, "go", Qt::QueuedConnection );
343            break;
344        }
345
346        default:
347            return;
348    }
349}
350
351
352void
353WhatsHotWidget::infoSystemFinished( QString target )
354{
355    Q_UNUSED( target );
356    m_loadingSpinner->fadeOut();
357}
358
359
360void
361WhatsHotWidget::leftCrumbIndexChanged( QModelIndex index )
362{
363
364    tDebug( LOGVERBOSE ) << "WhatsHot:: left crumb changed" << index.data();
365
366    QStandardItem* item = m_crumbModelLeft->itemFromIndex( m_sortedProxy->mapToSource( index ) );
367    if ( !item )
368        return;
369    if ( !item->data( Breadcrumb::ChartIdRole ).isValid() )
370        return;
371
372    // Build current views as default. Will be used on next restart
373    QStringList curr;
374    curr.append( index.data().toString().toLower() ); // This chartname
375
376    QList<QModelIndex> indexes;
377
378    while ( index.parent().isValid() )
379    {
380        indexes.prepend(index);
381        index = index.parent();
382        curr.prepend( index.data().toString().toLower() );
383    }
384    const QString chartId = item->data( Breadcrumb::ChartIdRole ).toString();
385    const QString chartSource = curr.takeFirst().toLower();
386    curr.append( chartSource );
387    curr.append( chartId );
388
389    // Write the current view
390    m_currentVIds[ chartSource ] = curr.join( "/" ); // Instead of keeping an array, join and split later
391    m_currentVIds[ "lastseen" ] = chartSource; // We keep a record of last seen
392
393    if ( m_artistModels.contains( chartId ) )
394    {
395        setLeftViewArtists( m_artistModels[ chartId ] );
396        return;
397    }
398    else if ( m_albumModels.contains( chartId ) )
399    {
400        setLeftViewAlbums( m_albumModels[ chartId ] );
401        return;
402    }
403    else if ( m_trackModels.contains( chartId ) )
404    {
405        setLeftViewTracks( m_trackModels[ chartId ] );
406        return;
407    }
408
409    if ( m_queuedFetches.contains( chartId ) )
410    {
411        return;
412    }
413
414    Tomahawk::InfoSystem::InfoStringHash criteria;
415    criteria.insert( "chart_id", chartId );
416    /// Remember to lower the source!
417    criteria.insert( "chart_source",  index.data().toString().toLower() );
418
419    Tomahawk::InfoSystem::InfoRequestData requestData;
420    QVariantMap customData;
421    customData.insert( "whatshot_side", "left" );
422    requestData.caller = s_whatsHotIdentifier;
423    requestData.customData = customData;
424    requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( criteria );
425    requestData.type = Tomahawk::InfoSystem::InfoChart;
426    requestData.timeoutMillis = 20000;
427    requestData.allSources = true;
428
429    qDebug() << "Making infosystem request for chart of type:" << chartId;
430    Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
431
432    m_queuedFetches.insert( chartId );
433    m_queueItemToShow = chartId;
434}
435
436
437void
438WhatsHotWidget::changeEvent( QEvent* e )
439{
440    QWidget::changeEvent( e );
441    switch ( e->type() )
442    {
443        case QEvent::LanguageChange:
444            ui->retranslateUi( this );
445            break;
446
447        default:
448            break;
449    }
450}
451
452
453QStandardItem*
454WhatsHotWidget::parseNode( QStandardItem* parentItem, const QString &label, const QVariant &data )
455{
456    Q_UNUSED( parentItem );
457//     tDebug( LOGVERBOSE ) << "WhatsHot:: parsing " << label;
458
459    QStandardItem *sourceItem = new QStandardItem( label );
460
461    if ( data.canConvert< QList< Tomahawk::InfoSystem::InfoStringHash > >() )
462    {
463        QList< Tomahawk::InfoSystem::InfoStringHash > charts = data.value< QList< Tomahawk::InfoSystem::InfoStringHash > >();
464
465        foreach ( Tomahawk::InfoSystem::InfoStringHash chart, charts )
466        {
467            QStandardItem *childItem= new QStandardItem( chart[ "label" ] );
468            childItem->setData( chart[ "id" ], Breadcrumb::ChartIdRole );
469
470            if ( m_currentVIds.contains( chart.value( "id" ).toLower() ) )
471            {
472                 childItem->setData( true, Breadcrumb::DefaultRole );
473            }
474            else if ( chart.value( "default", "" ) == "true" )
475            {
476                childItem->setData( true, Breadcrumb::DefaultRole );
477            }
478            sourceItem->appendRow( childItem );
479        }
480    }
481    else if ( data.canConvert<QVariantMap>() )
482    {
483        QVariantMap dataMap = data.toMap();
484        foreach ( const QString childLabel,dataMap.keys() )
485        {
486            QStandardItem *childItem  = parseNode( sourceItem, childLabel, dataMap[ childLabel ] );
487            sourceItem->appendRow( childItem );
488        }
489    }
490    else if ( data.canConvert<QVariantList>() )
491    {
492        QVariantList dataList = data.toList();
493
494        foreach ( const QVariant value, dataList )
495        {
496            QStandardItem *childItem = new QStandardItem( value.toString() );
497            sourceItem->appendRow( childItem );
498        }
499    }
500    else
501    {
502        QStandardItem *childItem = new QStandardItem( data.toString() );
503        sourceItem->appendRow( childItem );
504    }
505    return sourceItem;
506}
507
508void
509WhatsHotWidget::setLeftViewAlbums( PlayableModel* model )
510{
511    ui->albumsView->setPlayableModel( model );
512    ui->albumsView->proxyModel()->sort( -1 ); // disable sorting, must be called after artistsViewLeft->setTreeModel
513    ui->stackLeft->setCurrentIndex( 2 );
514
515}
516
517
518void
519WhatsHotWidget::setLeftViewArtists( TreeModel* model )
520{
521    ui->artistsViewLeft->proxyModel()->setStyle( PlayableProxyModel::Collection );
522    ui->artistsViewLeft->setTreeModel( model );
523    ui->artistsViewLeft->proxyModel()->sort( -1 ); // disable sorting, must be called after artistsViewLeft->setTreeModel
524    ui->stackLeft->setCurrentIndex( 1 );
525}
526
527
528void
529WhatsHotWidget::setLeftViewTracks( PlaylistModel* model )
530{
531    ui->tracksViewLeft->proxyModel()->setStyle( PlayableProxyModel::Large );
532    ui->tracksViewLeft->setPlaylistModel( model );
533    ui->tracksViewLeft->proxyModel()->sort( -1 );
534    ui->stackLeft->setCurrentIndex( 0 );
535}
536
537
538void
539WhatsHotWidget::chartArtistsLoaded( ChartDataLoader* loader, const QList< artist_ptr >& artists )
540{
541    QString chartId = loader->property( "chartid" ).toString();
542    Q_ASSERT( m_artistModels.contains( chartId ) );
543
544    if ( m_artistModels.contains( chartId ) )
545    {
546        foreach( const artist_ptr& artist, artists )
547        {
548            m_artistModels[ chartId ]->addArtists( artist );
549            m_artistModels[ chartId ]->finishLoading();
550        }
551    }
552
553    m_workers.remove( loader );
554    loader->deleteLater();
555}
556
557
558void
559WhatsHotWidget::chartTracksLoaded( ChartDataLoader* loader, const QList< query_ptr >& tracks )
560{
561    QString chartId = loader->property( "chartid" ).toString();
562    Q_ASSERT( m_trackModels.contains( chartId ) );
563
564    if ( m_trackModels.contains( chartId ) )
565    {
566        Pipeline::instance()->resolve( tracks );
567        m_trackModels[ chartId ]->appendQueries( tracks );
568        m_trackModels[ chartId ]->finishLoading();
569    }
570
571    m_workers.remove( loader );
572    loader->deleteLater();
573}
574
575
576void
577WhatsHotWidget::chartAlbumsLoaded( ChartDataLoader* loader, const QList< album_ptr >& albums )
578{
579    QString chartId = loader->property( "chartid" ).toString();
580    Q_ASSERT( m_albumModels.contains( chartId ) );
581
582    if ( m_albumModels.contains( chartId ) )
583    {
584        m_albumModels[ chartId ]->appendAlbums( albums );
585        m_albumModels[ chartId ]->finishLoading();
586    }
587
588    m_workers.remove( loader );
589    loader->deleteLater();
590}