/src/sourcetree/sourcetreeview.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 874 lines · 669 code · 168 blank · 37 comment · 126 complexity · 5b86d3f120ca1d16e67e82a7e266b04e 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-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. * Copyright 2010-2012, Leo Franchi <lfranchi@kde.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 "SourceTreeView.h"
  21. #include "ActionCollection.h"
  22. #include "Playlist.h"
  23. #include "ViewManager.h"
  24. #include "SourcesProxyModel.h"
  25. #include "SourceList.h"
  26. #include "SourceDelegate.h"
  27. #include "sourcetree/items/PlaylistItems.h"
  28. #include "sourcetree/items/SourceItem.h"
  29. #include "audio/AudioEngine.h"
  30. #include "SourcePlaylistInterface.h"
  31. #include "TomahawkSettings.h"
  32. #include "GlobalActionManager.h"
  33. #include "DropJob.h"
  34. #include "items/GenericPageItems.h"
  35. #include "items/TemporaryPageItem.h"
  36. #include "database/DatabaseCommand_SocialAction.h"
  37. #include "database/Database.h"
  38. #include "LatchManager.h"
  39. #include "utils/TomahawkUtilsGui.h"
  40. #include "utils/Logger.h"
  41. #include "utils/Closure.h"
  42. #include "widgets/SourceTreePopupDialog.h"
  43. #include <QAction>
  44. #include <QApplication>
  45. #include <QContextMenuEvent>
  46. #include <QDragEnterEvent>
  47. #include <QHeaderView>
  48. #include <QPainter>
  49. #include <QStyledItemDelegate>
  50. #include <QFileDialog>
  51. #include <QMessageBox>
  52. #include <QSize>
  53. using namespace Tomahawk;
  54. SourceTreeView::SourceTreeView( QWidget* parent )
  55. : QTreeView( parent )
  56. , m_latchManager( new LatchManager( this ) )
  57. , m_dragging( false )
  58. {
  59. setProperty( "flattenBranches", QVariant( true ) );
  60. setFrameShape( QFrame::NoFrame );
  61. setAttribute( Qt::WA_MacShowFocusRect, 0 );
  62. setContentsMargins( 0, 0, 0, 0 );
  63. QFont fnt;
  64. QFontMetrics fm( fnt );
  65. // This is sort of the longest string in there. With translations
  66. // we will never get it right so setting it to something reasonable for the average case
  67. setMinimumWidth( fm.width( "Track Album Artist Local Top10" ) );
  68. setHeaderHidden( true );
  69. setRootIsDecorated( true );
  70. setExpandsOnDoubleClick( false );
  71. setSelectionBehavior( QAbstractItemView::SelectRows );
  72. setDragDropMode( QAbstractItemView::DropOnly );
  73. setAcceptDrops( true );
  74. setDropIndicatorShown( false );
  75. setAllColumnsShowFocus( true );
  76. setUniformRowHeights( false );
  77. setIndentation( 0 );
  78. setSortingEnabled( true );
  79. sortByColumn( 0, Qt::AscendingOrder );
  80. setVerticalScrollMode( QTreeView::ScrollPerPixel );
  81. setMouseTracking( true );
  82. setEditTriggers( NoEditTriggers );
  83. setAutoExpandDelay( 500 );
  84. // TODO animation conflicts with the expanding-playlists-when-collection-is-null
  85. // so investigate
  86. // setAnimated( true );
  87. m_delegate = new SourceDelegate( this );
  88. connect( m_delegate, SIGNAL( latchOn( Tomahawk::source_ptr ) ), SLOT( latchOnOrCatchUp( Tomahawk::source_ptr ) ) );
  89. connect( m_delegate, SIGNAL( latchOff( Tomahawk::source_ptr ) ), SLOT( latchOff( Tomahawk::source_ptr ) ) );
  90. connect( m_delegate, SIGNAL( toggleRealtimeLatch( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr,bool ) ) );
  91. connect( m_delegate, SIGNAL( clicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) );
  92. connect( m_delegate, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemDoubleClicked( QModelIndex ) ) );
  93. setItemDelegate( m_delegate );
  94. setContextMenuPolicy( Qt::CustomContextMenu );
  95. connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) );
  96. m_model = new SourcesModel( this );
  97. m_proxyModel = new SourcesProxyModel( m_model, this );
  98. connect( m_proxyModel, SIGNAL( selectRequest( QPersistentModelIndex ) ), SLOT( selectRequest( QPersistentModelIndex ) ), Qt::QueuedConnection );
  99. connect( m_proxyModel, SIGNAL( expandRequest( QPersistentModelIndex ) ), SLOT( expandRequest( QPersistentModelIndex ) ) );
  100. connect( m_proxyModel, SIGNAL( toggleExpandRequest( QPersistentModelIndex ) ), SLOT( toggleExpandRequest( QPersistentModelIndex ) ) );
  101. setModel( m_proxyModel );
  102. header()->setStretchLastSection( false );
  103. header()->setResizeMode( 0, QHeaderView::Stretch );
  104. connect( this, SIGNAL( expanded( QModelIndex ) ), SLOT( onItemExpanded( QModelIndex ) ) );
  105. connect( selectionModel(), SIGNAL( selectionChanged( QItemSelection, QItemSelection ) ), SLOT( onSelectionChanged() ) );
  106. showOfflineSources( TomahawkSettings::instance()->showOfflineSources() );
  107. // Light-blue sourcetree on osx
  108. #ifdef Q_WS_MAC
  109. setStyleSheet( "SourceTreeView:active { background: #DDE4EB; } "
  110. "SourceTreeView { background: #EDEDED; } " );
  111. #endif
  112. connect( this, SIGNAL( latchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( latchRequest( Tomahawk::source_ptr ) ) );
  113. connect( this, SIGNAL( unlatchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( unlatchRequest( Tomahawk::source_ptr ) ) );
  114. connect( this, SIGNAL( catchUpRequest() ), m_latchManager, SLOT( catchUpRequest() ) );
  115. connect( this, SIGNAL( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ) );
  116. connect( ActionCollection::instance(), SIGNAL( privacyModeChanged() ), SLOT( repaint() ) );
  117. }
  118. SourceTreeView::~SourceTreeView()
  119. {
  120. }
  121. void
  122. SourceTreeView::setupMenus()
  123. {
  124. m_playlistMenu.clear();
  125. m_roPlaylistMenu.clear();
  126. m_latchMenu.clear();
  127. m_privacyMenu.clear();
  128. bool readonly = true;
  129. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  130. if ( type == SourcesModel::StaticPlaylist || type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
  131. {
  132. const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  133. const playlist_ptr playlist = item->playlist();
  134. if ( !playlist.isNull() )
  135. {
  136. readonly = !playlist->author()->isLocal();
  137. }
  138. }
  139. QAction* latchOnAction = ActionCollection::instance()->getAction( "latchOn" );
  140. m_latchMenu.addAction( latchOnAction );
  141. m_privacyMenu.addAction( ActionCollection::instance()->getAction( "togglePrivacy" ) );
  142. if ( type == SourcesModel::Collection )
  143. {
  144. SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
  145. source_ptr source = item->source();
  146. if ( !source.isNull() )
  147. {
  148. if ( m_latchManager->isLatched( source ) )
  149. {
  150. QAction* latchOffAction = ActionCollection::instance()->getAction( "latchOff" );
  151. m_latchMenu.addAction( latchOffAction );
  152. connect( latchOffAction, SIGNAL( triggered() ), SLOT( latchOff() ) );
  153. m_latchMenu.addSeparator();
  154. QAction* latchRealtimeAction = ActionCollection::instance()->getAction( "realtimeFollowingAlong" );
  155. latchRealtimeAction->setChecked( source->playlistInterface()->latchMode() == Tomahawk::PlaylistModes::RealTime );
  156. m_latchMenu.addAction( latchRealtimeAction );
  157. connect( latchRealtimeAction, SIGNAL( toggled( bool ) ), SLOT( latchModeToggled( bool ) ) );
  158. }
  159. }
  160. }
  161. QAction* loadPlaylistAction = ActionCollection::instance()->getAction( "loadPlaylist" );
  162. m_playlistMenu.addAction( loadPlaylistAction );
  163. QAction* renamePlaylistAction = ActionCollection::instance()->getAction( "renamePlaylist" );
  164. m_playlistMenu.addAction( renamePlaylistAction );
  165. m_playlistMenu.addSeparator();
  166. QAction* copyPlaylistAction = m_playlistMenu.addAction( tr( "&Copy Link" ) );
  167. if ( type == SourcesModel::StaticPlaylist )
  168. {
  169. QAction* exportPlaylist = m_playlistMenu.addAction( tr( "&Export Playlist") );
  170. connect( exportPlaylist, SIGNAL( triggered() ), this, SLOT( exportPlaylist() ) );
  171. }
  172. QAction* deletePlaylistAction = m_playlistMenu.addAction( tr( "&Delete %1" ).arg( SourcesModel::rowTypeToString( type ) ) );
  173. QString addToText;
  174. if ( type == SourcesModel::StaticPlaylist )
  175. addToText = tr( "Add to my Playlists" );
  176. if ( type == SourcesModel::AutomaticPlaylist )
  177. addToText = tr( "Add to my Automatic Playlists" );
  178. else if ( type == SourcesModel::Station )
  179. addToText = tr( "Add to my Stations" );
  180. QAction* addToLocalAction = m_roPlaylistMenu.addAction( addToText );
  181. m_roPlaylistMenu.addAction( copyPlaylistAction );
  182. deletePlaylistAction->setEnabled( !readonly );
  183. renamePlaylistAction->setEnabled( !readonly );
  184. addToLocalAction->setEnabled( readonly );
  185. // Handle any custom actions registered for playlists
  186. if ( type == SourcesModel::StaticPlaylist && !readonly &&
  187. !ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ).isEmpty() )
  188. {
  189. m_playlistMenu.addSeparator();
  190. const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  191. const playlist_ptr playlist = item->playlist();
  192. foreach ( QAction* action, ActionCollection::instance()->getAction( ActionCollection::LocalPlaylists ) )
  193. {
  194. if ( QObject* notifier = ActionCollection::instance()->actionNotifier( action ) )
  195. {
  196. QMetaObject::invokeMethod( notifier, "aboutToShow", Qt::DirectConnection, Q_ARG( QAction*, action ), Q_ARG( Tomahawk::playlist_ptr, playlist ) );
  197. }
  198. action->setProperty( "payload", QVariant::fromValue< playlist_ptr >( playlist ) );
  199. m_playlistMenu.addAction( action );
  200. }
  201. }
  202. connect( loadPlaylistAction, SIGNAL( triggered() ), SLOT( loadPlaylist() ) );
  203. connect( renamePlaylistAction, SIGNAL( triggered() ), SLOT( renamePlaylist() ) );
  204. connect( deletePlaylistAction, SIGNAL( triggered() ), SLOT( deletePlaylist() ) );
  205. connect( copyPlaylistAction, SIGNAL( triggered() ), SLOT( copyPlaylistLink() ) );
  206. connect( addToLocalAction, SIGNAL( triggered() ), SLOT( addToLocal() ) );
  207. connect( latchOnAction, SIGNAL( triggered() ), SLOT( latchOnOrCatchUp() ), Qt::QueuedConnection );
  208. }
  209. void
  210. SourceTreeView::showOfflineSources( bool offlineSourcesShown )
  211. {
  212. m_proxyModel->showOfflineSources( offlineSourcesShown );
  213. }
  214. void
  215. SourceTreeView::onSelectionChanged()
  216. {
  217. if ( currentIndex() != m_selectedIndex )
  218. {
  219. selectionModel()->blockSignals( true );
  220. selectionModel()->select( m_selectedIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current );
  221. selectionModel()->blockSignals( false );
  222. }
  223. }
  224. void
  225. SourceTreeView::onItemActivated( const QModelIndex& index )
  226. {
  227. if ( !index.isValid() || !index.flags().testFlag( Qt::ItemIsEnabled ) )
  228. return;
  229. SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
  230. item->activate();
  231. }
  232. void
  233. SourceTreeView::onItemDoubleClicked( const QModelIndex& idx )
  234. {
  235. if ( !selectionModel()->selectedIndexes().contains( idx ) )
  236. onItemActivated( idx );
  237. SourceTreeItem* item = itemFromIndex< SourceTreeItem >( idx );
  238. item->doubleClicked();
  239. }
  240. void
  241. SourceTreeView::onItemExpanded( const QModelIndex& idx )
  242. {
  243. // make sure to expand children nodes for collections
  244. if ( idx.data( SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Collection )
  245. {
  246. for( int i = 0; i < model()->rowCount( idx ); i++ )
  247. {
  248. setExpanded( model()->index( i, 0, idx ), true );
  249. }
  250. }
  251. }
  252. void
  253. SourceTreeView::selectRequest( const QPersistentModelIndex& idx )
  254. {
  255. m_selectedIndex = idx;
  256. if ( !selectionModel()->selectedIndexes().contains( idx ) )
  257. {
  258. scrollTo( idx, QTreeView::EnsureVisible );
  259. selectionModel()->select( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current );
  260. }
  261. }
  262. void
  263. SourceTreeView::expandRequest( const QPersistentModelIndex& idx )
  264. {
  265. expand( idx );
  266. }
  267. void
  268. SourceTreeView::toggleExpandRequest( const QPersistentModelIndex& idx )
  269. {
  270. if ( isExpanded( idx ) )
  271. collapse( idx );
  272. else
  273. expand( idx );
  274. }
  275. void
  276. SourceTreeView::loadPlaylist()
  277. {
  278. onItemActivated( m_contextMenuIndex );
  279. }
  280. void
  281. SourceTreeView::deletePlaylist( const QModelIndex& idxIn )
  282. {
  283. QModelIndex idx = idxIn.isValid() ? idxIn : m_contextMenuIndex;
  284. if ( !idx.isValid() )
  285. return;
  286. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( idx, SourcesModel::SourceTreeItemTypeRole ).toInt();
  287. QString typeDesc;
  288. switch ( type )
  289. {
  290. case SourcesModel::StaticPlaylist:
  291. typeDesc = tr( "playlist" );
  292. break;
  293. case SourcesModel::AutomaticPlaylist:
  294. typeDesc = tr( "automatic playlist" );
  295. break;
  296. case SourcesModel::Station:
  297. typeDesc = tr( "station" );
  298. break;
  299. default:
  300. Q_ASSERT( false );
  301. }
  302. PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
  303. playlist_ptr playlist = item->playlist();
  304. QPoint rightCenter = viewport()->mapToGlobal( visualRect( idx ).topRight() + QPoint( 0, visualRect( idx ).height() / 2 ) );
  305. #ifdef Q_OS_WIN
  306. rightCenter = QApplication::activeWindow()->mapFromGlobal( rightCenter );
  307. #endif
  308. if ( playlist->hasCustomDeleter() )
  309. {
  310. playlist->customDelete( rightCenter );
  311. }
  312. else
  313. {
  314. if ( m_popupDialog.isNull() )
  315. {
  316. m_popupDialog = QWeakPointer< SourceTreePopupDialog >( new SourceTreePopupDialog() );
  317. connect( m_popupDialog.data(), SIGNAL( result( bool ) ), this, SLOT( onDeletePlaylistResult( bool ) ) );
  318. }
  319. m_popupDialog.data()->setMainText( tr( "Would you like to delete the %1 <b>\"%2\"</b>?", "e.g. Would you like to delete the playlist named Foobar?" )
  320. .arg( typeDesc ).arg( idx.data().toString() ) );
  321. m_popupDialog.data()->setOkButtonText( tr( "Delete" ) );
  322. m_popupDialog.data()->setProperty( "idx", QVariant::fromValue< QModelIndex >( idx ) );
  323. m_popupDialog.data()->move( rightCenter.x() - m_popupDialog.data()->offset(), rightCenter.y() - m_popupDialog.data()->sizeHint().height() / 2. );
  324. m_popupDialog.data()->show();
  325. }
  326. }
  327. void
  328. SourceTreeView::onDeletePlaylistResult( bool result )
  329. {
  330. Q_ASSERT( !m_popupDialog.isNull() );
  331. const QModelIndex idx = m_popupDialog.data()->property( "idx" ).value< QModelIndex >();
  332. Q_ASSERT( idx.isValid() );
  333. if ( !result )
  334. return;
  335. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( idx, SourcesModel::SourceTreeItemTypeRole ).toInt();
  336. if ( type == SourcesModel::StaticPlaylist )
  337. {
  338. PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
  339. playlist_ptr playlist = item->playlist();
  340. qDebug() << "Doing delete of playlist:" << playlist->title();
  341. Playlist::remove( playlist );
  342. }
  343. else if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
  344. {
  345. DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( idx );
  346. dynplaylist_ptr playlist = item->dynPlaylist();
  347. qDebug() << "Doing delete of playlist:" << playlist->title();
  348. DynamicPlaylist::remove( playlist );
  349. }
  350. }
  351. void
  352. SourceTreeView::copyPlaylistLink()
  353. {
  354. QModelIndex idx = m_contextMenuIndex;
  355. if ( !idx.isValid() )
  356. return;
  357. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  358. if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
  359. {
  360. DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( m_contextMenuIndex );
  361. dynplaylist_ptr playlist = item->dynPlaylist();
  362. GlobalActionManager::instance()->copyPlaylistToClipboard( playlist );
  363. }
  364. else if ( type == SourcesModel::StaticPlaylist )
  365. {
  366. const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  367. const playlist_ptr playlist = item->playlist();
  368. GlobalActionManager::instance()->getShortLink( playlist );
  369. }
  370. }
  371. void
  372. SourceTreeView::exportPlaylist()
  373. {
  374. const QModelIndex idx = m_contextMenuIndex;
  375. if ( !idx.isValid() )
  376. return;
  377. const SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  378. Q_ASSERT( type == SourcesModel::StaticPlaylist );
  379. if ( type != SourcesModel::StaticPlaylist )
  380. return;
  381. const PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  382. const playlist_ptr playlist = item->playlist();
  383. const QString suggestedFilename = TomahawkSettings::instance()->playlistDefaultPath() + "/" + playlist->title();
  384. const QString filename = QFileDialog::getSaveFileName( TomahawkUtils::tomahawkWindow(), tr( "Save XSPF" ),
  385. suggestedFilename, tr( "Playlists (*.xspf)" ) );
  386. if ( !filename.isEmpty() )
  387. {
  388. const QFileInfo playlistAbsoluteFilePath( filename );
  389. TomahawkSettings::instance()->setPlaylistDefaultPath( playlistAbsoluteFilePath.absolutePath() );
  390. GlobalActionManager::instance()->savePlaylistToFile( playlist, filename );
  391. }
  392. }
  393. void
  394. SourceTreeView::addToLocal()
  395. {
  396. QModelIndex idx = m_contextMenuIndex;
  397. if ( !idx.isValid() )
  398. return;
  399. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  400. if ( type == SourcesModel::AutomaticPlaylist || type == SourcesModel::Station )
  401. {
  402. DynamicPlaylistItem* item = itemFromIndex< DynamicPlaylistItem >( m_contextMenuIndex );
  403. dynplaylist_ptr playlist = item->dynPlaylist();
  404. // copy to a link and then generate a new playlist from that
  405. // this way we cheaply regenerate the needed controls
  406. QString link = GlobalActionManager::instance()->copyPlaylistToClipboard( playlist );
  407. GlobalActionManager::instance()->parseTomahawkLink( link );
  408. }
  409. else if ( type == SourcesModel::StaticPlaylist )
  410. {
  411. PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  412. playlist_ptr playlist = item->playlist();
  413. // just create the new playlist with the same values
  414. QList< query_ptr > queries;
  415. foreach( const plentry_ptr& e, playlist->entries() )
  416. queries << e->query();
  417. playlist_ptr newpl = Playlist::create( SourceList::instance()->getLocal(), uuid(), playlist->title(), playlist->info(), playlist->creator(), playlist->shared(), queries );
  418. }
  419. }
  420. void
  421. SourceTreeView::latchOnOrCatchUp()
  422. {
  423. disconnect( this, SLOT( latchOnOrCatchUp() ) );
  424. if ( !m_contextMenuIndex.isValid() )
  425. return;
  426. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  427. if ( type != SourcesModel::Collection )
  428. return;
  429. SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
  430. source_ptr source = item->source();
  431. latchOnOrCatchUp( source );
  432. }
  433. void
  434. SourceTreeView::latchOff()
  435. {
  436. disconnect( this, SLOT( latchOff() ) );
  437. qDebug() << Q_FUNC_INFO;
  438. if ( !m_contextMenuIndex.isValid() )
  439. return;
  440. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  441. if ( type != SourcesModel::Collection )
  442. return;
  443. const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
  444. const source_ptr source = item->source();
  445. latchOff( source );
  446. }
  447. void
  448. SourceTreeView::latchOnOrCatchUp( const Tomahawk::source_ptr& source )
  449. {
  450. if ( m_latchManager->isLatched( source ) )
  451. emit catchUpRequest();
  452. else
  453. emit latchRequest( source );
  454. }
  455. void
  456. SourceTreeView::latchOff( const Tomahawk::source_ptr& source )
  457. {
  458. emit unlatchRequest( source );
  459. }
  460. void
  461. SourceTreeView::latchModeToggled( bool checked )
  462. {
  463. disconnect( this, SLOT( latchOff() ) );
  464. qDebug() << Q_FUNC_INFO;
  465. if ( !m_contextMenuIndex.isValid() )
  466. return;
  467. SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt();
  468. if ( type != SourcesModel::Collection )
  469. return;
  470. const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
  471. const source_ptr source = item->source();
  472. emit latchModeChangeRequest( source, checked );
  473. }
  474. void
  475. SourceTreeView::renamePlaylist()
  476. {
  477. if ( !m_contextMenuIndex.isValid() && !selectionModel()->selectedIndexes().isEmpty() )
  478. edit( selectionModel()->selectedIndexes().first() );
  479. else
  480. edit( m_contextMenuIndex );
  481. }
  482. void
  483. SourceTreeView::onCustomContextMenu( const QPoint& pos )
  484. {
  485. qDebug() << Q_FUNC_INFO;
  486. QModelIndex idx = m_contextMenuIndex = indexAt( pos );
  487. if ( !idx.isValid() )
  488. return;
  489. setupMenus();
  490. const QList< QAction* > customActions = model()->data( m_contextMenuIndex, SourcesModel::CustomActionRole ).value< QList< QAction* > >();
  491. if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::StaticPlaylist ||
  492. model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::AutomaticPlaylist ||
  493. model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Station )
  494. {
  495. PlaylistItem* item = itemFromIndex< PlaylistItem >( m_contextMenuIndex );
  496. if ( item->playlist()->author()->isLocal() )
  497. m_playlistMenu.exec( mapToGlobal( pos ) );
  498. else
  499. m_roPlaylistMenu.exec( mapToGlobal( pos ) );
  500. }
  501. else if ( model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Collection )
  502. {
  503. SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex );
  504. if ( !item->source().isNull() && !item->source()->isLocal() )
  505. m_latchMenu.exec( mapToGlobal( pos ) );
  506. else if ( !item->source().isNull() )
  507. m_privacyMenu.exec( mapToGlobal( pos ) );
  508. }
  509. else if ( !customActions.isEmpty() )
  510. {
  511. QMenu customMenu;
  512. customMenu.addActions( customActions );
  513. customMenu.exec( mapToGlobal( pos ) );
  514. }
  515. }
  516. void
  517. SourceTreeView::dragEnterEvent( QDragEnterEvent* event )
  518. {
  519. qDebug() << Q_FUNC_INFO;
  520. QTreeView::dragEnterEvent( event );
  521. if ( DropJob::acceptsMimeData( event->mimeData(), DropJob::Track | DropJob::Artist | DropJob::Album | DropJob::Playlist, DropJob::Create ) )
  522. {
  523. m_dragging = true;
  524. m_dropRect = QRect();
  525. m_dropIndex = QPersistentModelIndex();
  526. event->setDropAction( Qt::CopyAction );
  527. event->acceptProposedAction();
  528. }
  529. }
  530. void
  531. SourceTreeView::dragLeaveEvent( QDragLeaveEvent* event )
  532. {
  533. QTreeView::dragLeaveEvent( event );
  534. m_dragging = false;
  535. setDirtyRegion( m_dropRect );
  536. m_delegate->dragLeaveEvent();
  537. dataChanged( m_dropIndex, m_dropIndex );
  538. m_dropIndex = QPersistentModelIndex();
  539. }
  540. void
  541. SourceTreeView::dragMoveEvent( QDragMoveEvent* event )
  542. {
  543. bool accept = false;
  544. // Don't highlight the drop for a playlist, as it won't get added to the playlist but created generally
  545. if ( DropJob::isDropType( DropJob::Playlist, event->mimeData() ) )
  546. {
  547. event->accept();
  548. return;
  549. }
  550. QTreeView::dragMoveEvent( event );
  551. if ( DropJob::acceptsMimeData( event->mimeData(), DropJob::Track, DropJob::Append ) )
  552. {
  553. setDirtyRegion( m_dropRect );
  554. const QPoint pos = event->pos();
  555. const QModelIndex index = indexAt( pos );
  556. dataChanged( m_dropIndex, m_dropIndex );
  557. m_dropIndex = QPersistentModelIndex( index );
  558. if ( index.isValid() )
  559. {
  560. const QRect rect = visualRect( index );
  561. m_dropRect = rect;
  562. SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
  563. if ( item->willAcceptDrag( event->mimeData() ) )
  564. {
  565. accept = true;
  566. switch ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() )
  567. {
  568. case SourcesModel::StaticPlaylist:
  569. case SourcesModel::CategoryAdd:
  570. m_delegate->hovered( index, event->mimeData() );
  571. dataChanged( index, index );
  572. break;
  573. default:
  574. break;
  575. }
  576. }
  577. else
  578. m_delegate->hovered( QModelIndex(), 0 );
  579. }
  580. else
  581. {
  582. m_dropRect = QRect();
  583. }
  584. if ( accept || DropJob::isDropType( DropJob::Playlist, event->mimeData() ) )
  585. {
  586. // Playlists are accepted always since they can be dropped anywhere
  587. //tDebug() << Q_FUNC_INFO << "Accepting";
  588. event->setDropAction( Qt::CopyAction );
  589. event->accept();
  590. }
  591. else
  592. {
  593. // tDebug() << Q_FUNC_INFO << "Ignoring";
  594. event->ignore();
  595. }
  596. }
  597. else if ( DropJob::acceptsMimeData( event->mimeData(), DropJob::Playlist | DropJob::Artist | DropJob::Album, DropJob::Create ) )
  598. {
  599. event->setDropAction( Qt::CopyAction );
  600. event->accept();
  601. }
  602. setDirtyRegion( m_dropRect );
  603. }
  604. void
  605. SourceTreeView::dropEvent( QDropEvent* event )
  606. {
  607. const QPoint pos = event->pos();
  608. const QModelIndex index = indexAt( pos );
  609. if ( model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::StaticPlaylist
  610. || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::LovedTracksPage
  611. || model()->data( index, SourcesModel::SourceTreeItemTypeRole ).toInt() == SourcesModel::CategoryAdd )
  612. {
  613. SourceTreeItem* item = itemFromIndex< SourceTreeItem >( index );
  614. Q_ASSERT( item );
  615. item->setDropType( m_delegate->hoveredDropType() );
  616. qDebug() << Q_FUNC_INFO << "dropType is " << m_delegate->hoveredDropType();
  617. }
  618. // if it's a playlist drop, accept it anywhere in the sourcetree by manually parsing it.
  619. if ( DropJob::isDropType( DropJob::Playlist, event->mimeData() ) )
  620. {
  621. qDebug() << Q_FUNC_INFO << "Current Event";
  622. DropJob* dropThis = new DropJob;
  623. dropThis->setDropTypes( DropJob::Playlist );
  624. dropThis->setDropAction( DropJob::Create );
  625. dropThis->parseMimeData( event->mimeData() );
  626. // Don't add it to the playlist under drop, it's a new playlist now
  627. return;
  628. }
  629. // Need to fake the dropevent because the treeview would reject it if it is outside the item (on the tree)
  630. if ( pos.x() < 100 )
  631. {
  632. QDropEvent* newEvent = new QDropEvent( pos + QPoint( 100, 0 ), event->possibleActions(), event->mimeData(), event->mouseButtons(), event->keyboardModifiers(), event->type() );
  633. QTreeView::dropEvent( newEvent );
  634. delete newEvent;
  635. }
  636. else
  637. {
  638. QTreeView::dropEvent( event );
  639. }
  640. m_dragging = false;
  641. m_dropIndex = QPersistentModelIndex();
  642. m_delegate->dragLeaveEvent();
  643. dataChanged( index, index );
  644. }
  645. void
  646. SourceTreeView::keyPressEvent( QKeyEvent* event )
  647. {
  648. if ( ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace ) && !selectionModel()->selectedIndexes().isEmpty() )
  649. {
  650. QModelIndex idx = selectionModel()->selectedIndexes().first();
  651. if ( model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::StaticPlaylist ||
  652. model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::AutomaticPlaylist ||
  653. model()->data( idx, SourcesModel::SourceTreeItemTypeRole ) == SourcesModel::Station )
  654. {
  655. PlaylistItem* item = itemFromIndex< PlaylistItem >( idx );
  656. Q_ASSERT( item );
  657. if ( item->playlist()->author()->isLocal() )
  658. deletePlaylist( idx );
  659. }
  660. event->accept();
  661. }
  662. else
  663. {
  664. event->ignore();
  665. }
  666. QTreeView::keyPressEvent( event );
  667. }
  668. void
  669. SourceTreeView::paintEvent( QPaintEvent* event )
  670. {
  671. if ( m_dragging && !m_dropRect.isEmpty() )
  672. {
  673. QPainter painter( viewport() );
  674. const QRect itemRect = visualRect( m_dropIndex );
  675. QStyleOptionViewItemV4 opt;
  676. opt.initFrom( this );
  677. opt.rect = itemRect;
  678. opt.state = QStyle::State_Enabled | QStyle::State_Selected;
  679. style()->drawPrimitive( QStyle::PE_PanelItemViewRow, &opt, &painter, this );
  680. }
  681. QTreeView::paintEvent( event );
  682. }
  683. void
  684. SourceTreeView::drawRow( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
  685. {
  686. QTreeView::drawRow( painter, option, index );
  687. }
  688. void
  689. SourceTreeView::drawBranches( QPainter *painter, const QRect &rect, const QModelIndex &index ) const
  690. {
  691. if( !QString( qApp->style()->metaObject()->className() ).toLower().contains( "qtcurve" ) )
  692. QTreeView::drawBranches( painter, rect, index );
  693. }
  694. template< typename T > T*
  695. SourceTreeView::itemFromIndex( const QModelIndex& index ) const
  696. {
  697. Q_ASSERT( model()->data( index, SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
  698. T* item = qobject_cast< T* >( model()->data( index, SourcesModel::SourceTreeItemRole ).value< SourceTreeItem* >() );
  699. Q_ASSERT( item );
  700. return item;
  701. }
  702. void
  703. SourceTreeView::update( const QModelIndex &index )
  704. {
  705. dataChanged( index, index );
  706. }