/src/libtomahawk/playlist/TrackView.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 1026 lines · 776 code · 213 blank · 37 comment · 136 complexity · e0f1a531305d31a1cda34d99e52caf5a MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. * Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
  5. *
  6. * Tomahawk is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Tomahawk is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "TrackView.h"
  20. #include "ViewHeader.h"
  21. #include "PlayableModel.h"
  22. #include "PlayableProxyModel.h"
  23. #include "PlayableItem.h"
  24. #include "DownloadManager.h"
  25. #include "DropJob.h"
  26. #include "Result.h"
  27. #include "Source.h"
  28. #include "TomahawkSettings.h"
  29. #include "audio/AudioEngine.h"
  30. #include "widgets/OverlayWidget.h"
  31. #include "utils/TomahawkUtilsGui.h"
  32. #include "utils/Closure.h"
  33. #include "utils/AnimatedSpinner.h"
  34. #include "utils/Logger.h"
  35. #include "InboxModel.h"
  36. #include <QKeyEvent>
  37. #include <QPainter>
  38. #include <QScrollBar>
  39. #include <QDrag>
  40. // HACK
  41. #include <QTableView>
  42. #define SCROLL_TIMEOUT 280
  43. using namespace Tomahawk;
  44. TrackView::TrackView( QWidget* parent )
  45. : QTreeView( parent )
  46. , m_model( 0 )
  47. , m_proxyModel( 0 )
  48. , m_delegate( 0 )
  49. , m_header( new ViewHeader( this ) )
  50. , m_overlay( new OverlayWidget( this ) )
  51. , m_loadingSpinner( new LoadingSpinner( this ) )
  52. , m_resizing( false )
  53. , m_dragging( false )
  54. , m_alternatingRowColors( false )
  55. , m_autoExpanding( true )
  56. , m_contextMenu( new ContextMenu( this ) )
  57. {
  58. setFrameShape( QFrame::NoFrame );
  59. setAttribute( Qt::WA_MacShowFocusRect, 0 );
  60. setContentsMargins( 0, 0, 0, 0 );
  61. setMouseTracking( true );
  62. setSelectionMode( QAbstractItemView::ExtendedSelection );
  63. setSelectionBehavior( QAbstractItemView::SelectRows );
  64. setDragEnabled( true );
  65. setDropIndicatorShown( false );
  66. setDragDropMode( QAbstractItemView::InternalMove );
  67. setDragDropOverwriteMode( false );
  68. setAllColumnsShowFocus( true );
  69. setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
  70. setRootIsDecorated( false );
  71. setUniformRowHeights( true );
  72. setAlternatingRowColors( m_alternatingRowColors );
  73. setAutoResize( false );
  74. setEditTriggers( NoEditTriggers );
  75. setHeader( m_header );
  76. // HACK: enable moving of first column: QTBUG-33974 / https://github.com/qtproject/qtbase/commit/e0fc088c0c8bc61dbcaf5928b24986cd61a22777
  77. QTableView unused;
  78. unused.setVerticalHeader( header() );
  79. header()->setParent( this );
  80. unused.setVerticalHeader( new QHeaderView( Qt::Horizontal, &unused ) );
  81. setSortingEnabled( true );
  82. sortByColumn( -1 );
  83. setContextMenuPolicy( Qt::CustomContextMenu );
  84. m_timer.setInterval( SCROLL_TIMEOUT );
  85. // enable those connects if you want to enable lazily loading extra information for visible items
  86. // connect( verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), SLOT( onViewChanged() ) );
  87. // connect( verticalScrollBar(), SIGNAL( valueChanged( int ) ), SLOT( onViewChanged() ) );
  88. // connect( &m_timer, SIGNAL( timeout() ), SLOT( onScrollTimeout() ) );
  89. connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) );
  90. connect( this, SIGNAL( customContextMenuRequested( const QPoint& ) ), SLOT( onCustomContextMenu( const QPoint& ) ) );
  91. connect( m_contextMenu, SIGNAL( triggered( int ) ), SLOT( onMenuTriggered( int ) ) );
  92. setProxyModel( new PlayableProxyModel( this ) );
  93. }
  94. TrackView::~TrackView()
  95. {
  96. tDebug() << Q_FUNC_INFO << ( m_guid.isEmpty() ? QString( "with empty guid" ) : QString( "with guid %1" ).arg( m_guid ) );
  97. if ( !m_guid.isEmpty() && proxyModel()->playlistInterface() )
  98. {
  99. tDebug() << Q_FUNC_INFO << "Storing shuffle & random mode settings for guid" << m_guid;
  100. TomahawkSettings* s = TomahawkSettings::instance();
  101. s->setShuffleState( m_guid, proxyModel()->playlistInterface()->shuffled() );
  102. s->setRepeatMode( m_guid, proxyModel()->playlistInterface()->repeatMode() );
  103. }
  104. }
  105. QString
  106. TrackView::guid() const
  107. {
  108. if ( m_guid.isEmpty() )
  109. return QString();
  110. return QString( "%1/%2" ).arg( m_guid ).arg( m_proxyModel->columnCount() );
  111. }
  112. void
  113. TrackView::setGuid( const QString& newguid )
  114. {
  115. if ( newguid == m_guid )
  116. return;
  117. if ( !newguid.isEmpty() )
  118. {
  119. tDebug() << Q_FUNC_INFO << "Setting guid on header" << newguid << "for a view with" << m_proxyModel->columnCount() << "columns";
  120. m_guid = newguid;
  121. m_header->setGuid( guid() );
  122. if ( !m_guid.isEmpty() && proxyModel()->playlistInterface() )
  123. {
  124. tDebug() << Q_FUNC_INFO << "Restoring shuffle & random mode settings for guid" << m_guid;
  125. TomahawkSettings* s = TomahawkSettings::instance();
  126. proxyModel()->playlistInterface()->setShuffled( s->shuffleState( m_guid ) );
  127. proxyModel()->playlistInterface()->setRepeatMode( s->repeatMode( m_guid ) );
  128. }
  129. }
  130. }
  131. void
  132. TrackView::setProxyModel( PlayableProxyModel* model )
  133. {
  134. if ( m_proxyModel )
  135. {
  136. disconnect( m_proxyModel, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), this, SLOT( onModelFilling() ) );
  137. disconnect( m_proxyModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( onModelEmptyCheck() ) );
  138. disconnect( m_proxyModel, SIGNAL( filterChanged( QString ) ), this, SLOT( onFilterChanged( QString ) ) );
  139. disconnect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( onViewChanged() ) );
  140. disconnect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), this, SLOT( verifySize() ) );
  141. disconnect( m_proxyModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), this, SLOT( verifySize() ) );
  142. disconnect( m_proxyModel, SIGNAL( expandRequest( QPersistentModelIndex ) ), this, SLOT( expand( QPersistentModelIndex ) ) );
  143. disconnect( m_proxyModel, SIGNAL( selectRequest( QPersistentModelIndex ) ), this, SLOT( select( QPersistentModelIndex ) ) );
  144. disconnect( m_proxyModel, SIGNAL( currentIndexChanged( QModelIndex, QModelIndex ) ), this, SLOT( onCurrentIndexChanged( QModelIndex, QModelIndex ) ) );
  145. }
  146. m_proxyModel = model;
  147. connect( m_proxyModel, SIGNAL( rowsAboutToBeInserted( QModelIndex, int, int ) ), SLOT( onModelFilling() ) );
  148. connect( m_proxyModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), SLOT( onModelEmptyCheck() ) );
  149. connect( m_proxyModel, SIGNAL( filterChanged( QString ) ), SLOT( onFilterChanged( QString ) ) );
  150. connect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( onViewChanged() ) );
  151. connect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( verifySize() ) );
  152. connect( m_proxyModel, SIGNAL( rowsRemoved( QModelIndex, int, int ) ), SLOT( verifySize() ) );
  153. connect( m_proxyModel, SIGNAL( expandRequest( QPersistentModelIndex ) ), SLOT( expand( QPersistentModelIndex ) ) );
  154. connect( m_proxyModel, SIGNAL( selectRequest( QPersistentModelIndex ) ), SLOT( select( QPersistentModelIndex ) ) );
  155. connect( m_proxyModel, SIGNAL( currentIndexChanged( QModelIndex, QModelIndex ) ), SLOT( onCurrentIndexChanged( QModelIndex, QModelIndex ) ) );
  156. m_delegate = new PlaylistItemDelegate( this, m_proxyModel );
  157. QTreeView::setItemDelegate( m_delegate );
  158. QTreeView::setModel( m_proxyModel );
  159. }
  160. void
  161. TrackView::setModel( QAbstractItemModel* model )
  162. {
  163. Q_UNUSED( model );
  164. tDebug() << "Explicitly use setPlayableModel instead";
  165. Q_ASSERT( false );
  166. }
  167. void
  168. TrackView::setPlaylistItemDelegate( PlaylistItemDelegate* delegate )
  169. {
  170. if ( m_delegate )
  171. delete m_delegate;
  172. m_delegate = delegate;
  173. QTreeView::setItemDelegate( delegate );
  174. verifySize();
  175. }
  176. void
  177. TrackView::setPlayableModel( PlayableModel* model )
  178. {
  179. if ( m_model )
  180. {
  181. disconnect( m_model, SIGNAL( loadingStarted() ), m_loadingSpinner, SLOT( fadeIn() ) );
  182. disconnect( m_model, SIGNAL( loadingFinished() ), m_loadingSpinner, SLOT( fadeOut() ) );
  183. disconnect( m_model, SIGNAL( changed() ), this, SIGNAL( modelChanged() ) );
  184. }
  185. m_model = model;
  186. if ( m_proxyModel )
  187. {
  188. m_proxyModel->setSourcePlayableModel( m_model );
  189. }
  190. setAcceptDrops( true );
  191. m_header->setDefaultColumnWeights( m_proxyModel->columnWeights() );
  192. setGuid( m_proxyModel->guid() );
  193. switch( m_proxyModel->style() )
  194. {
  195. case PlayableProxyModel::SingleColumn:
  196. setHeaderHidden( true );
  197. setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
  198. break;
  199. default:
  200. setHeaderHidden( false );
  201. setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
  202. }
  203. connect( m_model, SIGNAL( loadingStarted() ), m_loadingSpinner, SLOT( fadeIn() ) );
  204. connect( m_model, SIGNAL( loadingFinished() ), m_loadingSpinner, SLOT( fadeOut() ) );
  205. connect( m_model, SIGNAL( changed() ), SIGNAL( modelChanged() ) );
  206. if ( m_model->isLoading() )
  207. m_loadingSpinner->fadeIn();
  208. if ( m_autoExpanding )
  209. {
  210. expandAll();
  211. selectFirstTrack();
  212. }
  213. onViewChanged();
  214. emit modelChanged();
  215. }
  216. void
  217. TrackView::setEmptyTip( const QString& tip )
  218. {
  219. m_emptyTip = tip;
  220. m_overlay->setText( tip );
  221. }
  222. void
  223. TrackView::onModelFilling()
  224. {
  225. QTreeView::setAlternatingRowColors( m_alternatingRowColors );
  226. }
  227. void
  228. TrackView::onModelEmptyCheck()
  229. {
  230. if ( !m_proxyModel->rowCount( QModelIndex() ) )
  231. QTreeView::setAlternatingRowColors( false );
  232. }
  233. void
  234. TrackView::onCurrentIndexChanged( const QModelIndex& newIndex, const QModelIndex& oldIndex )
  235. {
  236. if ( selectedIndexes().count() == 1 && currentIndex() == oldIndex )
  237. {
  238. selectionModel()->select( newIndex, QItemSelectionModel::SelectCurrent );
  239. currentChanged( newIndex, oldIndex );
  240. setCurrentIndex( newIndex );
  241. }
  242. }
  243. void
  244. TrackView::onViewChanged()
  245. {
  246. if ( m_timer.isActive() )
  247. m_timer.stop();
  248. m_timer.start();
  249. }
  250. void
  251. TrackView::onScrollTimeout()
  252. {
  253. if ( m_timer.isActive() )
  254. m_timer.stop();
  255. QModelIndex left = indexAt( viewport()->rect().topLeft() );
  256. while ( left.isValid() && left.parent().isValid() )
  257. left = left.parent();
  258. QModelIndex right = indexAt( viewport()->rect().bottomLeft() );
  259. while ( right.isValid() && right.parent().isValid() )
  260. right = right.parent();
  261. int max = m_proxyModel->playlistInterface()->trackCount();
  262. if ( right.isValid() )
  263. max = right.row();
  264. if ( !max )
  265. return;
  266. //FIXME
  267. for ( int i = left.row(); i <= max; i++ )
  268. {
  269. m_proxyModel->updateDetailedInfo( m_proxyModel->index( i, 0 ) );
  270. }
  271. }
  272. void
  273. TrackView::startPlayingFromStart()
  274. {
  275. if ( m_proxyModel->rowCount() == 0 )
  276. return;
  277. const QModelIndex index = m_proxyModel->index( 0, 0 );
  278. startAutoPlay( index );
  279. }
  280. void
  281. TrackView::autoPlayResolveFinished( const query_ptr& query, int row )
  282. {
  283. Q_ASSERT( !query.isNull() );
  284. Q_ASSERT( row >= 0 );
  285. if ( query.isNull() || row < 0 || query != m_autoPlaying )
  286. return;
  287. const QModelIndex index = m_proxyModel->index( row, 0 );
  288. if ( query->playable() )
  289. {
  290. onItemActivated( index );
  291. return;
  292. }
  293. // Try the next one..
  294. const QModelIndex sib = index.sibling( index.row() + 1, index.column() );
  295. if ( sib.isValid() )
  296. startAutoPlay( sib );
  297. }
  298. void
  299. TrackView::currentChanged( const QModelIndex& current, const QModelIndex& previous )
  300. {
  301. QTreeView::currentChanged( current, previous );
  302. if ( !m_model )
  303. return;
  304. PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( current ) );
  305. if ( item && item->query() )
  306. {
  307. emit querySelected( item->query() );
  308. }
  309. else
  310. {
  311. emit querySelected( query_ptr() );
  312. }
  313. }
  314. void
  315. TrackView::onItemActivated( const QModelIndex& index )
  316. {
  317. if ( !index.isValid() )
  318. return;
  319. tryToPlayItem( index );
  320. emit itemActivated( index );
  321. }
  322. void
  323. TrackView::startAutoPlay( const QModelIndex& index )
  324. {
  325. if ( tryToPlayItem( index ) )
  326. return;
  327. // item isn't playable but still resolving
  328. PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) );
  329. if ( item && !item->query().isNull() && !item->query()->resolvingFinished() )
  330. {
  331. m_autoPlaying = item->query(); // So we can kill it if user starts autoplaying this playlist again
  332. NewClosure( item->query().data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( autoPlayResolveFinished( Tomahawk::query_ptr, int ) ),
  333. item->query(), index.row() );
  334. return;
  335. }
  336. // not playable at all, try next
  337. const QModelIndex sib = index.sibling( index.row() + 1, index.column() );
  338. if ( sib.isValid() )
  339. startAutoPlay( sib );
  340. }
  341. bool
  342. TrackView::tryToPlayItem( const QModelIndex& index )
  343. {
  344. PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) );
  345. if ( item && !item->query().isNull() )
  346. {
  347. m_model->setCurrentIndex( m_proxyModel->mapToSource( index ) );
  348. AudioEngine::instance()->playItem( playlistInterface(), item->query() );
  349. return true;
  350. }
  351. return false;
  352. }
  353. void
  354. TrackView::keyPressEvent( QKeyEvent* event )
  355. {
  356. QTreeView::keyPressEvent( event );
  357. if ( !model() )
  358. return;
  359. if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
  360. {
  361. onItemActivated( currentIndex() );
  362. }
  363. if ( event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace )
  364. {
  365. tDebug() << "Removing selected items from playlist";
  366. deleteSelectedItems();
  367. }
  368. }
  369. void
  370. TrackView::onItemResized( const QModelIndex& index )
  371. {
  372. m_delegate->updateRowSize( index );
  373. }
  374. void
  375. TrackView::playItem()
  376. {
  377. onItemActivated( m_contextMenuIndex );
  378. }
  379. void
  380. TrackView::resizeEvent( QResizeEvent* event )
  381. {
  382. QTreeView::resizeEvent( event );
  383. int sortSection = m_header->sortIndicatorSection();
  384. Qt::SortOrder sortOrder = m_header->sortIndicatorOrder();
  385. if ( m_header->checkState() && sortSection >= 0 )
  386. {
  387. // restoreState keeps overwriting our previous sort-order
  388. sortByColumn( sortSection, sortOrder );
  389. }
  390. if ( !model() )
  391. return;
  392. if ( model()->columnCount() == 1 )
  393. {
  394. m_header->resizeSection( 0, event->size().width() );
  395. }
  396. }
  397. bool
  398. TrackView::eventFilter( QObject* obj, QEvent* event )
  399. {
  400. if ( event->type() == QEvent::DragEnter )
  401. {
  402. QDragEnterEvent* e = static_cast<QDragEnterEvent*>(event);
  403. dragEnterEvent( e );
  404. return true;
  405. }
  406. if ( event->type() == QEvent::DragMove )
  407. {
  408. QDragMoveEvent* e = static_cast<QDragMoveEvent*>(event);
  409. dragMoveEvent( e );
  410. return true;
  411. }
  412. if ( event->type() == QEvent::DragLeave )
  413. {
  414. QDragLeaveEvent* e = static_cast<QDragLeaveEvent*>(event);
  415. dragLeaveEvent( e );
  416. return true;
  417. }
  418. if ( event->type() == QEvent::Drop )
  419. {
  420. QDropEvent* e = static_cast<QDropEvent*>(event);
  421. dropEvent( e );
  422. return true;
  423. }
  424. return QObject::eventFilter( obj, event );
  425. }
  426. void
  427. TrackView::dragEnterEvent( QDragEnterEvent* event )
  428. {
  429. tDebug() << Q_FUNC_INFO;
  430. QTreeView::dragEnterEvent( event );
  431. if ( !model() || model()->isReadOnly() || model()->isLoading() )
  432. {
  433. event->ignore();
  434. return;
  435. }
  436. if ( DropJob::acceptsMimeData( event->mimeData() ) )
  437. {
  438. m_dragging = true;
  439. m_dropRect = QRect();
  440. event->acceptProposedAction();
  441. }
  442. }
  443. void
  444. TrackView::dragMoveEvent( QDragMoveEvent* event )
  445. {
  446. QTreeView::dragMoveEvent( event );
  447. if ( !model() || model()->isReadOnly() || model()->isLoading() )
  448. {
  449. event->ignore();
  450. return;
  451. }
  452. if ( DropJob::acceptsMimeData( event->mimeData() ) )
  453. {
  454. setDirtyRegion( m_dropRect );
  455. const QPoint pos = event->pos();
  456. QModelIndex index = indexAt( pos );
  457. bool pastLast = false;
  458. if ( !index.isValid() && proxyModel()->rowCount( QModelIndex() ) > 0 )
  459. {
  460. index = proxyModel()->index( proxyModel()->rowCount( QModelIndex() ) - 1, 0, QModelIndex() );
  461. pastLast = true;
  462. }
  463. if ( index.isValid() )
  464. {
  465. const QRect rect = visualRect( index );
  466. m_dropRect = rect;
  467. // indicate that the item will be inserted above the current place
  468. const int gap = 5; // FIXME constant
  469. int yHeight = ( pastLast ? rect.bottom() : rect.top() ) - gap / 2;
  470. m_dropRect = QRect( 0, yHeight, width(), gap );
  471. event->acceptProposedAction();
  472. }
  473. setDirtyRegion( m_dropRect );
  474. }
  475. }
  476. void
  477. TrackView::dragLeaveEvent( QDragLeaveEvent* event )
  478. {
  479. QTreeView::dragLeaveEvent( event );
  480. m_dragging = false;
  481. setDirtyRegion( m_dropRect );
  482. }
  483. void
  484. TrackView::dropEvent( QDropEvent* event )
  485. {
  486. tDebug() << Q_FUNC_INFO;
  487. QTreeView::dropEvent( event );
  488. if ( event->isAccepted() )
  489. {
  490. tDebug() << "Ignoring accepted event!";
  491. }
  492. else if ( event->source() != this )
  493. {
  494. // This code shouldn't be required when the PlayableModel properly accepts the incoming drop.
  495. // If we remove it, the queue for some reason doesn't accept the drops yet, though.
  496. if ( DropJob::acceptsMimeData( event->mimeData() ) )
  497. {
  498. const QPoint pos = event->pos();
  499. const QModelIndex index = indexAt( pos );
  500. if ( !model()->isReadOnly() && !model()->isLoading() )
  501. {
  502. tDebug() << Q_FUNC_INFO << "Drop Event accepted at row:" << index.row();
  503. event->acceptProposedAction();
  504. model()->dropMimeData( event->mimeData(), event->proposedAction(), index.row(), 0, index.parent() );
  505. }
  506. }
  507. }
  508. m_dragging = false;
  509. }
  510. void
  511. TrackView::leaveEvent( QEvent* event )
  512. {
  513. QTreeView::leaveEvent( event );
  514. m_delegate->resetHoverIndex();
  515. }
  516. void
  517. TrackView::paintEvent( QPaintEvent* event )
  518. {
  519. QTreeView::paintEvent( event );
  520. QPainter painter( viewport() );
  521. if ( m_dragging )
  522. {
  523. // draw drop indicator
  524. {
  525. // draw indicator for inserting items
  526. QBrush blendedBrush = viewOptions().palette.brush( QPalette::Normal, QPalette::Highlight );
  527. QColor color = blendedBrush.color();
  528. const int y = ( m_dropRect.top() + m_dropRect.bottom() ) / 2;
  529. const int thickness = m_dropRect.height() / 2;
  530. int alpha = 255;
  531. const int alphaDec = alpha / ( thickness + 1 );
  532. for ( int i = 0; i < thickness; i++ )
  533. {
  534. color.setAlpha( alpha );
  535. alpha -= alphaDec;
  536. painter.setPen( color );
  537. painter.drawLine( 0, y - i, width(), y - i );
  538. painter.drawLine( 0, y + i, width(), y + i );
  539. }
  540. }
  541. }
  542. }
  543. void
  544. TrackView::wheelEvent( QWheelEvent* event )
  545. {
  546. QTreeView::wheelEvent( event );
  547. m_delegate->resetHoverIndex();
  548. }
  549. void
  550. TrackView::onFilterChanged( const QString& )
  551. {
  552. if ( !selectedIndexes().isEmpty() )
  553. scrollTo( selectedIndexes().at( 0 ), QAbstractItemView::PositionAtCenter );
  554. if ( !filter().isEmpty() && !proxyModel()->playlistInterface()->trackCount() && model()->trackCount() )
  555. {
  556. m_overlay->setText( tr( "Sorry, your filter '%1' did not match any results." ).arg( filter() ) );
  557. m_overlay->show();
  558. }
  559. else
  560. {
  561. if ( model()->trackCount() )
  562. {
  563. m_overlay->hide();
  564. }
  565. else
  566. {
  567. m_overlay->setText( m_emptyTip );
  568. m_overlay->show();
  569. }
  570. }
  571. }
  572. void
  573. TrackView::startDrag( Qt::DropActions supportedActions )
  574. {
  575. QList<QPersistentModelIndex> pindexes;
  576. QModelIndexList indexes;
  577. foreach( const QModelIndex& idx, selectedIndexes() )
  578. {
  579. if ( ( m_proxyModel->flags( idx ) & Qt::ItemIsDragEnabled ) )
  580. {
  581. indexes << idx;
  582. pindexes << idx;
  583. }
  584. }
  585. if ( indexes.isEmpty() )
  586. return;
  587. tDebug() << "Dragging" << indexes.count() << "indexes";
  588. QMimeData* data = m_proxyModel->mimeData( indexes );
  589. if ( !data )
  590. return;
  591. QDrag* drag = new QDrag( this );
  592. drag->setMimeData( data );
  593. const QPixmap p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeTrack, indexes.count() );
  594. drag->setPixmap( p );
  595. drag->setHotSpot( QPoint( -20, -20 ) );
  596. Qt::DropAction action = drag->exec( supportedActions, Qt::CopyAction );
  597. if ( action == Qt::MoveAction )
  598. {
  599. m_proxyModel->removeIndexes( pindexes );
  600. }
  601. // delete drag; FIXME? On OSX it doesn't seem to get deleted automatically.
  602. }
  603. void
  604. TrackView::onCustomContextMenu( const QPoint& pos )
  605. {
  606. m_contextMenu->clear();
  607. m_contextMenu->setPlaylistInterface( playlistInterface() );
  608. QModelIndex idx = indexAt( pos );
  609. idx = idx.sibling( idx.row(), 0 );
  610. setContextMenuIndex( idx );
  611. if ( !idx.isValid() )
  612. return;
  613. if ( model() && !model()->isReadOnly() )
  614. m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionDelete );
  615. if ( model() && qobject_cast< InboxModel* >( model() ) )
  616. m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionMarkListened
  617. | ContextMenu::ActionDelete );
  618. if ( proxyModel()->style() != PlayableProxyModel::Collection )
  619. {
  620. bool allDownloaded = true;
  621. bool noneDownloadable = true;
  622. bool downloadable = false;
  623. foreach ( const QModelIndex& index, selectedIndexes() )
  624. {
  625. if ( index.column() )
  626. continue;
  627. PlayableItem* item = proxyModel()->itemFromIndex( proxyModel()->mapToSource( index ) );
  628. if( item->query()->results().isEmpty() )
  629. continue;
  630. downloadable = !item->query()->results().first()->downloadFormats().isEmpty();
  631. if ( downloadable )
  632. {
  633. noneDownloadable = false;
  634. }
  635. if ( downloadable && DownloadManager::instance()->localFileForDownload( item->query()->results().first()->downloadFormats().first().url.toString() ).isEmpty() )
  636. {
  637. allDownloaded = false;
  638. }
  639. if ( !allDownloaded || !noneDownloadable )
  640. {
  641. break;
  642. }
  643. }
  644. if ( !allDownloaded || !noneDownloadable )
  645. {
  646. m_contextMenu->setSupportedActions( m_contextMenu->supportedActions() | ContextMenu::ActionDownload );
  647. }
  648. }
  649. QList<query_ptr> queries;
  650. foreach ( const QModelIndex& index, selectedIndexes() )
  651. {
  652. if ( index.column() )
  653. continue;
  654. PlayableItem* item = proxyModel()->itemFromIndex( proxyModel()->mapToSource( index ) );
  655. if ( item && !item->query().isNull() )
  656. {
  657. queries << item->query();
  658. }
  659. }
  660. m_contextMenu->setQueries( queries );
  661. m_contextMenu->exec( viewport()->mapToGlobal( pos ) );
  662. }
  663. void
  664. TrackView::onMenuTriggered( int action )
  665. {
  666. switch ( action )
  667. {
  668. case ContextMenu::ActionPlay:
  669. onItemActivated( m_contextMenuIndex );
  670. break;
  671. case ContextMenu::ActionDelete:
  672. deleteSelectedItems();
  673. break;
  674. case ContextMenu::ActionDownload:
  675. downloadSelectedItems();
  676. break;
  677. default:
  678. break;
  679. }
  680. }
  681. Tomahawk::playlistinterface_ptr
  682. TrackView::playlistInterface() const
  683. {
  684. return proxyModel()->playlistInterface();
  685. }
  686. void
  687. TrackView::setPlaylistInterface( const Tomahawk::playlistinterface_ptr& playlistInterface )
  688. {
  689. proxyModel()->setPlaylistInterface( playlistInterface );
  690. }
  691. QString
  692. TrackView::title() const
  693. {
  694. return model()->title();
  695. }
  696. QString
  697. TrackView::description() const
  698. {
  699. return model()->description();
  700. }
  701. QPixmap
  702. TrackView::pixmap() const
  703. {
  704. return model()->icon();
  705. }
  706. bool
  707. TrackView::jumpToCurrentTrack()
  708. {
  709. scrollTo( proxyModel()->currentIndex(), QAbstractItemView::PositionAtCenter );
  710. selectionModel()->select( QModelIndex(), QItemSelectionModel::SelectCurrent );
  711. select( proxyModel()->currentIndex() );
  712. selectionModel()->select( proxyModel()->currentIndex(), QItemSelectionModel::SelectCurrent );
  713. return true;
  714. }
  715. bool
  716. TrackView::setFilter( const QString& filter )
  717. {
  718. ViewPage::setFilter( filter );
  719. m_proxyModel->setFilter( filter );
  720. return true;
  721. }
  722. void
  723. TrackView::deleteSelectedItems()
  724. {
  725. if ( !model()->isReadOnly() )
  726. {
  727. proxyModel()->removeIndexes( selectedIndexes() );
  728. }
  729. else
  730. {
  731. tDebug() << Q_FUNC_INFO << "Error: Model is read-only!";
  732. }
  733. }
  734. void
  735. TrackView::downloadSelectedItems()
  736. {
  737. foreach ( const QModelIndex& index, selectedIndexes() )
  738. {
  739. if ( index.column() )
  740. continue;
  741. PlayableItem* item = proxyModel()->itemFromIndex( proxyModel()->mapToSource( index ) );
  742. if ( !item )
  743. continue;
  744. if ( item->query()->results().isEmpty() || item->query()->results().first()->downloadFormats().isEmpty() )
  745. continue;
  746. if ( !DownloadManager::instance()->localFileForDownload( item->query()->results().first()->downloadFormats().first().url.toString() ).isEmpty() )
  747. continue;
  748. DownloadManager::instance()->addJob( item->result()->toDownloadJob( item->result()->downloadFormats().first() ) );
  749. }
  750. }
  751. void
  752. TrackView::verifySize()
  753. {
  754. if ( !autoResize() || !m_proxyModel || !m_proxyModel->rowCount() )
  755. return;
  756. unsigned int height = 0;
  757. for ( int i = 0; i < m_proxyModel->rowCount(); i++ )
  758. {
  759. height += indexRowSizeHint( m_proxyModel->index( i, 0 ) );
  760. }
  761. setFixedHeight( height + contentsMargins().top() + contentsMargins().bottom() );
  762. }
  763. void
  764. TrackView::setAutoResize( bool b )
  765. {
  766. m_autoResize = b;
  767. if ( m_autoResize )
  768. setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
  769. }
  770. void
  771. TrackView::setAlternatingRowColors( bool enable )
  772. {
  773. m_alternatingRowColors = enable;
  774. QTreeView::setAlternatingRowColors( enable );
  775. }
  776. void
  777. TrackView::expand( const QPersistentModelIndex& idx )
  778. {
  779. QTreeView::expand( idx );
  780. }
  781. void
  782. TrackView::select( const QPersistentModelIndex& idx )
  783. {
  784. if ( !selectedIndexes().isEmpty() )
  785. return;
  786. // selectionModel()->select( idx, QItemSelectionModel::SelectCurrent );
  787. currentChanged( idx, QModelIndex() );
  788. }
  789. void
  790. TrackView::selectFirstTrack()
  791. {
  792. if ( !m_proxyModel->rowCount() )
  793. return;
  794. if ( !selectedIndexes().isEmpty() )
  795. return;
  796. QModelIndex idx = m_proxyModel->index( 0, 0, QModelIndex() );
  797. PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( idx ) );
  798. if ( item->source() )
  799. {
  800. idx = m_proxyModel->index( 0, 0, idx );
  801. item = m_model->itemFromIndex( m_proxyModel->mapToSource( idx ) );
  802. }
  803. if ( item->query() )
  804. {
  805. // selectionModel()->select( idx, QItemSelectionModel::SelectCurrent );
  806. currentChanged( idx, QModelIndex() );
  807. }
  808. }
  809. PlayableModel*
  810. TrackView::model() const
  811. {
  812. return m_model.data();
  813. }