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