diff --git a/src/libtomahawk/playlist/ColumnView.cpp b/src/libtomahawk/playlist/ColumnView.cpp new file mode 100644 index 000000000..07818d32b --- /dev/null +++ b/src/libtomahawk/playlist/ColumnView.cpp @@ -0,0 +1,484 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2013, Christian Muehlhaeuser + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "ColumnView.h" + +#include "audio/AudioEngine.h" +#include "context/ContextWidget.h" +#include "utils/AnimatedSpinner.h" +#include "widgets/OverlayWidget.h" + +#include "ContextMenu.h" +#include "TomahawkSettings.h" +#include "ViewHeader.h" +#include "ColumnItemDelegate.h" +#include "ColumnViewPreviewWidget.h" +#include "TreeModel.h" +#include "PlayableItem.h" +#include "Source.h" +#include "ViewManager.h" +#include "utils/TomahawkUtilsGui.h" +#include "utils/Logger.h" + +#include +#include +#include +#include +#include +#include +#include + +#define SCROLL_TIMEOUT 280 + +using namespace Tomahawk; + + +ColumnView::ColumnView( QWidget* parent ) + : QColumnView( parent ) + , m_header( new ViewHeader( this ) ) + , m_overlay( new OverlayWidget( this ) ) + , m_model( 0 ) + , m_proxyModel( 0 ) + , m_delegate( 0 ) + , m_loadingSpinner( new LoadingSpinner( this ) ) + , m_previewWidget( new ColumnViewPreviewWidget( this ) ) + , m_updateContextView( true ) + , m_contextMenu( new ContextMenu( this ) ) +{ + setFrameShape( QFrame::NoFrame ); + setAttribute( Qt::WA_MacShowFocusRect, 0 ); + + setContentsMargins( 0, 0, 0, 0 ); + setMouseTracking( true ); + setAlternatingRowColors( true ); + setDragEnabled( true ); + setDropIndicatorShown( false ); + setDragDropOverwriteMode( false ); + setVerticalScrollMode( QAbstractItemView::ScrollPerPixel ); + setSelectionMode( QAbstractItemView::SingleSelection ); + setSelectionBehavior( QAbstractItemView::SelectRows ); + setContextMenuPolicy( Qt::CustomContextMenu ); + setProxyModel( new TreeProxyModel( this ) ); + setPreviewWidget( m_previewWidget ); + + m_timer.setInterval( SCROLL_TIMEOUT ); + connect( verticalScrollBar(), SIGNAL( rangeChanged( int, int ) ), SLOT( onViewChanged() ) ); + connect( verticalScrollBar(), SIGNAL( valueChanged( int ) ), SLOT( onViewChanged() ) ); + connect( &m_timer, SIGNAL( timeout() ), SLOT( onScrollTimeout() ) ); + + connect( this, SIGNAL( updatePreviewWidget( QModelIndex ) ), SLOT( onUpdatePreviewWidget( QModelIndex ) ) ); + connect( this, SIGNAL( doubleClicked( QModelIndex ) ), SLOT( onItemActivated( QModelIndex ) ) ); + connect( this, SIGNAL( customContextMenuRequested( QPoint ) ), SLOT( onCustomContextMenu( QPoint ) ) ); + connect( m_contextMenu, SIGNAL( triggered( int ) ), SLOT( onMenuTriggered( int ) ) ); +} + + +ColumnView::~ColumnView() +{ + tDebug() << Q_FUNC_INFO; +} + + +void +ColumnView::setProxyModel( TreeProxyModel* model ) +{ + m_proxyModel = model; + m_delegate = new ColumnItemDelegate( this, m_proxyModel ); + setItemDelegate( m_delegate ); + + QColumnView::setModel( m_proxyModel ); +} + + +void +ColumnView::setModel( QAbstractItemModel* model ) +{ + Q_UNUSED( model ); + tDebug() << "Explicitly use setPlaylistModel instead"; + Q_ASSERT( false ); +} + + +void +ColumnView::setTreeModel( TreeModel* model ) +{ + m_model = model; + + if ( m_proxyModel ) + { + m_proxyModel->setSourcePlayableModel( model ); + m_proxyModel->sort( 0 ); + } + + connect( m_proxyModel, SIGNAL( filteringStarted() ), SLOT( onFilteringStarted() ) ); + connect( m_proxyModel, SIGNAL( filteringFinished() ), m_loadingSpinner, SLOT( fadeOut() ) ); + + connect( m_proxyModel, SIGNAL( filteringFinished() ), SLOT( onFilterChangeFinished() ) ); + connect( m_proxyModel, SIGNAL( rowsInserted( QModelIndex, int, int ) ), SLOT( onViewChanged() ) ); + + guid(); // this will set the guid on the header + + m_header->setDefaultColumnWeights( m_proxyModel->columnWeights() ); + if ( m_proxyModel->style() == PlayableProxyModel::Large ) + { + setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + } + else + { + setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + } + + connect( model, SIGNAL( changed() ), this, SIGNAL( modelChanged() ) ); + emit modelChanged(); + +/* setColumnHidden( PlayableModel::Score, true ); // Hide score column per default + setColumnHidden( PlayableModel::Origin, true ); // Hide origin column per default + setColumnHidden( PlayableModel::Composer, true ); //Hide composer column per default + + setGuid( QString( "columnview/%1" ).arg( model->columnCount() ) ); + sortByColumn( PlayableModel::Artist, Qt::AscendingOrder );*/ + + QList< int > widths; + widths << 320; + setColumnWidths( widths ); +} + + +void +ColumnView::setEmptyTip( const QString& tip ) +{ + m_emptyTip = tip; + m_overlay->setText( tip ); +} + + +bool +ColumnView::setFilter( const QString& filter ) +{ + proxyModel()->setFilter( filter ); + return true; +} + + +void +ColumnView::onViewChanged() +{ + if ( m_timer.isActive() ) + m_timer.stop(); + + m_timer.start(); +} + + +void +ColumnView::onScrollTimeout() +{ + if ( m_timer.isActive() ) + m_timer.stop(); + + QModelIndex left = indexAt( viewport()->rect().topLeft() ); + while ( left.isValid() && left.parent().isValid() ) + left = left.parent(); + + QModelIndex right = indexAt( viewport()->rect().bottomLeft() ); + while ( right.isValid() && right.parent().isValid() ) + right = right.parent(); + + int max = m_proxyModel->playlistInterface()->trackCount(); + if ( right.isValid() ) + max = right.row() + 1; + + if ( !max ) + return; + + for ( int i = left.row(); i < max; i++ ) + { + m_model->getCover( m_proxyModel->mapToSource( m_proxyModel->index( i, 0 ) ) ); + } +} + + +void +ColumnView::currentChanged( const QModelIndex& current, const QModelIndex& previous ) +{ + QColumnView::currentChanged( current, previous ); + + if ( !m_updateContextView ) + return; + + PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( current ) ); + if ( item ) + { + if ( !item->result().isNull() ) + ViewManager::instance()->context()->setQuery( item->result()->toQuery() ); + else if ( !item->artist().isNull() ) + ViewManager::instance()->context()->setArtist( item->artist() ); + else if ( !item->album().isNull() ) + ViewManager::instance()->context()->setAlbum( item->album() ); + else if ( !item->query().isNull() ) + ViewManager::instance()->context()->setQuery( item->query() ); + } +} + + +void +ColumnView::onItemActivated( const QModelIndex& index ) +{ + PlayableItem* item = m_model->itemFromIndex( m_proxyModel->mapToSource( index ) ); + if ( item ) + { + if ( !item->result().isNull() && item->result()->isOnline() ) + { + AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), item->result() ); + } + else if ( !item->query().isNull() ) + { + AudioEngine::instance()->playItem( m_proxyModel->playlistInterface(), item->query() ); + } + } +} + + +void +ColumnView::keyPressEvent( QKeyEvent* event ) +{ + QColumnView::keyPressEvent( event ); + + if ( !model() ) + return; + + if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return ) + { + onItemActivated( currentIndex() ); + } +} + + +void +ColumnView::resizeEvent( QResizeEvent* event ) +{ + QColumnView::resizeEvent( event ); + m_header->checkState(); + + if ( !model() ) + return; + + if ( model()->columnCount( QModelIndex() ) == 1 ) + { + m_header->resizeSection( 0, event->size().width() ); + } +} + + +void +ColumnView::wheelEvent( QWheelEvent* event ) +{ + QColumnView::wheelEvent( event ); + + m_delegate->resetHoverIndex(); + repaint(); +} + + +void +ColumnView::onFilterChangeFinished() +{ + if ( selectedIndexes().count() ) + scrollTo( selectedIndexes().at( 0 ), QAbstractItemView::PositionAtCenter ); + + if ( !proxyModel()->filter().isEmpty() && !proxyModel()->playlistInterface()->trackCount() && model()->trackCount() ) + { + m_overlay->setText( tr( "Sorry, your filter '%1' did not match any results." ).arg( proxyModel()->filter() ) ); + m_overlay->show(); + } + else + { + if ( model()->trackCount() ) + { + m_overlay->hide(); + } + else + { + m_overlay->setText( m_emptyTip ); + m_overlay->show(); + } + } +} + + +void +ColumnView::onFilteringStarted() +{ + m_overlay->hide(); + m_loadingSpinner->fadeIn(); +} + + +void +ColumnView::startDrag( Qt::DropActions supportedActions ) +{ + QList pindexes; + QModelIndexList indexes; + foreach( const QModelIndex& idx, selectedIndexes() ) + { + if ( ( m_proxyModel->flags( idx ) & Qt::ItemIsDragEnabled ) ) + { + indexes << idx; + pindexes << idx; + } + } + + if ( indexes.count() == 0 ) + return; + + tDebug( LOGVERBOSE ) << "Dragging" << indexes.count() << "indexes"; + QMimeData* data = m_proxyModel->mimeData( indexes ); + if ( !data ) + return; + + QDrag* drag = new QDrag( this ); + drag->setMimeData( data ); + + QPixmap p; + if ( data->hasFormat( "application/tomahawk.metadata.artist" ) ) + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeArtist, indexes.count() ); + else if ( data->hasFormat( "application/tomahawk.metadata.album" ) ) + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeAlbum, indexes.count() ); + else + p = TomahawkUtils::createDragPixmap( TomahawkUtils::MediaTypeTrack, indexes.count() ); + + drag->setPixmap( p ); + drag->setHotSpot( QPoint( -20, -20 ) ); + + drag->exec( supportedActions, Qt::CopyAction ); +} + + +void +ColumnView::onCustomContextMenu( const QPoint& pos ) +{ + m_contextMenu->clear(); + + QModelIndex idx = indexAt( pos ); + idx = idx.sibling( idx.row(), 0 ); + m_contextMenuIndex = idx; + + if ( !idx.isValid() ) + return; + + QList queries; + QList artists; + QList albums; + + QModelIndexList indexes = selectedIndexes(); + if ( !indexes.contains( idx ) ) + { + indexes.clear(); + indexes << idx; + } + + foreach ( const QModelIndex& index, indexes ) + { + if ( index.column() || indexes.contains( index.parent() ) ) + continue; + + PlayableItem* item = m_proxyModel->itemFromIndex( m_proxyModel->mapToSource( index ) ); + + if ( item && !item->result().isNull() ) + queries << item->result()->toQuery(); + else if ( item && !item->query().isNull() ) + queries << item->query(); + if ( item && !item->artist().isNull() ) + artists << item->artist(); + if ( item && !item->album().isNull() ) + albums << item->album(); + } + + m_contextMenu->setQueries( queries ); + m_contextMenu->setArtists( artists ); + m_contextMenu->setAlbums( albums ); + m_contextMenu->setPlaylistInterface( proxyModel()->playlistInterface() ); + + m_contextMenu->exec( viewport()->mapToGlobal( pos ) ); +} + + +void +ColumnView::onMenuTriggered( int action ) +{ + switch ( action ) + { + case ContextMenu::ActionPlay: + onItemActivated( m_contextMenuIndex ); + break; + + default: + break; + } +} + + +bool +ColumnView::jumpToCurrentTrack() +{ + if ( !m_proxyModel || !m_proxyModel->sourceModel() ) + return false; + + scrollTo( m_proxyModel->currentIndex(), QAbstractItemView::PositionAtCenter ); + return true; +} + + +QString +ColumnView::guid() const +{ + if ( m_guid.isEmpty() ) + { + m_guid = QString( "columnview/%1" ).arg( m_model->columnCount( QModelIndex() ) ); + m_header->setGuid( m_guid ); + } + + return m_guid; +} + + +void +ColumnView::onUpdatePreviewWidget( const QModelIndex& index ) +{ + PlayableItem* item = m_proxyModel->itemFromIndex( m_proxyModel->mapToSource( index ) ); + if ( !item || !item->result() ) + { + QList< int > widths = columnWidths(); + QList< int > finalWidths; + foreach ( int w, widths ) + { + finalWidths << qMax( 320, w ); + } + setColumnWidths( finalWidths ); + + return; + } + + m_previewWidget->setQuery( item->result()->toQuery() ); + + QList< int > widths = columnWidths(); + const int previewWidth = viewport()->width() - widths.at( 0 ) - widths.at( 1 ) - widths.at( 2 ); + widths.removeLast(); + widths << qMax( previewWidth, m_previewWidget->minimumSize().width() + 32 ); + setColumnWidths( widths ); +} diff --git a/src/libtomahawk/playlist/ColumnView.h b/src/libtomahawk/playlist/ColumnView.h new file mode 100644 index 000000000..d2bcd847d --- /dev/null +++ b/src/libtomahawk/playlist/ColumnView.h @@ -0,0 +1,119 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2011, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef COLUMNVIEW_H +#define COLUMNVIEW_H + +#include +#include +#include + +#include "TreeProxyModel.h" +#include "ViewPage.h" + +#include "PlaylistInterface.h" + +#include "DllMacro.h" + +namespace Tomahawk +{ + class ContextMenu; +}; + +class ViewHeader; +class AnimatedSpinner; +class OverlayWidget; +class TreeModel; +class ColumnItemDelegate; +class ColumnViewPreviewWidget; + +class DLLEXPORT ColumnView : public QColumnView +{ +Q_OBJECT + +public: + explicit ColumnView( QWidget* parent = 0 ); + ~ColumnView(); + + virtual QString guid() const; + virtual void setGuid( const QString& guid ) { m_guid = guid; } + + void setProxyModel( TreeProxyModel* model ); + + TreeModel* model() const { return m_model; } + TreeProxyModel* proxyModel() const { return m_proxyModel; } + OverlayWidget* overlay() const { return m_overlay; } + + void setModel( QAbstractItemModel* model ); + void setTreeModel( TreeModel* model ); + + virtual bool setFilter( const QString& filter ); + void setEmptyTip( const QString& tip ); + + virtual bool jumpToCurrentTrack(); + + bool updatesContextView() const { return m_updateContextView; } + void setUpdatesContextView( bool b ) { m_updateContextView = b; } + +public slots: + void onItemActivated( const QModelIndex& index ); + +signals: + void modelChanged(); + +protected: + virtual void startDrag( Qt::DropActions supportedActions ); + virtual void resizeEvent( QResizeEvent* event ); + + virtual void keyPressEvent( QKeyEvent* event ); + virtual void wheelEvent( QWheelEvent* event ); + +protected slots: + virtual void currentChanged( const QModelIndex& current, const QModelIndex& previous ); + virtual void onUpdatePreviewWidget( const QModelIndex& index ); + +private slots: + void onFilterChangeFinished(); + void onFilteringStarted(); + void onViewChanged(); + void onScrollTimeout(); + + void onCustomContextMenu( const QPoint& pos ); + void onMenuTriggered( int action ); + +private: + ViewHeader* m_header; + OverlayWidget* m_overlay; + TreeModel* m_model; + TreeProxyModel* m_proxyModel; + ColumnItemDelegate* m_delegate; + AnimatedSpinner* m_loadingSpinner; + ColumnViewPreviewWidget* m_previewWidget; + + bool m_updateContextView; + + QModelIndex m_contextMenuIndex; + Tomahawk::ContextMenu* m_contextMenu; + + QString m_emptyTip; + QTimer m_timer; + mutable QString m_guid; +}; + +#endif // COLUMNVIEW_H