/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. #include "PlaylistItems.h"
  20. #include "Query.h"
  21. #include "ViewManager.h"
  22. #include "playlist/dynamic/GeneratorInterface.h"
  23. #include "playlist/PlaylistView.h"
  24. #include "CategoryItems.h"
  25. #include "SourceItem.h"
  26. #include "utils/TomahawkUtils.h"
  27. #include "utils/Logger.h"
  28. #include "DropJob.h"
  29. #include "Source.h"
  30. #include "audio/AudioEngine.h"
  31. #include <QMimeData>
  32. #include <QPainter>
  33. using namespace Tomahawk;
  34. PlaylistItem::PlaylistItem( SourcesModel* mdl, SourceTreeItem* parent, const playlist_ptr& pl, int index )
  35. : SourceTreeItem( mdl, parent, SourcesModel::StaticPlaylist, index )
  36. , m_loaded( false )
  37. , m_canSubscribe( false )
  38. , m_showSubscribed( false )
  39. , m_playlist( pl )
  40. {
  41. connect( pl.data(), SIGNAL( revisionLoaded( Tomahawk::PlaylistRevision ) ),
  42. SLOT( onPlaylistLoaded( Tomahawk::PlaylistRevision ) ), Qt::QueuedConnection );
  43. connect( pl.data(), SIGNAL( changed() ),
  44. SLOT( onUpdated() ), Qt::QueuedConnection );
  45. m_icon = QIcon( RESPATH "images/playlist-icon.png" );
  46. if( ViewManager::instance()->pageForPlaylist( pl ) )
  47. model()->linkSourceItemToPage( this, ViewManager::instance()->pageForPlaylist( pl ) );
  48. if ( !m_playlist->updaters().isEmpty() )
  49. createOverlay();
  50. }
  51. QString
  52. PlaylistItem::text() const
  53. {
  54. return m_playlist->title();
  55. }
  56. Tomahawk::playlist_ptr
  57. PlaylistItem::playlist() const
  58. {
  59. return m_playlist;
  60. }
  61. void
  62. PlaylistItem::onPlaylistLoaded( Tomahawk::PlaylistRevision revision )
  63. {
  64. Q_UNUSED( revision );
  65. m_loaded = true;
  66. emit updated();
  67. }
  68. void
  69. PlaylistItem::onPlaylistChanged()
  70. {
  71. emit updated();
  72. }
  73. int
  74. PlaylistItem::peerSortValue() const
  75. {
  76. // return m_playlist->createdOn();
  77. return 0;
  78. }
  79. int
  80. PlaylistItem::IDValue() const
  81. {
  82. return m_playlist->createdOn();
  83. }
  84. bool
  85. PlaylistItem::isBeingPlayed() const
  86. {
  87. if ( ViewPage* page = ViewManager::instance()->pageForPlaylist( m_playlist ) )
  88. {
  89. if ( AudioEngine::instance()->currentTrackPlaylist() == page->playlistInterface() )
  90. return true;
  91. if ( page->playlistInterface()->hasChildInterface( AudioEngine::instance()->currentTrackPlaylist() ) )
  92. return true;
  93. }
  94. return false;
  95. }
  96. Qt::ItemFlags
  97. PlaylistItem::flags() const
  98. {
  99. Qt::ItemFlags flags = SourceTreeItem::flags();
  100. flags |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
  101. if ( !m_loaded )
  102. flags &= !Qt::ItemIsEnabled;
  103. if ( playlist()->author()->isLocal() )
  104. flags |= Qt::ItemIsEditable;
  105. if ( playlist()->busy() )
  106. {
  107. flags &= !Qt::ItemIsEnabled;
  108. flags &= !Qt::ItemIsEditable;
  109. flags &= !Qt::ItemIsDropEnabled;
  110. }
  111. return flags;
  112. }
  113. void
  114. PlaylistItem::activate()
  115. {
  116. ViewPage* p = ViewManager::instance()->show( m_playlist );
  117. model()->linkSourceItemToPage( this, p );
  118. }
  119. void
  120. PlaylistItem::doubleClicked()
  121. {
  122. ViewPage* p = ViewManager::instance()->currentPage();
  123. if ( PlaylistView* view = dynamic_cast< PlaylistView* >( p ) )
  124. {
  125. view->startPlayingFromStart();
  126. }
  127. }
  128. void
  129. PlaylistItem::setLoaded( bool loaded )
  130. {
  131. m_loaded = loaded;
  132. }
  133. bool
  134. PlaylistItem::willAcceptDrag( const QMimeData* data ) const
  135. {
  136. Q_UNUSED( data );
  137. return !m_playlist.isNull() && m_playlist->author()->isLocal() && DropJob::acceptsMimeData( data, DropJob::Track ) && !m_playlist->busy();
  138. }
  139. PlaylistItem::DropTypes
  140. PlaylistItem::supportedDropTypes( const QMimeData* data ) const
  141. {
  142. if ( data->hasFormat( "application/tomahawk.mixed" ) )
  143. {
  144. // If this is mixed but only queries/results, we can still handle them
  145. bool mixedQueries = true;
  146. QByteArray itemData = data->data( "application/tomahawk.mixed" );
  147. QDataStream stream( &itemData, QIODevice::ReadOnly );
  148. QString mimeType;
  149. qlonglong val;
  150. while ( !stream.atEnd() )
  151. {
  152. stream >> mimeType;
  153. if ( mimeType != "application/tomahawk.query.list" &&
  154. mimeType != "application/tomahawk.result.list" )
  155. {
  156. mixedQueries = false;
  157. break;
  158. }
  159. stream >> val;
  160. }
  161. if ( mixedQueries )
  162. return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
  163. else
  164. return DropTypesNone;
  165. }
  166. if ( data->hasFormat( "application/tomahawk.query.list" ) )
  167. return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
  168. else if ( data->hasFormat( "application/tomahawk.result.list" ) )
  169. return DropTypeThisTrack | DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
  170. else if ( data->hasFormat( "application/tomahawk.metadata.album" ) )
  171. return DropTypeThisAlbum | DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
  172. else if ( data->hasFormat( "application/tomahawk.metadata.artist" ) )
  173. return DropTypeAllFromArtist | DropTypeLocalItems | DropTypeTop50;
  174. else if ( data->hasFormat( "text/plain" ) )
  175. {
  176. return DropTypesNone;
  177. }
  178. return DropTypesNone;
  179. }
  180. bool
  181. PlaylistItem::dropMimeData( const QMimeData* data, Qt::DropAction action )
  182. {
  183. Q_UNUSED( action );
  184. if ( m_playlist->busy() )
  185. return false;
  186. QList< Tomahawk::query_ptr > queries;
  187. if ( data->hasFormat( "application/tomahawk.playlist.id" ) &&
  188. data->data( "application/tomahawk.playlist.id" ) == m_playlist->guid() )
  189. return false; // don't allow dropping on ourselves
  190. if ( !DropJob::acceptsMimeData( data, DropJob::Track ) )
  191. return false;
  192. DropJob *dj = new DropJob();
  193. dj->setDropTypes( DropJob::Track );
  194. dj->setDropAction( DropJob::Append );
  195. connect( dj, SIGNAL( tracks( QList< Tomahawk::query_ptr > ) ), this, SLOT( parsedDroppedTracks( QList< Tomahawk::query_ptr > ) ) );
  196. if ( dropType() == DropTypeAllFromArtist )
  197. dj->setGetWholeArtists( true );
  198. if ( dropType() == DropTypeThisAlbum )
  199. dj->setGetWholeAlbums( true );
  200. if ( dropType() == DropTypeLocalItems )
  201. {
  202. dj->setGetWholeArtists( true );
  203. dj->tracksFromMimeData( data, false, true );
  204. }
  205. else if ( dropType() == DropTypeTop50 )
  206. {
  207. dj->setGetWholeArtists( true );
  208. dj->tracksFromMimeData( data, false, false, true );
  209. }
  210. else
  211. dj->tracksFromMimeData( data, false, false );
  212. // TODO can't know if it works or not yet...
  213. return true;
  214. }
  215. void
  216. PlaylistItem::parsedDroppedTracks( const QList< query_ptr >& tracks )
  217. {
  218. qDebug() << "adding" << tracks.count() << "tracks";
  219. if ( tracks.count() && !m_playlist.isNull() && m_playlist->author()->isLocal() )
  220. {
  221. qDebug() << "on playlist:" << m_playlist->title() << m_playlist->guid() << m_playlist->currentrevision();
  222. m_playlist->addEntries( tracks, m_playlist->currentrevision() );
  223. }
  224. }
  225. void
  226. PlaylistItem::onUpdated()
  227. {
  228. const bool newOverlay = createOverlay();
  229. if ( !newOverlay && !m_overlaidIcon.isNull() )
  230. m_overlaidIcon = QIcon();
  231. emit updated();
  232. }
  233. bool
  234. PlaylistItem::collaborative() const
  235. {
  236. Q_ASSERT( !m_playlist.isNull() );
  237. if ( m_playlist->updaters().isEmpty() )
  238. return false;
  239. bool collaborative = false;
  240. foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
  241. {
  242. if( !updater->collaborative() )
  243. continue;
  244. /// @note: We only care for collaborations if in sync
  245. if( !updater->sync() )
  246. continue;
  247. collaborative = updater->collaborative();
  248. }
  249. return collaborative;
  250. }
  251. bool
  252. PlaylistItem::createOverlay()
  253. {
  254. Q_ASSERT( !m_playlist.isNull() );
  255. if ( m_playlist->updaters().isEmpty() )
  256. return false;
  257. m_showSubscribed = false;
  258. m_canSubscribe = false;
  259. foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
  260. {
  261. if ( updater->canSubscribe() )
  262. {
  263. m_canSubscribe = true;
  264. m_showSubscribed = updater->subscribed();
  265. break;
  266. }
  267. }
  268. if ( m_canSubscribe && m_showSubscribed && m_subscribedOnIcon.isNull() )
  269. m_subscribedOnIcon = QPixmap( RESPATH "images/subscribe-on.png" );
  270. else if ( m_canSubscribe && !m_showSubscribed && m_subscribedOffIcon.isNull() )
  271. m_subscribedOffIcon = QPixmap( RESPATH "images/subscribe-off.png" );
  272. QList< QPixmap > icons;
  273. foreach ( PlaylistUpdaterInterface* updater, m_playlist->updaters() )
  274. {
  275. if ( updater->sync() && !updater->typeIcon().isNull() )
  276. icons << updater->typeIcon();
  277. }
  278. m_overlaidIcon = QIcon();
  279. m_overlaidUpdaters = m_playlist->updaters();
  280. if ( icons.isEmpty() )
  281. return false;
  282. // For now we only support up to 2 overlaid updater icons,
  283. // we need to add smarter scaling etc to manage more at once
  284. if ( icons.size() > 2 )
  285. icons = icons.mid( 0, 2 );
  286. QPixmap base = m_icon.pixmap( 48, 48 );
  287. QPainter p( &base );
  288. const int w = base.width() / 2;
  289. QRect overlayRect( base.rect().right() - w, base.rect().height() - w, w, w );
  290. foreach ( const QPixmap& overlay, icons )
  291. {
  292. p.drawPixmap( overlayRect, overlay );
  293. // NOTE only works if icons.size == 2 as ensured above
  294. overlayRect.moveLeft( 0 );
  295. }
  296. p.end();
  297. m_overlaidIcon.addPixmap( base );
  298. return true;
  299. }
  300. QIcon
  301. PlaylistItem::icon() const
  302. {
  303. if ( !m_overlaidIcon.isNull() )
  304. return m_overlaidIcon;
  305. else
  306. return m_icon;
  307. }
  308. bool
  309. PlaylistItem::setData( const QVariant& v, bool role )
  310. {
  311. Q_UNUSED( role );
  312. if ( m_playlist->author()->isLocal() )
  313. {
  314. m_playlist->rename( v.toString() );
  315. return true;
  316. }
  317. return false;
  318. }
  319. SourceTreeItem*
  320. PlaylistItem::activateCurrent()
  321. {
  322. if( ViewManager::instance()->pageForPlaylist( m_playlist ) == ViewManager::instance()->currentPage() )
  323. {
  324. model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() );
  325. emit selectRequest( this );
  326. return this;
  327. }
  328. return 0;
  329. }
  330. void
  331. PlaylistItem::setSubscribed( bool subscribed )
  332. {
  333. Q_ASSERT( !m_overlaidUpdaters.isEmpty() );
  334. if ( m_overlaidUpdaters.isEmpty() )
  335. {
  336. qWarning() << "NO playlist updater but got a toggle subscribed action on the playlist item!?";
  337. return;
  338. }
  339. else if ( m_overlaidUpdaters.size() > 1 )
  340. {
  341. qWarning() << "Got TWO subscribed updaters at the same time? Toggling both... wtf";
  342. }
  343. foreach( PlaylistUpdaterInterface* updater, m_overlaidUpdaters )
  344. {
  345. updater->setSubscribed( subscribed );
  346. }
  347. }
  348. DynamicPlaylistItem::DynamicPlaylistItem( SourcesModel* mdl, SourceTreeItem* parent, const dynplaylist_ptr& pl, int index )
  349. : PlaylistItem( mdl, parent, pl.staticCast< Playlist >(), index )
  350. , m_dynplaylist( pl )
  351. {
  352. setRowType( m_dynplaylist->mode() == Static ? SourcesModel::AutomaticPlaylist : SourcesModel::Station );
  353. connect( pl.data(), SIGNAL( dynamicRevisionLoaded( Tomahawk::DynamicPlaylistRevision ) ),
  354. SLOT( onDynamicPlaylistLoaded( Tomahawk::DynamicPlaylistRevision ) ), Qt::QueuedConnection );
  355. m_stationIcon = QIcon( RESPATH "images/station.png" );
  356. m_automaticPlaylistIcon = QIcon( RESPATH "images/automatic-playlist.png" );
  357. if( ViewManager::instance()->pageForDynPlaylist( pl ) )
  358. model()->linkSourceItemToPage( this, ViewManager::instance()->pageForDynPlaylist( pl ) );
  359. }
  360. DynamicPlaylistItem::~DynamicPlaylistItem()
  361. {
  362. }
  363. void
  364. DynamicPlaylistItem::activate()
  365. {
  366. ViewPage* p = ViewManager::instance()->show( m_dynplaylist );
  367. model()->linkSourceItemToPage( this, p );
  368. }
  369. void
  370. DynamicPlaylistItem::onDynamicPlaylistLoaded( DynamicPlaylistRevision revision )
  371. {
  372. setLoaded( true );
  373. checkReparentHackNeeded( revision );
  374. // END HACK
  375. emit updated();
  376. }
  377. int
  378. DynamicPlaylistItem::peerSortValue() const
  379. {
  380. // return m_dynplaylist->createdOn();
  381. return 0;
  382. }
  383. int
  384. DynamicPlaylistItem::IDValue() const
  385. {
  386. return m_dynplaylist->createdOn();
  387. }
  388. void
  389. DynamicPlaylistItem::checkReparentHackNeeded( const DynamicPlaylistRevision& revision )
  390. {
  391. // 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.
  392. // we need to reparent this playlist to the correct category :-/.
  393. CategoryItem* cat = qobject_cast< CategoryItem* >( parent() );
  394. // qDebug() << "with category" << cat;
  395. // if( cat ) qDebug() << "and cat type:" << cat->categoryType();
  396. if( cat )
  397. {
  398. CategoryItem* from = cat;
  399. CategoryItem* to = 0;
  400. if( cat->categoryType() == SourcesModel::PlaylistsCategory && revision.mode == OnDemand ) { // WRONG
  401. SourceItem* col = qobject_cast< SourceItem* >( cat->parent() );
  402. to = col->stationsCategory();
  403. if( !to ) { // you have got to be fucking kidding me
  404. int fme = col->children().count();
  405. col->beginRowsAdded( fme, fme );
  406. to = new CategoryItem( model(), col, SourcesModel::StationsCategory, false );
  407. col->appendChild( to ); // we f'ing know it's not local b/c we're not getting into this mess ourselves
  408. col->endRowsAdded();
  409. col->setStationsCategory( to );
  410. }
  411. } else if( cat->categoryType() == SourcesModel::StationsCategory && revision.mode == Static ) { // WRONG
  412. SourceItem* col = qobject_cast< SourceItem* >( cat->parent() );
  413. to = col->playlistsCategory();
  414. // qDebug() << "TRYING TO HACK TO:" << to;
  415. if( !to ) { // you have got to be fucking kidding me
  416. int fme = col->children().count();
  417. col->beginRowsAdded( fme, fme );
  418. to = new CategoryItem( model(), col, SourcesModel::PlaylistsCategory, false );
  419. col->appendChild( to ); // we f'ing know it's not local b/c we're not getting into this mess ourselves
  420. col->endRowsAdded();
  421. col->setPlaylistsCategory( to );
  422. }
  423. }
  424. if( to ) {
  425. // qDebug() << "HACKING! moving dynamic playlist from" << from->text() << "to:" << to->text();
  426. // remove and add
  427. int idx = from->children().indexOf( this );
  428. from->beginRowsRemoved( idx, idx );
  429. from->removeChild( this );
  430. from->endRowsRemoved();
  431. idx = to->children().count();
  432. to->beginRowsAdded( idx, idx );
  433. to->appendChild( this );
  434. to->endRowsAdded();
  435. setParentItem( to );
  436. }
  437. }
  438. }
  439. dynplaylist_ptr
  440. DynamicPlaylistItem::dynPlaylist() const
  441. {
  442. return m_dynplaylist;
  443. }
  444. QString
  445. DynamicPlaylistItem::text() const
  446. {
  447. return m_dynplaylist->title();
  448. }
  449. bool
  450. DynamicPlaylistItem::willAcceptDrag( const QMimeData* data ) const
  451. {
  452. Q_UNUSED( data );
  453. return false;
  454. }
  455. QIcon
  456. DynamicPlaylistItem::icon() const
  457. {
  458. if ( m_dynplaylist->mode() == OnDemand )
  459. {
  460. return m_stationIcon;
  461. }
  462. else
  463. {
  464. return m_automaticPlaylistIcon;
  465. }
  466. }
  467. SourceTreeItem*
  468. DynamicPlaylistItem::activateCurrent()
  469. {
  470. if( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) == ViewManager::instance()->currentPage() )
  471. {
  472. model()->linkSourceItemToPage( this, ViewManager::instance()->currentPage() );
  473. emit selectRequest( this );
  474. return this;
  475. }
  476. return 0;
  477. }
  478. bool
  479. DynamicPlaylistItem::isBeingPlayed() const
  480. {
  481. if ( ViewManager::instance()->pageForDynPlaylist( m_dynplaylist ) )
  482. return AudioEngine::instance()->currentTrackPlaylist() == ViewManager::instance()->pageForDynPlaylist( m_dynplaylist )->playlistInterface();
  483. return false;
  484. }
  485. bool
  486. PlaylistItem::canSubscribe() const
  487. {
  488. return m_canSubscribe;
  489. }
  490. bool
  491. PlaylistItem::subscribed() const
  492. {
  493. return m_showSubscribed;
  494. }
  495. QPixmap
  496. PlaylistItem::subscribedIcon() const
  497. {
  498. return m_showSubscribed ? m_subscribedOnIcon : m_subscribedOffIcon;
  499. }