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