/src/sourcetree/sourcesmodel.cpp

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